diff options
Diffstat (limited to 'src/libcharon/sa/ikev1')
36 files changed, 8954 insertions, 0 deletions
diff --git a/src/libcharon/sa/ikev1/authenticators/hybrid_authenticator.c b/src/libcharon/sa/ikev1/authenticators/hybrid_authenticator.c new file mode 100644 index 000000000..f1bc1ecc2 --- /dev/null +++ b/src/libcharon/sa/ikev1/authenticators/hybrid_authenticator.c @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "hybrid_authenticator.h" + +#include <daemon.h> + +typedef struct private_hybrid_authenticator_t private_hybrid_authenticator_t; + +/** + * Private data of an hybrid_authenticator_t object. + */ +struct private_hybrid_authenticator_t { + + /** + * Public authenticator_t interface. + */ + hybrid_authenticator_t public; + + /** + * Public key authenticator + */ + authenticator_t *sig; + + /** + * HASH payload authenticator without credentials + */ + authenticator_t *hash; +}; + +METHOD(authenticator_t, build_i, status_t, + private_hybrid_authenticator_t *this, message_t *message) +{ + return this->hash->build(this->hash, message); +} + +METHOD(authenticator_t, process_r, status_t, + private_hybrid_authenticator_t *this, message_t *message) +{ + return this->hash->process(this->hash, message); +} + +METHOD(authenticator_t, build_r, status_t, + private_hybrid_authenticator_t *this, message_t *message) +{ + return this->sig->build(this->sig, message); +} + +METHOD(authenticator_t, process_i, status_t, + private_hybrid_authenticator_t *this, message_t *message) +{ + return this->sig->process(this->sig, message); +} + +METHOD(authenticator_t, destroy, void, + private_hybrid_authenticator_t *this) +{ + DESTROY_IF(this->hash); + DESTROY_IF(this->sig); + free(this); +} + +/* + * Described in header. + */ +hybrid_authenticator_t *hybrid_authenticator_create(ike_sa_t *ike_sa, + bool initiator, diffie_hellman_t *dh, + chunk_t dh_value, chunk_t sa_payload, + chunk_t id_payload) +{ + private_hybrid_authenticator_t *this; + + INIT(this, + .public = { + .authenticator = { + .is_mutual = (void*)return_false, + .destroy = _destroy, + }, + }, + .sig = authenticator_create_v1(ike_sa, initiator, AUTH_RSA, dh, + dh_value, sa_payload, id_payload), + .hash = authenticator_create_v1(ike_sa, initiator, AUTH_PSK, + dh, dh_value, sa_payload, chunk_clone(id_payload)), + ); + if (!this->sig || !this->hash) + { + destroy(this); + return NULL; + } + if (initiator) + { + this->public.authenticator.build = _build_i; + this->public.authenticator.process = _process_i; + } + else + { + this->public.authenticator.build = _build_r; + this->public.authenticator.process = _process_r; + } + return &this->public; +} diff --git a/src/libcharon/sa/ikev1/authenticators/hybrid_authenticator.h b/src/libcharon/sa/ikev1/authenticators/hybrid_authenticator.h new file mode 100644 index 000000000..6a0bb1e59 --- /dev/null +++ b/src/libcharon/sa/ikev1/authenticators/hybrid_authenticator.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup hybrid_authenticator hybrid_authenticator + * @{ @ingroup authenticators + */ + +#ifndef HYBRID_AUTHENTICATOR_H_ +#define HYBRID_AUTHENTICATOR_H_ + +typedef struct hybrid_authenticator_t hybrid_authenticator_t; + +#include <sa/authenticator.h> + +/** + * Implementation of authenticator_t using IKEv1 hybrid authentication. + */ +struct hybrid_authenticator_t { + + /** + * Implemented authenticator_t interface. + */ + authenticator_t authenticator; +}; + +/** + * Create an authenticator to build hybrid signatures. + * + * @param ike_sa associated IKE_SA + * @param initiator TRUE if we are the IKE_SA initiator + * @param dh diffie hellman key exchange + * @param dh_value others public diffie hellman value + * @param sa_payload generated SA payload data, without payload header + * @param id_payload encoded ID payload of peer to authenticate or verify + * without payload header (gets owned) + * @return hybrid authenticator + */ +hybrid_authenticator_t *hybrid_authenticator_create(ike_sa_t *ike_sa, + bool initiator, diffie_hellman_t *dh, + chunk_t dh_value, chunk_t sa_payload, + chunk_t id_payload); + +#endif /** HYBRID_AUTHENTICATOR_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/authenticators/psk_v1_authenticator.c b/src/libcharon/sa/ikev1/authenticators/psk_v1_authenticator.c new file mode 100644 index 000000000..ce794a286 --- /dev/null +++ b/src/libcharon/sa/ikev1/authenticators/psk_v1_authenticator.c @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "psk_v1_authenticator.h" + +#include <daemon.h> +#include <sa/ikev1/keymat_v1.h> +#include <encoding/payloads/hash_payload.h> + +typedef struct private_psk_v1_authenticator_t private_psk_v1_authenticator_t; + +/** + * Private data of an psk_v1_authenticator_t object. + */ +struct private_psk_v1_authenticator_t { + + /** + * Public authenticator_t interface. + */ + psk_v1_authenticator_t public; + + /** + * Assigned IKE_SA + */ + ike_sa_t *ike_sa; + + /** + * TRUE if we are initiator + */ + bool initiator; + + /** + * DH key exchange + */ + diffie_hellman_t *dh; + + /** + * Others DH public value + */ + chunk_t dh_value; + + /** + * Encoded SA payload, without fixed header + */ + chunk_t sa_payload; + + /** + * Encoded ID payload, without fixed header + */ + chunk_t id_payload; +}; + +METHOD(authenticator_t, build, status_t, + private_psk_v1_authenticator_t *this, message_t *message) +{ + hash_payload_t *hash_payload; + keymat_v1_t *keymat; + chunk_t hash, dh; + + this->dh->get_my_public_value(this->dh, &dh); + keymat = (keymat_v1_t*)this->ike_sa->get_keymat(this->ike_sa); + hash = keymat->get_hash(keymat, this->initiator, dh, this->dh_value, + this->ike_sa->get_id(this->ike_sa), this->sa_payload, + this->id_payload); + free(dh.ptr); + + hash_payload = hash_payload_create(HASH_V1); + hash_payload->set_hash(hash_payload, hash); + message->add_payload(message, &hash_payload->payload_interface); + free(hash.ptr); + + return SUCCESS; +} + +METHOD(authenticator_t, process, status_t, + private_psk_v1_authenticator_t *this, message_t *message) +{ + hash_payload_t *hash_payload; + keymat_v1_t *keymat; + chunk_t hash, dh; + + hash_payload = (hash_payload_t*)message->get_payload(message, HASH_V1); + if (!hash_payload) + { + DBG1(DBG_IKE, "HASH payload missing in message"); + return FAILED; + } + + this->dh->get_my_public_value(this->dh, &dh); + keymat = (keymat_v1_t*)this->ike_sa->get_keymat(this->ike_sa); + hash = keymat->get_hash(keymat, !this->initiator, this->dh_value, dh, + this->ike_sa->get_id(this->ike_sa), this->sa_payload, + this->id_payload); + free(dh.ptr); + if (chunk_equals(hash, hash_payload->get_hash(hash_payload))) + { + free(hash.ptr); + return SUCCESS; + } + free(hash.ptr); + DBG1(DBG_IKE, "calculated HASH does not match HASH payload"); + return FAILED; +} + +METHOD(authenticator_t, destroy, void, + private_psk_v1_authenticator_t *this) +{ + chunk_free(&this->id_payload); + free(this); +} + +/* + * Described in header. + */ +psk_v1_authenticator_t *psk_v1_authenticator_create(ike_sa_t *ike_sa, + bool initiator, diffie_hellman_t *dh, + chunk_t dh_value, chunk_t sa_payload, + chunk_t id_payload) +{ + private_psk_v1_authenticator_t *this; + + INIT(this, + .public = { + .authenticator = { + .build = _build, + .process = _process, + .is_mutual = (void*)return_false, + .destroy = _destroy, + }, + }, + .ike_sa = ike_sa, + .initiator = initiator, + .dh = dh, + .dh_value = dh_value, + .sa_payload = sa_payload, + .id_payload = id_payload, + ); + + return &this->public; +} diff --git a/src/libcharon/sa/ikev1/authenticators/psk_v1_authenticator.h b/src/libcharon/sa/ikev1/authenticators/psk_v1_authenticator.h new file mode 100644 index 000000000..194b96456 --- /dev/null +++ b/src/libcharon/sa/ikev1/authenticators/psk_v1_authenticator.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup psk_v1_authenticator psk_v1_authenticator + * @{ @ingroup authenticators + */ + +#ifndef PSK_V1_AUTHENTICATOR_H_ +#define PSK_V1_AUTHENTICATOR_H_ + +typedef struct psk_v1_authenticator_t psk_v1_authenticator_t; + +#include <sa/authenticator.h> + +/** + * Implementation of authenticator_t using pre-shared keys for IKEv1. + */ +struct psk_v1_authenticator_t { + + /** + * Implemented authenticator_t interface. + */ + authenticator_t authenticator; +}; + +/** + * Create an authenticator to build PSK signatures. + * + * @param ike_sa associated IKE_SA + * @param initiator TRUE if we are the IKE_SA initiator + * @param dh diffie hellman key exchange + * @param dh_value others public diffie hellman value + * @param sa_payload generated SA payload data, without payload header + * @param id_payload encoded ID payload of peer to authenticate or verify + * without payload header (gets owned) + * @return PSK authenticator + */ +psk_v1_authenticator_t *psk_v1_authenticator_create(ike_sa_t *ike_sa, + bool initiator, diffie_hellman_t *dh, + chunk_t dh_value, chunk_t sa_payload, + chunk_t id_payload); + +#endif /** PSK_V1_AUTHENTICATOR_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/authenticators/pubkey_v1_authenticator.c b/src/libcharon/sa/ikev1/authenticators/pubkey_v1_authenticator.c new file mode 100644 index 000000000..56fcf2c9d --- /dev/null +++ b/src/libcharon/sa/ikev1/authenticators/pubkey_v1_authenticator.c @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "pubkey_v1_authenticator.h" + +#include <daemon.h> +#include <sa/ikev1/keymat_v1.h> +#include <encoding/payloads/hash_payload.h> + +typedef struct private_pubkey_v1_authenticator_t private_pubkey_v1_authenticator_t; + +/** + * Private data of an pubkey_v1_authenticator_t object. + */ +struct private_pubkey_v1_authenticator_t { + + /** + * Public authenticator_t interface. + */ + pubkey_v1_authenticator_t public; + + /** + * Assigned IKE_SA + */ + ike_sa_t *ike_sa; + + /** + * TRUE if we are initiator + */ + bool initiator; + + /** + * DH key exchange + */ + diffie_hellman_t *dh; + + /** + * Others DH public value + */ + chunk_t dh_value; + + /** + * Encoded SA payload, without fixed header + */ + chunk_t sa_payload; + + /** + * Encoded ID payload, without fixed header + */ + chunk_t id_payload; +}; + +METHOD(authenticator_t, build, status_t, + private_pubkey_v1_authenticator_t *this, message_t *message) +{ + hash_payload_t *sig_payload; + chunk_t hash, sig, dh; + keymat_v1_t *keymat; + status_t status; + private_key_t *private; + identification_t *id; + auth_cfg_t *auth; + key_type_t type; + signature_scheme_t scheme; + + /* TODO-IKEv1: other key types */ + type = KEY_RSA; + scheme = SIGN_RSA_EMSA_PKCS1_NULL; + + id = this->ike_sa->get_my_id(this->ike_sa); + auth = this->ike_sa->get_auth_cfg(this->ike_sa, TRUE); + private = lib->credmgr->get_private(lib->credmgr, type, id, auth); + if (!private) + { + DBG1(DBG_IKE, "no private key found for '%Y'", id); + return NOT_FOUND; + } + + this->dh->get_my_public_value(this->dh, &dh); + keymat = (keymat_v1_t*)this->ike_sa->get_keymat(this->ike_sa); + hash = keymat->get_hash(keymat, this->initiator, dh, this->dh_value, + this->ike_sa->get_id(this->ike_sa), this->sa_payload, + this->id_payload); + free(dh.ptr); + + if (private->sign(private, scheme, hash, &sig)) + { + sig_payload = hash_payload_create(SIGNATURE_V1); + sig_payload->set_hash(sig_payload, sig); + free(sig.ptr); + message->add_payload(message, &sig_payload->payload_interface); + status = SUCCESS; + DBG1(DBG_IKE, "authentication of '%Y' (myself) successful", id); + } + else + { + DBG1(DBG_IKE, "authentication of '%Y' (myself) failed", id); + status = FAILED; + } + private->destroy(private); + free(hash.ptr); + + return status; +} + +METHOD(authenticator_t, process, status_t, + private_pubkey_v1_authenticator_t *this, message_t *message) +{ + chunk_t hash, sig, dh; + keymat_v1_t *keymat; + public_key_t *public; + hash_payload_t *sig_payload; + auth_cfg_t *auth, *current_auth; + enumerator_t *enumerator; + status_t status = NOT_FOUND; + key_type_t type; + signature_scheme_t scheme; + identification_t *id; + + /* TODO-IKEv1: currently RSA only */ + type = KEY_RSA; + scheme = SIGN_RSA_EMSA_PKCS1_NULL; + + sig_payload = (hash_payload_t*)message->get_payload(message, SIGNATURE_V1); + if (!sig_payload) + { + DBG1(DBG_IKE, "SIG payload missing in message"); + return FAILED; + } + + id = this->ike_sa->get_other_id(this->ike_sa); + this->dh->get_my_public_value(this->dh, &dh); + keymat = (keymat_v1_t*)this->ike_sa->get_keymat(this->ike_sa); + hash = keymat->get_hash(keymat, !this->initiator, this->dh_value, dh, + this->ike_sa->get_id(this->ike_sa), this->sa_payload, + this->id_payload); + free(dh.ptr); + + sig = sig_payload->get_hash(sig_payload); + auth = this->ike_sa->get_auth_cfg(this->ike_sa, FALSE); + enumerator = lib->credmgr->create_public_enumerator(lib->credmgr, type, + id, auth); + while (enumerator->enumerate(enumerator, &public, ¤t_auth)) + { + if (public->verify(public, scheme, hash, sig)) + { + DBG1(DBG_IKE, "authentication of '%Y' with %N successful", + id, key_type_names, type); + status = SUCCESS; + auth->merge(auth, current_auth, FALSE); + auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PUBKEY); + break; + } + else + { + DBG1(DBG_IKE, "signature validation failed, looking for another key"); + status = FAILED; + } + } + enumerator->destroy(enumerator); + free(hash.ptr); + if (status != SUCCESS) + { + DBG1(DBG_IKE, "no trusted %N public key found for '%Y'", + key_type_names, type, id); + } + return status; +} + +METHOD(authenticator_t, destroy, void, + private_pubkey_v1_authenticator_t *this) +{ + chunk_free(&this->id_payload); + free(this); +} + +/* + * Described in header. + */ +pubkey_v1_authenticator_t *pubkey_v1_authenticator_create(ike_sa_t *ike_sa, + bool initiator, diffie_hellman_t *dh, + chunk_t dh_value, chunk_t sa_payload, + chunk_t id_payload) +{ + private_pubkey_v1_authenticator_t *this; + + INIT(this, + .public = { + .authenticator = { + .build = _build, + .process = _process, + .is_mutual = (void*)return_false, + .destroy = _destroy, + }, + }, + .ike_sa = ike_sa, + .initiator = initiator, + .dh = dh, + .dh_value = dh_value, + .sa_payload = sa_payload, + .id_payload = id_payload, + ); + + return &this->public; +} diff --git a/src/libcharon/sa/ikev1/authenticators/pubkey_v1_authenticator.h b/src/libcharon/sa/ikev1/authenticators/pubkey_v1_authenticator.h new file mode 100644 index 000000000..bafc3a2b2 --- /dev/null +++ b/src/libcharon/sa/ikev1/authenticators/pubkey_v1_authenticator.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup pubkey_v1_authenticator pubkey_v1_authenticator + * @{ @ingroup authenticators + */ + +#ifndef PUBKEY_V1_AUTHENTICATOR_H_ +#define PUBKEY_V1_AUTHENTICATOR_H_ + +typedef struct pubkey_v1_authenticator_t pubkey_v1_authenticator_t; + +#include <sa/authenticator.h> + +/** + * Implementation of authenticator_t using public keys for IKEv1. + */ +struct pubkey_v1_authenticator_t { + + /** + * Implemented authenticator_t interface. + */ + authenticator_t authenticator; +}; + +/** + * Create an authenticator to build and verify public key signatures. + * + * @param ike_sa associated IKE_SA + * @param initiator TRUE if we are IKE_SA initiator + * @param dh diffie hellman key exchange + * @param dh_value others public diffie hellman value + * @param sa_payload generated SA payload data, without payload header + * @param id_payload encoded ID payload of peer to authenticate or verify + * without payload header (gets owned) + * @return pubkey authenticator + */ +pubkey_v1_authenticator_t *pubkey_v1_authenticator_create(ike_sa_t *ike_sa, + bool initiator, diffie_hellman_t *dh, + chunk_t dh_value, chunk_t sa_payload, + chunk_t id_payload); + +#endif /** PUBKEY_V1_AUTHENTICATOR_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/authenticators/xauth/xauth_manager.c b/src/libcharon/sa/ikev1/authenticators/xauth/xauth_manager.c new file mode 100644 index 000000000..432c9c0ab --- /dev/null +++ b/src/libcharon/sa/ikev1/authenticators/xauth/xauth_manager.c @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "xauth_manager.h" + +#include <utils/linked_list.h> +#include <threading/rwlock.h> + +typedef struct private_xauth_manager_t private_xauth_manager_t; +typedef struct xauth_entry_t xauth_entry_t; + +/** + * XAuth constructor entry + */ +struct xauth_entry_t { + + /** + * Xauth backend name + */ + char *name; + + /** + * Role of the method, XAUTH_SERVER or XAUTH_PEER + */ + xauth_role_t role; + + /** + * constructor function to create instance + */ + xauth_constructor_t constructor; +}; + +/** + * private data of xauth_manager + */ +struct private_xauth_manager_t { + + /** + * public functions + */ + xauth_manager_t public; + + /** + * list of eap_entry_t's + */ + linked_list_t *methods; + + /** + * rwlock to lock methods + */ + rwlock_t *lock; +}; + +METHOD(xauth_manager_t, add_method, void, + private_xauth_manager_t *this, char *name, xauth_role_t role, + xauth_constructor_t constructor) +{ + xauth_entry_t *entry; + + INIT(entry, + .name = name, + .role = role, + .constructor = constructor, + ); + + this->lock->write_lock(this->lock); + this->methods->insert_last(this->methods, entry); + this->lock->unlock(this->lock); +} + +METHOD(xauth_manager_t, remove_method, void, + private_xauth_manager_t *this, xauth_constructor_t constructor) +{ + enumerator_t *enumerator; + xauth_entry_t *entry; + + this->lock->write_lock(this->lock); + enumerator = this->methods->create_enumerator(this->methods); + while (enumerator->enumerate(enumerator, &entry)) + { + if (constructor == entry->constructor) + { + this->methods->remove_at(this->methods, enumerator); + free(entry); + } + } + enumerator->destroy(enumerator); + this->lock->unlock(this->lock); +} + +METHOD(xauth_manager_t, create_instance, xauth_method_t*, + private_xauth_manager_t *this, char *name, xauth_role_t role, + identification_t *server, identification_t *peer) +{ + enumerator_t *enumerator; + xauth_entry_t *entry; + xauth_method_t *method = NULL; + + this->lock->read_lock(this->lock); + enumerator = this->methods->create_enumerator(this->methods); + while (enumerator->enumerate(enumerator, &entry)) + { + if (role == entry->role && + (!name || streq(name, entry->name))) + { + method = entry->constructor(server, peer); + if (method) + { + break; + } + } + } + enumerator->destroy(enumerator); + this->lock->unlock(this->lock); + return method; +} + +METHOD(xauth_manager_t, destroy, void, + private_xauth_manager_t *this) +{ + this->methods->destroy_function(this->methods, free); + this->lock->destroy(this->lock); + free(this); +} + +/* + * See header + */ +xauth_manager_t *xauth_manager_create() +{ + private_xauth_manager_t *this; + + INIT(this, + .public = { + .add_method = _add_method, + .remove_method = _remove_method, + .create_instance = _create_instance, + .destroy = _destroy, + }, + .methods = linked_list_create(), + .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), + ); + + return &this->public; +} diff --git a/src/libcharon/sa/ikev1/authenticators/xauth/xauth_manager.h b/src/libcharon/sa/ikev1/authenticators/xauth/xauth_manager.h new file mode 100644 index 000000000..e7e84d06b --- /dev/null +++ b/src/libcharon/sa/ikev1/authenticators/xauth/xauth_manager.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup xauth_manager xauth_manager + * @{ @ingroup xauth + */ + +#ifndef XAUTH_MANAGER_H_ +#define XAUTH_MANAGER_H_ + +#include <sa/ikev1/authenticators/xauth/xauth_method.h> + +typedef struct xauth_manager_t xauth_manager_t; + +/** + * The XAuth manager manages all XAuth implementations and creates instances. + * + * A plugin registers it's implemented XAuth method at the manager by + * providing type and a contructor function. The manager then instanciates + * xauth_method_t instances through the provided constructor to handle + * XAuth authentication. + */ +struct xauth_manager_t { + + /** + * Register a XAuth method implementation. + * + * @param name backend name to register + * @param role XAUTH_SERVER or XAUTH_PEER + * @param constructor constructor function, returns an xauth_method_t + */ + void (*add_method)(xauth_manager_t *this, char *name, + xauth_role_t role, xauth_constructor_t constructor); + + /** + * Unregister a XAuth method implementation using it's constructor. + * + * @param constructor constructor function, as added in add_method + */ + void (*remove_method)(xauth_manager_t *this, xauth_constructor_t constructor); + + /** + * Create a new XAuth method instance. + * + * @param name backend name, as it was registered with + * @param role XAUTH_SERVER or XAUTH_PEER + * @param server identity of the server + * @param peer identity of the peer (client) + * @return XAUTH method instance, NULL if no constructor found + */ + xauth_method_t* (*create_instance)(xauth_manager_t *this, + char *name, xauth_role_t role, + identification_t *server, identification_t *peer); + + /** + * Destroy a eap_manager instance. + */ + void (*destroy)(xauth_manager_t *this); +}; + +/** + * Create a eap_manager instance. + */ +xauth_manager_t *xauth_manager_create(); + +#endif /** XAUTH_MANAGER_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/authenticators/xauth/xauth_method.c b/src/libcharon/sa/ikev1/authenticators/xauth/xauth_method.c new file mode 100644 index 000000000..838822d1e --- /dev/null +++ b/src/libcharon/sa/ikev1/authenticators/xauth/xauth_method.c @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2006 Martin Willi + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "xauth_method.h" + +#include <daemon.h> + +ENUM(xauth_role_names, XAUTH_SERVER, XAUTH_PEER, + "XAUTH_SERVER", + "XAUTH_PEER", +); + +/** + * See header + */ +bool xauth_method_register(plugin_t *plugin, plugin_feature_t *feature, + bool reg, void *data) +{ + if (reg) + { + charon->xauth->add_method(charon->xauth, feature->arg.xauth, + feature->type == FEATURE_XAUTH_SERVER ? XAUTH_SERVER : XAUTH_PEER, + (xauth_constructor_t)data); + } + else + { + charon->xauth->remove_method(charon->xauth, (xauth_constructor_t)data); + } + return TRUE; +} diff --git a/src/libcharon/sa/ikev1/authenticators/xauth/xauth_method.h b/src/libcharon/sa/ikev1/authenticators/xauth/xauth_method.h new file mode 100644 index 000000000..9f6067dbf --- /dev/null +++ b/src/libcharon/sa/ikev1/authenticators/xauth/xauth_method.h @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2006 Martin Willi + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup xauth_method xauth_method + * @{ @ingroup xauth + */ + +#ifndef XAUTH_METHOD_H_ +#define XAUTH_METHOD_H_ + +typedef struct xauth_method_t xauth_method_t; +typedef enum xauth_role_t xauth_role_t; + +#include <library.h> +#include <plugins/plugin.h> +#include <utils/identification.h> +#include <encoding/payloads/cp_payload.h> + +/** + * Role of an xauth_method, SERVER or PEER (client) + */ +enum xauth_role_t { + XAUTH_SERVER, + XAUTH_PEER, +}; + +/** + * enum names for xauth_role_t. + */ +extern enum_name_t *xauth_role_names; + +/** + * Interface of an XAuth method for server and client side. + * + * An XAuth method initiates an XAuth exchange and processes requests and + * responses. An XAuth method may need multiple exchanges before succeeding. + * Sending of XAUTH(STATUS) message is done by the framework, not a method. + */ +struct xauth_method_t { + + /** + * Initiate the XAuth exchange. + * + * initiate() is only useable for server implementations, as clients only + * reply to server requests. + * A cp_payload is created in "out" if result is NEED_MORE. + * + * @param out cp_payload to send to the client + * @return + * - NEED_MORE, if an other exchange is required + * - FAILED, if unable to create XAuth request payload + */ + status_t (*initiate) (xauth_method_t *this, cp_payload_t **out); + + /** + * Process a received XAuth message. + * + * A cp_payload is created in "out" if result is NEED_MORE. + * + * @param in cp_payload response received + * @param out created cp_payload to send + * @return + * - NEED_MORE, if an other exchange is required + * - FAILED, if XAuth method failed + * - SUCCESS, if XAuth method succeeded + */ + status_t (*process) (xauth_method_t *this, cp_payload_t *in, + cp_payload_t **out); + + /** + * Get the XAuth username received as XAuth initiator. + * + * @return used XAuth username, pointer to internal data + */ + identification_t* (*get_identity)(xauth_method_t *this); + + /** + * Destroys a eap_method_t object. + */ + void (*destroy) (xauth_method_t *this); +}; + +/** + * Constructor definition for a pluggable XAuth method. + * + * Each XAuth module must define a constructor function which will return + * an initialized object with the methods defined in xauth_method_t. + * Constructors for server and peers are identical, to support both roles + * of a XAuth method, a plugin needs register two constructors in the + * xauth_manager_t. + * + * @param server ID of the server to use for credential lookup + * @param peer ID of the peer to use for credential lookup + * @return implementation of the eap_method_t interface + */ +typedef xauth_method_t *(*xauth_constructor_t)(identification_t *server, + identification_t *peer); + +/** + * Helper function to (un-)register XAuth methods from plugin features. + * + * This function is a plugin_feature_callback_t and can be used with the + * PLUGIN_CALLBACK macro to register a XAuth method constructor. + * + * @param plugin plugin registering the XAuth method constructor + * @param feature associated plugin feature + * @param reg TRUE to register, FALSE to unregister. + * @param data data passed to callback, an xauth_constructor_t + */ +bool xauth_method_register(plugin_t *plugin, plugin_feature_t *feature, + bool reg, void *data); + +#endif /** XAUTH_METHOD_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/keymat_v1.c b/src/libcharon/sa/ikev1/keymat_v1.c new file mode 100644 index 000000000..100c9526a --- /dev/null +++ b/src/libcharon/sa/ikev1/keymat_v1.c @@ -0,0 +1,1008 @@ +/* + * Copyright (C) 2011 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "keymat_v1.h" + +#include <daemon.h> +#include <encoding/generator.h> +#include <encoding/payloads/nonce_payload.h> +#include <utils/linked_list.h> + +typedef struct private_keymat_v1_t private_keymat_v1_t; + +/** + * Max. number of IVs to track. + */ +#define MAX_IV 3 + +/** + * Max. number of Quick Modes to track. + */ +#define MAX_QM 2 + +/** + * Data stored for IVs + */ +typedef struct { + /** message ID */ + u_int32_t mid; + /** current IV */ + chunk_t iv; + /** last block of encrypted message */ + chunk_t last_block; +} iv_data_t; + +/** + * Private data of an keymat_t object. + */ +struct private_keymat_v1_t { + + /** + * Public keymat_v1_t interface. + */ + keymat_v1_t public; + + /** + * IKE_SA Role, initiator or responder + */ + bool initiator; + + /** + * General purpose PRF + */ + prf_t *prf; + + /** + * Negotiated PRF algorithm + */ + pseudo_random_function_t prf_alg; + + /** + * Crypter wrapped in an aead_t interface + */ + aead_t *aead; + + /** + * Hasher used for IV generation (and other things like e.g. NAT-T) + */ + hasher_t *hasher; + + /** + * Key used for authentication during main mode + */ + chunk_t skeyid; + + /** + * Key to derive key material from for non-ISAKMP SAs, rekeying + */ + chunk_t skeyid_d; + + /** + * Key used for authentication after main mode + */ + chunk_t skeyid_a; + + /** + * Phase 1 IV + */ + iv_data_t phase1_iv; + + /** + * Keep track of IVs for exchanges after phase 1. We store only a limited + * number of IVs in an MRU sort of way. Stores iv_data_t objects. + */ + linked_list_t *ivs; + + /** + * Keep track of Nonces during Quick Mode exchanges. Only a limited number + * of QMs are tracked at the same time. Stores qm_data_t objects. + */ + linked_list_t *qms; +}; + + +/** + * Destroy an iv_data_t object. + */ +static void iv_data_destroy(iv_data_t *this) +{ + chunk_free(&this->last_block); + chunk_free(&this->iv); + free(this); +} + +/** + * Data stored for Quick Mode exchanges + */ +typedef struct { + /** message ID */ + u_int32_t mid; + /** Ni_b (Nonce from first message) */ + chunk_t n_i; + /** Nr_b (Nonce from second message) */ + chunk_t n_r; +} qm_data_t; + +/** + * Destroy a qm_data_t object. + */ +static void qm_data_destroy(qm_data_t *this) +{ + chunk_free(&this->n_i); + chunk_free(&this->n_r); + free(this); +} + +/** + * Constants used in key derivation. + */ +static const chunk_t octet_0 = chunk_from_chars(0x00); +static const chunk_t octet_1 = chunk_from_chars(0x01); +static const chunk_t octet_2 = chunk_from_chars(0x02); + +/** + * Simple aead_t implementation without support for authentication. + */ +typedef struct { + /** implements aead_t interface */ + aead_t aead; + /** crypter to be used */ + crypter_t *crypter; +} private_aead_t; + + +METHOD(aead_t, encrypt, void, + private_aead_t *this, chunk_t plain, chunk_t assoc, chunk_t iv, + chunk_t *encrypted) +{ + this->crypter->encrypt(this->crypter, plain, iv, encrypted); +} + +METHOD(aead_t, decrypt, bool, + private_aead_t *this, chunk_t encrypted, chunk_t assoc, chunk_t iv, + chunk_t *plain) +{ + this->crypter->decrypt(this->crypter, encrypted, iv, plain); + return TRUE; +} + +METHOD(aead_t, get_block_size, size_t, + private_aead_t *this) +{ + return this->crypter->get_block_size(this->crypter); +} + +METHOD(aead_t, get_icv_size, size_t, + private_aead_t *this) +{ + return 0; +} + +METHOD(aead_t, get_iv_size, size_t, + private_aead_t *this) +{ + /* in order to create the messages properly we return 0 here */ + return 0; +} + +METHOD(aead_t, get_key_size, size_t, + private_aead_t *this) +{ + return this->crypter->get_key_size(this->crypter); +} + +METHOD(aead_t, set_key, void, + private_aead_t *this, chunk_t key) +{ + this->crypter->set_key(this->crypter, key); +} + +METHOD(aead_t, aead_destroy, void, + private_aead_t *this) +{ + this->crypter->destroy(this->crypter); + free(this); +} + +/** + * Expand SKEYID_e according to Appendix B in RFC 2409. + * TODO-IKEv1: verify keys (e.g. for weak keys, see Appendix B) + */ +static chunk_t expand_skeyid_e(chunk_t skeyid_e, size_t key_size, prf_t *prf) +{ + size_t block_size; + chunk_t seed, ka; + int i; + + if (skeyid_e.len >= key_size) + { /* no expansion required, reduce to key_size */ + skeyid_e.len = key_size; + return skeyid_e; + } + block_size = prf->get_block_size(prf); + ka = chunk_alloc((key_size / block_size + 1) * block_size); + ka.len = key_size; + + /* Ka = K1 | K2 | ..., K1 = prf(SKEYID_e, 0), K2 = prf(SKEYID_e, K1) ... */ + prf->set_key(prf, skeyid_e); + seed = octet_0; + for (i = 0; i < key_size; i += block_size) + { + prf->get_bytes(prf, seed, ka.ptr + i); + seed = chunk_create(ka.ptr + i, block_size); + } + chunk_clear(&skeyid_e); + return ka; +} + +/** + * Create a simple implementation of the aead_t interface which only encrypts + * or decrypts data. + */ +static aead_t *create_aead(proposal_t *proposal, prf_t *prf, chunk_t skeyid_e) +{ + private_aead_t *this; + u_int16_t alg, key_size; + crypter_t *crypter; + chunk_t ka; + + if (!proposal->get_algorithm(proposal, ENCRYPTION_ALGORITHM, &alg, + &key_size)) + { + DBG1(DBG_IKE, "no %N selected", + transform_type_names, ENCRYPTION_ALGORITHM); + return NULL; + } + crypter = lib->crypto->create_crypter(lib->crypto, alg, key_size / 8); + if (!crypter) + { + DBG1(DBG_IKE, "%N %N (key size %d) not supported!", + transform_type_names, ENCRYPTION_ALGORITHM, + encryption_algorithm_names, alg, key_size); + return NULL; + } + key_size = crypter->get_key_size(crypter); + ka = expand_skeyid_e(skeyid_e, crypter->get_key_size(crypter), prf); + DBG4(DBG_IKE, "encryption key Ka %B", &ka); + crypter->set_key(crypter, ka); + chunk_clear(&ka); + + INIT(this, + .aead = { + .encrypt = _encrypt, + .decrypt = _decrypt, + .get_block_size = _get_block_size, + .get_icv_size = _get_icv_size, + .get_iv_size = _get_iv_size, + .get_key_size = _get_key_size, + .set_key = _set_key, + .destroy = _aead_destroy, + }, + .crypter = crypter, + ); + return &this->aead; +} + +/** + * Converts integrity algorithm to PRF algorithm + */ +static u_int16_t auth_to_prf(u_int16_t alg) +{ + switch (alg) + { + case AUTH_HMAC_SHA1_96: + return PRF_HMAC_SHA1; + case AUTH_HMAC_SHA2_256_128: + return PRF_HMAC_SHA2_256; + case AUTH_HMAC_SHA2_384_192: + return PRF_HMAC_SHA2_384; + case AUTH_HMAC_SHA2_512_256: + return PRF_HMAC_SHA2_512; + case AUTH_HMAC_MD5_96: + return PRF_HMAC_MD5; + case AUTH_AES_XCBC_96: + return PRF_AES128_XCBC; + default: + return PRF_UNDEFINED; + } +} + +/** + * Converts integrity algorithm to hash algorithm + */ +static u_int16_t auth_to_hash(u_int16_t alg) +{ + switch (alg) + { + case AUTH_HMAC_SHA1_96: + return HASH_SHA1; + case AUTH_HMAC_SHA2_256_128: + return HASH_SHA256; + case AUTH_HMAC_SHA2_384_192: + return HASH_SHA384; + case AUTH_HMAC_SHA2_512_256: + return HASH_SHA512; + case AUTH_HMAC_MD5_96: + return HASH_MD5; + default: + return HASH_UNKNOWN; + } +} + +/** + * Adjust the key length for PRF algorithms that expect a fixed key length. + */ +static void adjust_keylen(u_int16_t alg, chunk_t *key) +{ + switch (alg) + { + case PRF_AES128_XCBC: + /* while rfc4434 defines variable keys for AES-XCBC, rfc3664 does + * not and therefore fixed key semantics apply to XCBC for key + * derivation. */ + key->len = min(key->len, 16); + break; + default: + /* all other algorithms use variable key length */ + break; + } +} + +METHOD(keymat_v1_t, derive_ike_keys, bool, + private_keymat_v1_t *this, proposal_t *proposal, diffie_hellman_t *dh, + chunk_t dh_other, chunk_t nonce_i, chunk_t nonce_r, ike_sa_id_t *id, + auth_method_t auth, shared_key_t *shared_key) +{ + chunk_t g_xy, g_xi, g_xr, dh_me, spi_i, spi_r, nonces, data, skeyid_e; + u_int16_t alg; + + spi_i = chunk_alloca(sizeof(u_int64_t)); + spi_r = chunk_alloca(sizeof(u_int64_t)); + + if (!proposal->get_algorithm(proposal, PSEUDO_RANDOM_FUNCTION, &alg, NULL)) + { /* no PRF negotiated, use HMAC version of integrity algorithm instead */ + if (!proposal->get_algorithm(proposal, INTEGRITY_ALGORITHM, &alg, NULL) + || (alg = auth_to_prf(alg)) == PRF_UNDEFINED) + { + DBG1(DBG_IKE, "no %N selected", + transform_type_names, PSEUDO_RANDOM_FUNCTION); + return FALSE; + } + } + this->prf_alg = alg; + this->prf = lib->crypto->create_prf(lib->crypto, alg); + if (!this->prf) + { + DBG1(DBG_IKE, "%N %N not supported!", + transform_type_names, PSEUDO_RANDOM_FUNCTION, + pseudo_random_function_names, alg); + return FALSE; + } + if (this->prf->get_block_size(this->prf) < + this->prf->get_key_size(this->prf)) + { /* TODO-IKEv1: support PRF output expansion (RFC 2409, Appendix B) */ + DBG1(DBG_IKE, "expansion of %N %N output not supported!", + transform_type_names, PSEUDO_RANDOM_FUNCTION, + pseudo_random_function_names, alg); + return FALSE; + } + + if (dh->get_shared_secret(dh, &g_xy) != SUCCESS) + { + return FALSE; + } + DBG4(DBG_IKE, "shared Diffie Hellman secret %B", &g_xy); + + *((u_int64_t*)spi_i.ptr) = id->get_initiator_spi(id); + *((u_int64_t*)spi_r.ptr) = id->get_responder_spi(id); + nonces = chunk_cata("cc", nonce_i, nonce_r); + + switch (auth) + { + case AUTH_PSK: + case AUTH_XAUTH_INIT_PSK: + { /* SKEYID = prf(pre-shared-key, Ni_b | Nr_b) */ + chunk_t psk; + if (!shared_key) + { + chunk_clear(&g_xy); + return FALSE; + } + psk = shared_key->get_key(shared_key); + adjust_keylen(alg, &psk); + this->prf->set_key(this->prf, psk); + this->prf->allocate_bytes(this->prf, nonces, &this->skeyid); + break; + } + case AUTH_RSA: + case AUTH_XAUTH_INIT_RSA: + case AUTH_XAUTH_RESP_RSA: + case AUTH_HYBRID_INIT_RSA: + case AUTH_HYBRID_RESP_RSA: + { + this->prf->set_key(this->prf, nonces); + this->prf->allocate_bytes(this->prf, g_xy, &this->skeyid); + break; + } + default: + /* TODO-IKEv1: implement key derivation for other schemes */ + /* authentication class not supported */ + chunk_clear(&g_xy); + return FALSE; + } + adjust_keylen(alg, &this->skeyid); + DBG4(DBG_IKE, "SKEYID %B", &this->skeyid); + + /* SKEYID_d = prf(SKEYID, g^xy | CKY-I | CKY-R | 0) */ + data = chunk_cat("cccc", g_xy, spi_i, spi_r, octet_0); + this->prf->set_key(this->prf, this->skeyid); + this->prf->allocate_bytes(this->prf, data, &this->skeyid_d); + chunk_clear(&data); + DBG4(DBG_IKE, "SKEYID_d %B", &this->skeyid_d); + + /* SKEYID_a = prf(SKEYID, SKEYID_d | g^xy | CKY-I | CKY-R | 1) */ + data = chunk_cat("ccccc", this->skeyid_d, g_xy, spi_i, spi_r, octet_1); + this->prf->set_key(this->prf, this->skeyid); + this->prf->allocate_bytes(this->prf, data, &this->skeyid_a); + chunk_clear(&data); + DBG4(DBG_IKE, "SKEYID_a %B", &this->skeyid_a); + + /* SKEYID_e = prf(SKEYID, SKEYID_a | g^xy | CKY-I | CKY-R | 2) */ + data = chunk_cat("ccccc", this->skeyid_a, g_xy, spi_i, spi_r, octet_2); + this->prf->set_key(this->prf, this->skeyid); + this->prf->allocate_bytes(this->prf, data, &skeyid_e); + chunk_clear(&data); + DBG4(DBG_IKE, "SKEYID_e %B", &skeyid_e); + + chunk_clear(&g_xy); + + this->aead = create_aead(proposal, this->prf, skeyid_e); + if (!this->aead) + { + return FALSE; + } + if (!this->hasher && !this->public.create_hasher(&this->public, proposal)) + { + return FALSE; + } + + dh->get_my_public_value(dh, &dh_me); + g_xi = this->initiator ? dh_me : dh_other; + g_xr = this->initiator ? dh_other : dh_me; + + /* initial IV = hash(g^xi | g^xr) */ + data = chunk_cata("cc", g_xi, g_xr); + this->hasher->allocate_hash(this->hasher, data, &this->phase1_iv.iv); + if (this->phase1_iv.iv.len > this->aead->get_block_size(this->aead)) + { + this->phase1_iv.iv.len = this->aead->get_block_size(this->aead); + } + chunk_free(&dh_me); + DBG4(DBG_IKE, "initial IV %B", &this->phase1_iv.iv); + + return TRUE; +} + +METHOD(keymat_v1_t, derive_child_keys, bool, + private_keymat_v1_t *this, proposal_t *proposal, diffie_hellman_t *dh, + u_int32_t spi_i, u_int32_t spi_r, chunk_t nonce_i, chunk_t nonce_r, + chunk_t *encr_i, chunk_t *integ_i, chunk_t *encr_r, chunk_t *integ_r) +{ + u_int16_t enc_alg, int_alg, enc_size = 0, int_size = 0; + u_int8_t protocol; + prf_plus_t *prf_plus; + chunk_t seed, secret = chunk_empty; + + if (proposal->get_algorithm(proposal, ENCRYPTION_ALGORITHM, + &enc_alg, &enc_size)) + { + DBG2(DBG_CHD, " using %N for encryption", + encryption_algorithm_names, enc_alg); + + if (!enc_size) + { + enc_size = keymat_get_keylen_encr(enc_alg); + } + if (enc_alg != ENCR_NULL && !enc_size) + { + DBG1(DBG_CHD, "no keylength defined for %N", + encryption_algorithm_names, enc_alg); + return FALSE; + } + /* to bytes */ + enc_size /= 8; + + /* CCM/GCM/CTR/GMAC needs additional bytes */ + switch (enc_alg) + { + case ENCR_AES_CCM_ICV8: + case ENCR_AES_CCM_ICV12: + case ENCR_AES_CCM_ICV16: + case ENCR_CAMELLIA_CCM_ICV8: + case ENCR_CAMELLIA_CCM_ICV12: + case ENCR_CAMELLIA_CCM_ICV16: + enc_size += 3; + break; + case ENCR_AES_GCM_ICV8: + case ENCR_AES_GCM_ICV12: + case ENCR_AES_GCM_ICV16: + case ENCR_AES_CTR: + case ENCR_NULL_AUTH_AES_GMAC: + enc_size += 4; + break; + default: + break; + } + } + + if (proposal->get_algorithm(proposal, INTEGRITY_ALGORITHM, + &int_alg, &int_size)) + { + DBG2(DBG_CHD, " using %N for integrity", + integrity_algorithm_names, int_alg); + + if (!int_size) + { + int_size = keymat_get_keylen_integ(int_alg); + } + if (!int_size) + { + DBG1(DBG_CHD, "no keylength defined for %N", + integrity_algorithm_names, int_alg); + return FALSE; + } + /* to bytes */ + int_size /= 8; + } + + /* KEYMAT = prf+(SKEYID_d, [ g(qm)^xy | ] protocol | SPI | Ni_b | Nr_b) */ + this->prf->set_key(this->prf, this->skeyid_d); + protocol = proposal->get_protocol(proposal); + if (dh) + { + if (dh->get_shared_secret(dh, &secret) != SUCCESS) + { + return FALSE; + } + DBG4(DBG_CHD, "DH secret %B", &secret); + } + + seed = chunk_cata("ccccc", secret, chunk_from_thing(protocol), + chunk_from_thing(spi_r), nonce_i, nonce_r); + DBG4(DBG_CHD, "initiator SA seed %B", &seed); + + prf_plus = prf_plus_create(this->prf, FALSE, seed); + prf_plus->allocate_bytes(prf_plus, enc_size, encr_i); + prf_plus->allocate_bytes(prf_plus, int_size, integ_i); + prf_plus->destroy(prf_plus); + + seed = chunk_cata("ccccc", secret, chunk_from_thing(protocol), + chunk_from_thing(spi_i), nonce_i, nonce_r); + DBG4(DBG_CHD, "responder SA seed %B", &seed); + prf_plus = prf_plus_create(this->prf, FALSE, seed); + prf_plus->allocate_bytes(prf_plus, enc_size, encr_r); + prf_plus->allocate_bytes(prf_plus, int_size, integ_r); + prf_plus->destroy(prf_plus); + + chunk_clear(&secret); + + if (enc_size) + { + DBG4(DBG_CHD, "encryption initiator key %B", encr_i); + DBG4(DBG_CHD, "encryption responder key %B", encr_r); + } + if (int_size) + { + DBG4(DBG_CHD, "integrity initiator key %B", integ_i); + DBG4(DBG_CHD, "integrity responder key %B", integ_r); + } + return TRUE; +} + +METHOD(keymat_v1_t, create_hasher, bool, + private_keymat_v1_t *this, proposal_t *proposal) +{ + u_int16_t alg; + if (!proposal->get_algorithm(proposal, INTEGRITY_ALGORITHM, &alg, NULL) || + (alg = auth_to_hash(alg)) == HASH_UNKNOWN) + { + DBG1(DBG_IKE, "no %N selected", transform_type_names, HASH_ALGORITHM); + return FALSE; + } + this->hasher = lib->crypto->create_hasher(lib->crypto, alg); + if (!this->hasher) + { + DBG1(DBG_IKE, "%N %N not supported!", + transform_type_names, HASH_ALGORITHM, + hash_algorithm_names, alg); + return FALSE; + } + return TRUE; +} + +METHOD(keymat_v1_t, get_hasher, hasher_t*, + private_keymat_v1_t *this) +{ + return this->hasher; +} + +METHOD(keymat_v1_t, get_hash, chunk_t, + private_keymat_v1_t *this, bool initiator, chunk_t dh, chunk_t dh_other, + ike_sa_id_t *ike_sa_id, chunk_t sa_i, chunk_t id) +{ + chunk_t hash, data; + u_int64_t spi, spi_other; + + /* HASH_I = prf(SKEYID, g^xi | g^xr | CKY-I | CKY-R | SAi_b | IDii_b ) + * HASH_R = prf(SKEYID, g^xr | g^xi | CKY-R | CKY-I | SAi_b | IDir_b ) + */ + if (initiator) + { + spi = ike_sa_id->get_initiator_spi(ike_sa_id); + spi_other = ike_sa_id->get_responder_spi(ike_sa_id); + } + else + { + spi_other = ike_sa_id->get_initiator_spi(ike_sa_id); + spi = ike_sa_id->get_responder_spi(ike_sa_id); + } + data = chunk_cat("cccccc", dh, dh_other, + chunk_from_thing(spi), chunk_from_thing(spi_other), + sa_i, id); + + DBG3(DBG_IKE, "HASH_%c data %B", initiator ? 'I' : 'R', &data); + + this->prf->set_key(this->prf, this->skeyid); + this->prf->allocate_bytes(this->prf, data, &hash); + + DBG3(DBG_IKE, "HASH_%c %B", initiator ? 'I' : 'R', &hash); + + free(data.ptr); + return hash; +} + +/** + * Get the nonce value found in the given message. + * Returns FALSE if none is found. + */ +static bool get_nonce(message_t *message, chunk_t *n) +{ + nonce_payload_t *nonce; + nonce = (nonce_payload_t*)message->get_payload(message, NONCE_V1); + if (nonce) + { + *n = nonce->get_nonce(nonce); + return TRUE; + } + return FALSE; +} + +/** + * Generate the message data in order to generate the hashes. + */ +static chunk_t get_message_data(message_t *message, generator_t *generator) +{ + payload_t *payload, *next; + enumerator_t *enumerator; + u_int32_t *lenpos; + + if (message->is_encoded(message)) + { /* inbound, although the message is generated, we cannot access the + * cleartext message data, so generate it anyway */ + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + if (payload->get_type(payload) == HASH_V1) + { + continue; + } + generator->generate_payload(generator, payload); + } + enumerator->destroy(enumerator); + } + else + { + /* outbound, generate the payloads (there is no HASH payload yet) */ + enumerator = message->create_payload_enumerator(message); + if (enumerator->enumerate(enumerator, &payload)) + { + while (enumerator->enumerate(enumerator, &next)) + { + payload->set_next_type(payload, next->get_type(next)); + generator->generate_payload(generator, payload); + payload = next; + } + payload->set_next_type(payload, NO_PAYLOAD); + generator->generate_payload(generator, payload); + } + enumerator->destroy(enumerator); + } + return generator->get_chunk(generator, &lenpos); +} + +/** + * Try to find data about a Quick Mode with the given message ID, + * if none is found, state is generated. + */ +static qm_data_t *lookup_quick_mode(private_keymat_v1_t *this, u_int32_t mid) +{ + enumerator_t *enumerator; + qm_data_t *qm, *found = NULL; + + enumerator = this->qms->create_enumerator(this->qms); + while (enumerator->enumerate(enumerator, &qm)) + { + if (qm->mid == mid) + { /* state gets moved to the front of the list */ + this->qms->remove_at(this->qms, enumerator); + found = qm; + break; + } + } + enumerator->destroy(enumerator); + if (!found) + { + INIT(found, + .mid = mid, + ); + } + this->qms->insert_first(this->qms, found); + /* remove least recently used state if maximum reached */ + if (this->qms->get_count(this->qms) > MAX_QM && + this->qms->remove_last(this->qms, (void**)&qm) == SUCCESS) + { + qm_data_destroy(qm); + } + return found; +} + +METHOD(keymat_v1_t, get_hash_phase2, chunk_t, + private_keymat_v1_t *this, message_t *message) +{ + u_int32_t mid = message->get_message_id(message), mid_n = htonl(mid); + chunk_t data = chunk_empty, hash = chunk_empty; + bool add_message = TRUE; + char *name = "Hash"; + + if (!this->prf) + { /* no keys derived yet */ + return hash; + } + + /* Hashes are simple for most exchanges in Phase 2: + * Hash = prf(SKEYID_a, M-ID | Complete message after HASH payload) + * For Quick Mode there are three hashes: + * Hash(1) = same as above + * Hash(2) = prf(SKEYID_a, M-ID | Ni_b | Message after HASH payload) + * Hash(3) = prf(SKEYID_a, 0 | M-ID | Ni_b | Nr_b) + * So, for Quick Mode we keep track of the nonce values. + */ + switch (message->get_exchange_type(message)) + { + case QUICK_MODE: + { + qm_data_t *qm = lookup_quick_mode(this, mid); + if (!qm->n_i.ptr) + { /* Hash(1) = prf(SKEYID_a, M-ID | Message after HASH payload) */ + name = "Hash(1)"; + if (!get_nonce(message, &qm->n_i)) + { + return hash; + } + data = chunk_from_thing(mid_n); + } + else if (!qm->n_r.ptr) + { /* Hash(2) = prf(SKEYID_a, M-ID | Ni_b | Message after HASH) */ + name = "Hash(2)"; + if (!get_nonce(message, &qm->n_r)) + { + return hash; + } + data = chunk_cata("cc", chunk_from_thing(mid_n), qm->n_i); + } + else + { /* Hash(3) = prf(SKEYID_a, 0 | M-ID | Ni_b | Nr_b) */ + name = "Hash(3)"; + data = chunk_cata("cccc", octet_0, chunk_from_thing(mid_n), + qm->n_i, qm->n_r); + add_message = FALSE; + /* we don't need the state anymore */ + this->qms->remove(this->qms, qm, NULL); + qm_data_destroy(qm); + } + break; + } + case TRANSACTION: + case INFORMATIONAL_V1: + /* Hash = prf(SKEYID_a, M-ID | Message after HASH payload) */ + data = chunk_from_thing(mid_n); + break; + default: + break; + } + if (data.ptr) + { + this->prf->set_key(this->prf, this->skeyid_a); + if (add_message) + { + generator_t *generator = generator_create_no_dbg(); + chunk_t msg = get_message_data(message, generator); + this->prf->allocate_bytes(this->prf, data, NULL); + this->prf->allocate_bytes(this->prf, msg, &hash); + generator->destroy(generator); + } + else + { + this->prf->allocate_bytes(this->prf, data, &hash); + } + DBG3(DBG_IKE, "%s %B", name, &hash); + } + return hash; +} + +/** + * Generate an IV + */ +static void generate_iv(private_keymat_v1_t *this, iv_data_t *iv) +{ + if (iv->mid == 0 || iv->iv.ptr) + { /* use last block of previous encrypted message */ + chunk_free(&iv->iv); + iv->iv = iv->last_block; + iv->last_block = chunk_empty; + } + else + { + /* initial phase 2 IV = hash(last_phase1_block | mid) */ + u_int32_t net = htonl(iv->mid); + chunk_t data = chunk_cata("cc", this->phase1_iv.iv, + chunk_from_thing(net)); + this->hasher->allocate_hash(this->hasher, data, &iv->iv); + if (iv->iv.len > this->aead->get_block_size(this->aead)) + { + iv->iv.len = this->aead->get_block_size(this->aead); + } + } + DBG4(DBG_IKE, "next IV for MID %u %B", iv->mid, &iv->iv); +} + +/** + * Try to find an IV for the given message ID, if not found, generate it. + */ +static iv_data_t *lookup_iv(private_keymat_v1_t *this, u_int32_t mid) +{ + enumerator_t *enumerator; + iv_data_t *iv, *found = NULL; + + if (mid == 0) + { + return &this->phase1_iv; + } + + enumerator = this->ivs->create_enumerator(this->ivs); + while (enumerator->enumerate(enumerator, &iv)) + { + if (iv->mid == mid) + { /* IV gets moved to the front of the list */ + this->ivs->remove_at(this->ivs, enumerator); + found = iv; + break; + } + } + enumerator->destroy(enumerator); + if (!found) + { + INIT(found, + .mid = mid, + ); + generate_iv(this, found); + } + this->ivs->insert_first(this->ivs, found); + /* remove least recently used IV if maximum reached */ + if (this->ivs->get_count(this->ivs) > MAX_IV && + this->ivs->remove_last(this->ivs, (void**)&iv) == SUCCESS) + { + iv_data_destroy(iv); + } + return found; +} + +METHOD(keymat_v1_t, get_iv, chunk_t, + private_keymat_v1_t *this, u_int32_t mid) +{ + return chunk_clone(lookup_iv(this, mid)->iv); +} + +METHOD(keymat_v1_t, update_iv, void, + private_keymat_v1_t *this, u_int32_t mid, chunk_t last_block) +{ + iv_data_t *iv = lookup_iv(this, mid); + if (iv) + { /* update last block */ + chunk_free(&iv->last_block); + iv->last_block = chunk_clone(last_block); + } +} + +METHOD(keymat_v1_t, confirm_iv, void, + private_keymat_v1_t *this, u_int32_t mid) +{ + iv_data_t *iv = lookup_iv(this, mid); + if (iv) + { + generate_iv(this, iv); + } +} + +METHOD(keymat_t, create_dh, diffie_hellman_t*, + private_keymat_v1_t *this, diffie_hellman_group_t group) +{ + return lib->crypto->create_dh(lib->crypto, group); +} + +METHOD(keymat_t, get_aead, aead_t*, + private_keymat_v1_t *this, bool in) +{ + return this->aead; +} + +METHOD(keymat_t, destroy, void, + private_keymat_v1_t *this) +{ + DESTROY_IF(this->prf); + DESTROY_IF(this->aead); + DESTROY_IF(this->hasher); + chunk_clear(&this->skeyid); + chunk_clear(&this->skeyid_d); + chunk_clear(&this->skeyid_a); + chunk_free(&this->phase1_iv.iv); + chunk_free(&this->phase1_iv.last_block); + this->ivs->destroy_function(this->ivs, (void*)iv_data_destroy); + this->qms->destroy_function(this->qms, (void*)qm_data_destroy); + free(this); +} + +/** + * See header + */ +keymat_v1_t *keymat_v1_create(bool initiator) +{ + private_keymat_v1_t *this; + + INIT(this, + .public = { + .keymat = { + .create_dh = _create_dh, + .get_aead = _get_aead, + .destroy = _destroy, + }, + .derive_ike_keys = _derive_ike_keys, + .derive_child_keys = _derive_child_keys, + .create_hasher = _create_hasher, + .get_hasher = _get_hasher, + .get_hash = _get_hash, + .get_hash_phase2 = _get_hash_phase2, + .get_iv = _get_iv, + .update_iv = _update_iv, + .confirm_iv = _confirm_iv, + }, + .ivs = linked_list_create(), + .qms = linked_list_create(), + .initiator = initiator, + .prf_alg = PRF_UNDEFINED, + ); + + return &this->public; +} diff --git a/src/libcharon/sa/ikev1/keymat_v1.h b/src/libcharon/sa/ikev1/keymat_v1.h new file mode 100644 index 000000000..bb1022b5e --- /dev/null +++ b/src/libcharon/sa/ikev1/keymat_v1.h @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2011 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup keymat_v1 keymat_v1 + * @{ @ingroup sa + */ + +#ifndef KEYMAT_V1_H_ +#define KEYMAT_V1_H_ + +#include <sa/keymat.h> +#include <sa/authenticator.h> + +typedef struct keymat_v1_t keymat_v1_t; + +/** + * Derivation and management of sensitive keying material, IKEv1 variant. + */ +struct keymat_v1_t { + + /** + * Implements keymat_t. + */ + keymat_t keymat; + + /** + * Derive keys for the IKE_SA. + * + * These keys are not handed out, but are used by the associated signers, + * crypters and authentication functions. + * + * @param proposal selected algorithms + * @param dh diffie hellman key allocated by create_dh() + * @param dh_other public DH value from other peer + * @param nonce_i initiators nonce value + * @param nonce_r responders nonce value + * @param id IKE_SA identifier + * @param auth authentication method + * @param shared_key PSK in case of AUTH_CLASS_PSK, NULL otherwise + * @return TRUE on success + */ + bool (*derive_ike_keys)(keymat_v1_t *this, proposal_t *proposal, + diffie_hellman_t *dh, chunk_t dh_other, + chunk_t nonce_i, chunk_t nonce_r, ike_sa_id_t *id, + auth_method_t auth, shared_key_t *shared_key); + + /** + * Derive keys for the CHILD_SA. + * + * @param proposal selected algorithms + * @param dh diffie hellman key, NULL if none used + * @param spi_i SPI chosen by initiatior + * @param spi_r SPI chosen by responder + * @param nonce_i quick mode initiator nonce + * @param nonce_r quick mode responder nonce + * @param encr_i allocated initiators encryption key + * @param integ_i allocated initiators integrity key + * @param encr_r allocated responders encryption key + * @param integ_r allocated responders integrity key + */ + bool (*derive_child_keys)(keymat_v1_t *this, proposal_t *proposal, + diffie_hellman_t *dh, u_int32_t spi_i, u_int32_t spi_r, + chunk_t nonce_i, chunk_t nonce_r, + chunk_t *encr_i, chunk_t *integ_i, + chunk_t *encr_r, chunk_t *integ_r); + + /** + * Create the negotiated hasher. + * + * @param proposal selected algorithms + * @return TRUE, if creation was successful + */ + bool (*create_hasher)(keymat_v1_t *this, proposal_t *proposal); + + /** + * Get the negotiated hasher. + * + * @return allocated hasher or NULL + */ + hasher_t *(*get_hasher)(keymat_v1_t *this); + + /** + * Get HASH data for authentication. + * + * @param initiatior TRUE to create HASH_I, FALSE for HASH_R + * @param dh public DH value of peer to create HASH for + * @param dh_other others public DH value + * @param ike_sa_id IKE_SA identifier + * @param sa_i encoded SA payload of initiator + * @param id encoded IDii payload for HASH_I (IDir for HASH_R) + * @return allocated HASH data + */ + chunk_t (*get_hash)(keymat_v1_t *this, bool initiator, + chunk_t dh, chunk_t dh_other, ike_sa_id_t *ike_sa_id, + chunk_t sa_i, chunk_t id); + + /** + * Get HASH data for integrity/authentication in Phase 2 exchanges. + * + * @param message message to generate the HASH data for + * @return allocated HASH data + */ + chunk_t (*get_hash_phase2)(keymat_v1_t *this, message_t *message); + + + /** + * Returns the IV for a message with the given message ID. + * + * @param mid message ID + * @return IV (needs to be freed) + */ + chunk_t (*get_iv)(keymat_v1_t *this, u_int32_t mid); + + /** + * Updates the IV for the next message with the given message ID. + * + * A call of confirm_iv() is required in order to actually make the IV + * available. This is needed for the inbound case where we store the last + * block of the encrypted message but want to update the IV only after + * verification of the decrypted message. + * + * @param mid message ID + * @param last_block last block of encrypted message (gets cloned) + */ + void (*update_iv)(keymat_v1_t *this, u_int32_t mid, chunk_t last_block); + + /** + * Confirms the updated IV for the given message ID. + * + * To actually make the new IV available via get_iv this method has to + * be called after update_iv. + * + * @param mid message ID + */ + void (*confirm_iv)(keymat_v1_t *this, u_int32_t mid); + +}; + +/** + * Create a keymat instance. + * + * @param initiator TRUE if we are the initiator + * @return keymat instance + */ +keymat_v1_t *keymat_v1_create(bool initiator); + +#endif /** KEYMAT_V1_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/task_manager_v1.c b/src/libcharon/sa/ikev1/task_manager_v1.c new file mode 100644 index 000000000..8e4aa7496 --- /dev/null +++ b/src/libcharon/sa/ikev1/task_manager_v1.c @@ -0,0 +1,1115 @@ +/* + * Copyright (C) 2007-2011 Tobias Brunner + * Copyright (C) 2007-2011 Martin Willi + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "task_manager_v1.h" + +#include <math.h> + +#include <daemon.h> +#include <sa/ikev1/tasks/main_mode.h> +#include <sa/ikev1/tasks/quick_mode.h> +#include <sa/ikev1/tasks/xauth.h> +#include <sa/ikev1/tasks/mode_config.h> +#include <sa/ikev1/tasks/informational.h> +#include <sa/ikev1/tasks/isakmp_natd.h> +#include <sa/ikev1/tasks/isakmp_vendor.h> +#include <sa/ikev1/tasks/isakmp_cert_pre.h> +#include <sa/ikev1/tasks/isakmp_cert_post.h> +#include <processing/jobs/retransmit_job.h> +#include <processing/jobs/delete_ike_sa_job.h> + +/** + * Number of old messages hashes we keep for retransmission. + * + * In Main Mode, we must ignore messages from a previous message pair if + * we already continued to the next. Otherwise a late retransmission + * could be considered as a reply to the newer request. + */ +#define MAX_OLD_HASHES 2 + +typedef struct exchange_t exchange_t; + +/** + * An exchange in the air, used do detect and handle retransmission + */ +struct exchange_t { + + /** + * Message ID used for this transaction + */ + u_int32_t mid; + + /** + * generated packet for retransmission + */ + packet_t *packet; +}; + +typedef struct private_task_manager_t private_task_manager_t; + +/** + * private data of the task manager + */ +struct private_task_manager_t { + + /** + * public functions + */ + task_manager_v1_t public; + + /** + * associated IKE_SA we are serving + */ + ike_sa_t *ike_sa; + + /** + * RNG to create message IDs + */ + rng_t *rng; + + /** + * Exchange we are currently handling as responder + */ + struct { + /** + * Hash of a previously received message + */ + u_int32_t hash; + + /** + * packet for retransmission + */ + packet_t *packet; + + } responding; + + /** + * Exchange we are currently handling as initiator + */ + struct { + /** + * Message ID of the exchange + */ + u_int32_t mid; + + /** + * Hashes of old responses we can ignore + */ + u_int32_t old_hashes[MAX_OLD_HASHES]; + + /** + * Position in old hash array + */ + int old_hash_pos; + + /** + * Sequence number of the last sent message + */ + u_int32_t seqnr; + + /** + * how many times we have retransmitted so far + */ + u_int retransmitted; + + /** + * packet for retransmission + */ + packet_t *packet; + + /** + * type of the initated exchange + */ + exchange_type_t type; + + } initiating; + + /** + * List of queued tasks not yet in action + */ + linked_list_t *queued_tasks; + + /** + * List of active tasks, initiated by ourselve + */ + linked_list_t *active_tasks; + + /** + * List of tasks initiated by peer + */ + linked_list_t *passive_tasks; + + /** + * Queued messages not yet ready to process + */ + message_t *queued; + + /** + * Number of times we retransmit messages before giving up + */ + u_int retransmit_tries; + + /** + * Retransmission timeout + */ + double retransmit_timeout; + + /** + * Base to calculate retransmission timeout + */ + double retransmit_base; +}; + +/** + * Flush a single task queue + */ +static void flush_queue(private_task_manager_t *this, linked_list_t *list) +{ + task_t *task; + + if (this->queued) + { + this->queued->destroy(this->queued); + this->queued = NULL; + } + while (list->remove_last(list, (void**)&task) == SUCCESS) + { + task->destroy(task); + } +} + +/** + * flush all tasks in the task manager + */ +static void flush(private_task_manager_t *this) +{ + flush_queue(this, this->queued_tasks); + flush_queue(this, this->passive_tasks); + flush_queue(this, this->active_tasks); +} + +/** + * move a task of a specific type from the queue to the active list + */ +static bool activate_task(private_task_manager_t *this, task_type_t type) +{ + enumerator_t *enumerator; + task_t *task; + bool found = FALSE; + + enumerator = this->queued_tasks->create_enumerator(this->queued_tasks); + while (enumerator->enumerate(enumerator, (void**)&task)) + { + if (task->get_type(task) == type) + { + DBG2(DBG_IKE, " activating %N task", task_type_names, type); + this->queued_tasks->remove_at(this->queued_tasks, enumerator); + this->active_tasks->insert_last(this->active_tasks, task); + found = TRUE; + break; + } + } + enumerator->destroy(enumerator); + return found; +} + +METHOD(task_manager_t, retransmit, status_t, + private_task_manager_t *this, u_int32_t message_seqnr) +{ + /* this.initiating packet used as marker for received response */ + if (message_seqnr == this->initiating.seqnr && this->initiating.packet ) + { + u_int32_t timeout; + packet_t *packet; + job_t *job; + + if (this->initiating.retransmitted <= this->retransmit_tries) + { + timeout = (u_int32_t)(this->retransmit_timeout * 1000.0 * + pow(this->retransmit_base, this->initiating.retransmitted)); + } + else + { + DBG1(DBG_IKE, "giving up after %d retransmits", + this->initiating.retransmitted - 1); + if (this->ike_sa->get_state(this->ike_sa) != IKE_CONNECTING) + { + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + } + return DESTROY_ME; + } + + if (this->initiating.retransmitted) + { + DBG1(DBG_IKE, "retransmit %d of request with message ID %u seqnr (%d)", + this->initiating.retransmitted, this->initiating.mid, message_seqnr); + } + packet = this->initiating.packet->clone(this->initiating.packet); + charon->sender->send(charon->sender, packet); + + this->initiating.retransmitted++; + job = (job_t*)retransmit_job_create(this->initiating.seqnr, + this->ike_sa->get_id(this->ike_sa)); + lib->scheduler->schedule_job_ms(lib->scheduler, job, timeout); + } + return SUCCESS; +} + +METHOD(task_manager_t, initiate, status_t, + private_task_manager_t *this) +{ + enumerator_t *enumerator; + task_t *task; + message_t *message; + host_t *me, *other; + status_t status; + exchange_type_t exchange = EXCHANGE_TYPE_UNDEFINED; + bool new_mid = FALSE, expect_response = FALSE, flushed = FALSE; + + if (!this->rng) + { + DBG1(DBG_IKE, "no RNG supported"); + return FAILED; + } + + if (this->initiating.type != EXCHANGE_TYPE_UNDEFINED) + { + DBG2(DBG_IKE, "delaying task initiation, %N exchange in progress", + exchange_type_names, this->initiating.type); + /* do not initiate if we already have a message in the air */ + return SUCCESS; + } + + if (this->active_tasks->get_count(this->active_tasks) == 0) + { + DBG2(DBG_IKE, "activating new tasks"); + switch (this->ike_sa->get_state(this->ike_sa)) + { + case IKE_CREATED: + activate_task(this, TASK_ISAKMP_VENDOR); + activate_task(this, TASK_ISAKMP_CERT_PRE); + if (activate_task(this, TASK_MAIN_MODE)) + { + exchange = ID_PROT; + activate_task(this, TASK_ISAKMP_CERT_POST); + activate_task(this, TASK_ISAKMP_NATD); + } + break; + case IKE_CONNECTING: + if (activate_task(this, TASK_ISAKMP_DELETE)) + { + exchange = INFORMATIONAL_V1; + new_mid = TRUE; + break; + } + if (activate_task(this, TASK_XAUTH)) + { + exchange = TRANSACTION; + new_mid = TRUE; + break; + } + if (activate_task(this, TASK_INFORMATIONAL)) + { + exchange = INFORMATIONAL_V1; + new_mid = TRUE; + break; + } + break; + case IKE_ESTABLISHED: + if (activate_task(this, TASK_MODE_CONFIG)) + { + exchange = TRANSACTION; + new_mid = TRUE; + break; + } + if (activate_task(this, TASK_QUICK_MODE)) + { + exchange = QUICK_MODE; + new_mid = TRUE; + break; + } + if (activate_task(this, TASK_INFORMATIONAL)) + { + exchange = INFORMATIONAL_V1; + new_mid = TRUE; + break; + } + if (activate_task(this, TASK_ISAKMP_DELETE)) + { + exchange = INFORMATIONAL_V1; + new_mid = TRUE; + break; + } + if (activate_task(this, TASK_QUICK_DELETE)) + { + exchange = INFORMATIONAL_V1; + new_mid = TRUE; + break; + } + break; + default: + break; + } + } + else + { + DBG2(DBG_IKE, "reinitiating already active tasks"); + enumerator = this->active_tasks->create_enumerator(this->active_tasks); + while (enumerator->enumerate(enumerator, (void**)&task)) + { + DBG2(DBG_IKE, " %N task", task_type_names, task->get_type(task)); + switch (task->get_type(task)) + { + case TASK_MAIN_MODE: + exchange = ID_PROT; + break; + case TASK_QUICK_MODE: + exchange = QUICK_MODE; + break; + case TASK_XAUTH: + exchange = TRANSACTION; + new_mid = TRUE; + break; + default: + continue; + } + break; + } + enumerator->destroy(enumerator); + } + + if (exchange == EXCHANGE_TYPE_UNDEFINED) + { + DBG2(DBG_IKE, "nothing to initiate"); + /* nothing to do yet... */ + return SUCCESS; + } + + me = this->ike_sa->get_my_host(this->ike_sa); + other = this->ike_sa->get_other_host(this->ike_sa); + + message = message_create(IKEV1_MAJOR_VERSION, IKEV1_MINOR_VERSION); + if (new_mid) + { + this->rng->get_bytes(this->rng, sizeof(this->initiating.mid), + (void*)&this->initiating.mid); + } + message->set_message_id(message, this->initiating.mid); + message->set_source(message, me->clone(me)); + message->set_destination(message, other->clone(other)); + message->set_exchange_type(message, exchange); + this->initiating.type = exchange; + this->initiating.retransmitted = 0; + + enumerator = this->active_tasks->create_enumerator(this->active_tasks); + while (enumerator->enumerate(enumerator, (void*)&task)) + { + switch (task->build(task, message)) + { + case SUCCESS: + /* task completed, remove it */ + this->active_tasks->remove_at(this->active_tasks, enumerator); + task->destroy(task); + continue; + case NEED_MORE: + expect_response = TRUE; + /* processed, but task needs another exchange */ + continue; + case ALREADY_DONE: + flush_queue(this, this->active_tasks); + flushed = TRUE; + break; + case FAILED: + default: + if (this->ike_sa->get_state(this->ike_sa) != IKE_CONNECTING) + { + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + } + /* FALL */ + case DESTROY_ME: + /* critical failure, destroy IKE_SA */ + enumerator->destroy(enumerator); + message->destroy(message); + flush(this); + return DESTROY_ME; + } + break; + } + enumerator->destroy(enumerator); + + if (this->active_tasks->get_count(this->active_tasks) == 0) + { /* tasks completed, no exchange active anymore */ + this->initiating.type = EXCHANGE_TYPE_UNDEFINED; + } + if (flushed) + { + message->destroy(message); + return initiate(this); + } + this->initiating.seqnr++; + + status = this->ike_sa->generate_message(this->ike_sa, message, + &this->initiating.packet); + if (status != SUCCESS) + { + /* message generation failed. There is nothing more to do than to + * close the SA */ + message->destroy(message); + flush(this); + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + return DESTROY_ME; + } + message->destroy(message); + + if (expect_response) + { + return retransmit(this, this->initiating.seqnr); + } + charon->sender->send(charon->sender, + this->initiating.packet->clone(this->initiating.packet)); + this->initiating.packet->destroy(this->initiating.packet); + this->initiating.packet = NULL; + + if (exchange == INFORMATIONAL_V1) + { + switch (this->ike_sa->get_state(this->ike_sa)) + { + case IKE_CONNECTING: + /* close after sending an INFORMATIONAL when unestablished */ + return FAILED; + case IKE_DELETING: + /* close after sending a DELETE */ + return DESTROY_ME; + default: + break; + } + } + return SUCCESS; +} + +/** + * handle exchange collisions + */ +static bool handle_collisions(private_task_manager_t *this, task_t *task) +{ + return FALSE; +} + +/** + * build a response depending on the "passive" task list + */ +static status_t build_response(private_task_manager_t *this, message_t *request) +{ + enumerator_t *enumerator; + task_t *task; + message_t *message; + host_t *me, *other; + bool delete = FALSE, flushed = FALSE; + status_t status; + + me = request->get_destination(request); + other = request->get_source(request); + + message = message_create(IKEV1_MAJOR_VERSION, IKEV1_MINOR_VERSION); + message->set_exchange_type(message, request->get_exchange_type(request)); + /* send response along the path the request came in */ + message->set_source(message, me->clone(me)); + message->set_destination(message, other->clone(other)); + message->set_message_id(message, request->get_message_id(request)); + message->set_request(message, FALSE); + + enumerator = this->passive_tasks->create_enumerator(this->passive_tasks); + while (enumerator->enumerate(enumerator, (void*)&task)) + { + switch (task->build(task, message)) + { + case SUCCESS: + /* task completed, remove it */ + this->passive_tasks->remove_at(this->passive_tasks, enumerator); + if (!handle_collisions(this, task)) + { + task->destroy(task); + } + continue; + case NEED_MORE: + /* processed, but task needs another exchange */ + if (handle_collisions(this, task)) + { + this->passive_tasks->remove_at(this->passive_tasks, + enumerator); + } + continue; + case ALREADY_DONE: + flush_queue(this, this->passive_tasks); + flushed = TRUE; + break; + case FAILED: + default: + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + /* FALL */ + case DESTROY_ME: + /* destroy IKE_SA, but SEND response first */ + delete = TRUE; + break; + } + break; + } + enumerator->destroy(enumerator); + + DESTROY_IF(this->responding.packet); + this->responding.packet = NULL; + if (flushed) + { + message->destroy(message); + return initiate(this); + } + status = this->ike_sa->generate_message(this->ike_sa, message, + &this->responding.packet); + message->destroy(message); + if (status != SUCCESS) + { + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + return DESTROY_ME; + } + + charon->sender->send(charon->sender, + this->responding.packet->clone(this->responding.packet)); + if (delete) + { + return DESTROY_ME; + } + return SUCCESS; +} + +/** + * Send a notify in a separate INFORMATIONAL exchange back to the sender. + * The notify protocol_id is set to ISAKMP + */ +static void send_notify(private_task_manager_t *this, message_t *request, + notify_type_t type) +{ + message_t *response; + packet_t *packet; + host_t *me, *other; + u_int32_t mid; + + if (request && request->get_exchange_type(request) == INFORMATIONAL_V1) + { /* don't respond to INFORMATIONAL requests to avoid a notify war */ + DBG1(DBG_IKE, "ignore malformed INFORMATIONAL request"); + return; + } + + response = message_create(IKEV1_MAJOR_VERSION, IKEV1_MINOR_VERSION); + response->set_exchange_type(response, INFORMATIONAL_V1); + response->set_request(response, TRUE); + this->rng->get_bytes(this->rng, sizeof(mid), (void*)&mid); + response->set_message_id(response, mid); + response->add_payload(response, (payload_t*) + notify_payload_create_from_protocol_and_type(NOTIFY_V1, + PROTO_IKE, type)); + + me = this->ike_sa->get_my_host(this->ike_sa); + if (me->is_anyaddr(me)) + { + me = request->get_destination(request); + this->ike_sa->set_my_host(this->ike_sa, me->clone(me)); + } + other = this->ike_sa->get_other_host(this->ike_sa); + if (other->is_anyaddr(other)) + { + other = request->get_source(request); + this->ike_sa->set_other_host(this->ike_sa, other->clone(other)); + } + response->set_source(response, me->clone(me)); + response->set_destination(response, other->clone(other)); + if (this->ike_sa->generate_message(this->ike_sa, response, + &packet) == SUCCESS) + { + charon->sender->send(charon->sender, packet); + } + response->destroy(response); +} + +/** + * handle an incoming request message + */ +static status_t process_request(private_task_manager_t *this, + message_t *message) +{ + enumerator_t *enumerator; + task_t *task = NULL; + bool send_response = FALSE; + + if (this->passive_tasks->get_count(this->passive_tasks) == 0) + { /* create tasks depending on request type, if not already some queued */ + switch (message->get_exchange_type(message)) + { + case ID_PROT: + task = (task_t *)isakmp_vendor_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t*)isakmp_cert_pre_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t *)main_mode_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t*)isakmp_cert_post_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t *)isakmp_natd_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + break; + case AGGRESSIVE: + /* TODO-IKEv1: agressive mode */ + return FAILED; + case QUICK_MODE: + if (this->ike_sa->get_state(this->ike_sa) != IKE_ESTABLISHED) + { + DBG1(DBG_IKE, "received quick mode request for " + "unestablished IKE_SA, ignored"); + return FAILED; + } + task = (task_t *)quick_mode_create(this->ike_sa, NULL, + NULL, NULL); + this->passive_tasks->insert_last(this->passive_tasks, task); + break; + case INFORMATIONAL_V1: + task = (task_t *)informational_create(this->ike_sa, NULL); + this->passive_tasks->insert_last(this->passive_tasks, task); + break; + case TRANSACTION: + if (this->ike_sa->get_state(this->ike_sa) == IKE_ESTABLISHED) + { + task = (task_t *)mode_config_create(this->ike_sa, FALSE); + } + else + { + task = (task_t *)xauth_create(this->ike_sa, FALSE); + } + this->passive_tasks->insert_last(this->passive_tasks, task); + break; + default: + return FAILED; + } + } + /* let the tasks process the message */ + enumerator = this->passive_tasks->create_enumerator(this->passive_tasks); + while (enumerator->enumerate(enumerator, (void*)&task)) + { + switch (task->process(task, message)) + { + case SUCCESS: + /* task completed, remove it */ + this->passive_tasks->remove_at(this->passive_tasks, enumerator); + task->destroy(task); + continue; + case NEED_MORE: + /* processed, but task needs at least another call to build() */ + send_response = TRUE; + continue; + case ALREADY_DONE: + send_response = FALSE; + flush_queue(this, this->passive_tasks); + break; + case FAILED: + default: + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + /* FALL */ + case DESTROY_ME: + /* critical failure, destroy IKE_SA */ + this->passive_tasks->remove_at(this->passive_tasks, enumerator); + enumerator->destroy(enumerator); + task->destroy(task); + return DESTROY_ME; + } + break; + } + enumerator->destroy(enumerator); + + if (send_response) + { + if (build_response(this, message) != SUCCESS) + { + return DESTROY_ME; + } + } + else + { /* We don't send a response, so don't retransmit one if we get + * the same message again. */ + DESTROY_IF(this->responding.packet); + this->responding.packet = NULL; + } + if (this->passive_tasks->get_count(this->passive_tasks) == 0 && + this->queued_tasks->get_count(this->queued_tasks) > 0) + { + /* passive tasks completed, check if an active task has been queued, + * such as XAUTH or modeconfig push */ + return initiate(this); + } + return SUCCESS; +} + +/** + * handle an incoming response message + */ +static status_t process_response(private_task_manager_t *this, + message_t *message) +{ + enumerator_t *enumerator; + status_t status; + task_t *task; + + if (message->get_exchange_type(message) != this->initiating.type) + { + DBG1(DBG_IKE, "received %N response, but expected %N", + exchange_type_names, message->get_exchange_type(message), + exchange_type_names, this->initiating.type); + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + return DESTROY_ME; + } + + enumerator = this->active_tasks->create_enumerator(this->active_tasks); + while (enumerator->enumerate(enumerator, (void*)&task)) + { + switch (task->process(task, message)) + { + case SUCCESS: + /* task completed, remove it */ + this->active_tasks->remove_at(this->active_tasks, enumerator); + task->destroy(task); + continue; + case NEED_MORE: + /* processed, but task needs another exchange */ + continue; + case ALREADY_DONE: + flush_queue(this, this->active_tasks); + break; + case FAILED: + default: + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + /* FALL */ + case DESTROY_ME: + /* critical failure, destroy IKE_SA */ + this->active_tasks->remove_at(this->active_tasks, enumerator); + enumerator->destroy(enumerator); + task->destroy(task); + return DESTROY_ME; + } + break; + } + enumerator->destroy(enumerator); + + this->initiating.type = EXCHANGE_TYPE_UNDEFINED; + this->initiating.packet->destroy(this->initiating.packet); + this->initiating.packet = NULL; + + if (this->queued && this->active_tasks->get_count(this->active_tasks) == 0) + { + status = this->public.task_manager.process_message( + &this->public.task_manager, this->queued); + this->queued->destroy(this->queued); + this->queued = NULL; + if (status == DESTROY_ME) + { + return status; + } + } + + return initiate(this); +} + +/** + * Parse the given message and verify that it is valid. + */ +static status_t parse_message(private_task_manager_t *this, message_t *msg) +{ + status_t status; + + status = msg->parse_body(msg, this->ike_sa->get_keymat(this->ike_sa)); + + if (status != SUCCESS) + { + switch (status) + { + case NOT_SUPPORTED: + DBG1(DBG_IKE, "unsupported exchange type"); + send_notify(this, msg, INVALID_EXCHANGE_TYPE); + break; + case PARSE_ERROR: + DBG1(DBG_IKE, "message parsing failed"); + send_notify(this, msg, PAYLOAD_MALFORMED); + break; + case VERIFY_ERROR: + DBG1(DBG_IKE, "message verification failed"); + send_notify(this, msg, PAYLOAD_MALFORMED); + break; + case FAILED: + DBG1(DBG_IKE, "integrity check failed"); + send_notify(this, msg, INVALID_HASH_INFORMATION); + break; + case INVALID_STATE: + DBG1(DBG_IKE, "found encrypted message, but no keys available"); + send_notify(this, msg, PAYLOAD_MALFORMED); + default: + break; + } + DBG1(DBG_IKE, "%N %s with message ID %u processing failed", + exchange_type_names, msg->get_exchange_type(msg), + msg->get_request(msg) ? "request" : "response", + msg->get_message_id(msg)); + + if (this->ike_sa->get_state(this->ike_sa) == IKE_CREATED) + { /* invalid initiation attempt, close SA */ + return DESTROY_ME; + } + } + return status; +} + +METHOD(task_manager_t, process_message, status_t, + private_task_manager_t *this, message_t *msg) +{ + u_int32_t hash, mid, i; + host_t *me, *other; + status_t status; + + /* TODO-IKEv1: update hosts more selectively */ + me = msg->get_destination(msg); + other = msg->get_source(msg); + mid = msg->get_message_id(msg); + hash = chunk_hash(msg->get_packet_data(msg)); + for (i = 0; i < MAX_OLD_HASHES; i++) + { + if (this->initiating.old_hashes[i] == hash) + { + DBG1(DBG_IKE, "received retransmit of response with ID %u, " + "but next request already sent", mid); + return SUCCESS; + } + } + + if ((mid && mid == this->initiating.mid) || + (this->initiating.mid == 0 && + msg->get_exchange_type(msg) == this->initiating.type && + this->active_tasks->get_count(this->active_tasks))) + { + msg->set_request(msg, FALSE); + status = parse_message(this, msg); + if (status != SUCCESS) + { + return status; + } + this->ike_sa->set_statistic(this->ike_sa, STAT_INBOUND, + time_monotonic(NULL)); + this->ike_sa->update_hosts(this->ike_sa, me, other, TRUE); + charon->bus->message(charon->bus, msg, FALSE); + if (process_response(this, msg) != SUCCESS) + { + flush(this); + return DESTROY_ME; + } + this->initiating.old_hashes[(this->initiating.old_hash_pos++) % + MAX_OLD_HASHES] = hash; + } + else + { + if (hash == this->responding.hash) + { + if (this->responding.packet) + { + DBG1(DBG_IKE, "received retransmit of request with ID %u, " + "retransmitting response", mid); + charon->sender->send(charon->sender, + this->responding.packet->clone(this->responding.packet)); + } + else + { + DBG1(DBG_IKE, "received retransmit of request with ID %u, " + "but no response to retransmit", mid); + } + return SUCCESS; + } + if (msg->get_exchange_type(msg) == TRANSACTION && + this->active_tasks->get_count(this->active_tasks)) + { /* main mode not yet complete, queue XAuth/Mode config tasks */ + if (this->queued) + { + DBG1(DBG_IKE, "ignoring additional %N request, queue full", + exchange_type_names, TRANSACTION); + return SUCCESS; + } + this->queued = message_create_from_packet(msg->get_packet(msg)); + if (this->queued->parse_header(this->queued) != SUCCESS) + { + this->queued->destroy(this->queued); + this->queued = NULL; + return FAILED; + } + DBG1(DBG_IKE, "queueing %N request as tasks still active", + exchange_type_names, TRANSACTION); + return SUCCESS; + } + + msg->set_request(msg, TRUE); + status = parse_message(this, msg); + if (status != SUCCESS) + { + return status; + } + /* if this IKE_SA is virgin, we check for a config */ + if (this->ike_sa->get_ike_cfg(this->ike_sa) == NULL) + { + ike_sa_id_t *ike_sa_id; + ike_cfg_t *ike_cfg; + job_t *job; + ike_cfg = charon->backends->get_ike_cfg(charon->backends, me, other); + if (ike_cfg == NULL) + { + /* no config found for these hosts, destroy */ + DBG1(DBG_IKE, "no IKE config found for %H...%H, sending %N", + me, other, notify_type_names, NO_PROPOSAL_CHOSEN); + send_notify(this, msg, NO_PROPOSAL_CHOSEN); + return DESTROY_ME; + } + this->ike_sa->set_ike_cfg(this->ike_sa, ike_cfg); + ike_cfg->destroy(ike_cfg); + /* add a timeout if peer does not establish it completely */ + ike_sa_id = this->ike_sa->get_id(this->ike_sa); + job = (job_t*)delete_ike_sa_job_create(ike_sa_id, FALSE); + lib->scheduler->schedule_job(lib->scheduler, job, + lib->settings->get_int(lib->settings, + "charon.half_open_timeout", HALF_OPEN_IKE_SA_TIMEOUT)); + } + this->ike_sa->set_statistic(this->ike_sa, STAT_INBOUND, + time_monotonic(NULL)); + this->ike_sa->update_hosts(this->ike_sa, me, other, TRUE); + charon->bus->message(charon->bus, msg, TRUE); + if (process_request(this, msg) != SUCCESS) + { + flush(this); + return DESTROY_ME; + } + this->responding.hash = hash; + } + return SUCCESS; +} + +METHOD(task_manager_t, queue_task, void, + private_task_manager_t *this, task_t *task) +{ + DBG2(DBG_IKE, "queueing %N task", task_type_names, task->get_type(task)); + this->queued_tasks->insert_last(this->queued_tasks, task); +} + +METHOD(task_manager_t, adopt_tasks, void, + private_task_manager_t *this, task_manager_t *other_public) +{ + private_task_manager_t *other = (private_task_manager_t*)other_public; + task_t *task; + + /* move queued tasks from other to this */ + while (other->queued_tasks->remove_last(other->queued_tasks, + (void**)&task) == SUCCESS) + { + DBG2(DBG_IKE, "migrating %N task", task_type_names, task->get_type(task)); + task->migrate(task, this->ike_sa); + this->queued_tasks->insert_first(this->queued_tasks, task); + } +} + +METHOD(task_manager_t, busy, bool, + private_task_manager_t *this) +{ + return (this->active_tasks->get_count(this->active_tasks) > 0); +} + +METHOD(task_manager_t, incr_mid, void, + private_task_manager_t *this, bool initiate) +{ +} + +METHOD(task_manager_t, reset, void, + private_task_manager_t *this, u_int32_t initiate, u_int32_t respond) +{ +} + +METHOD(task_manager_t, create_task_enumerator, enumerator_t*, + private_task_manager_t *this, task_queue_t queue) +{ + switch (queue) + { + case TASK_QUEUE_ACTIVE: + return this->active_tasks->create_enumerator(this->active_tasks); + case TASK_QUEUE_PASSIVE: + return this->passive_tasks->create_enumerator(this->passive_tasks); + case TASK_QUEUE_QUEUED: + return this->queued_tasks->create_enumerator(this->queued_tasks); + default: + return enumerator_create_empty(); + } +} + +METHOD(task_manager_t, destroy, void, + private_task_manager_t *this) +{ + flush(this); + + this->active_tasks->destroy(this->active_tasks); + this->queued_tasks->destroy(this->queued_tasks); + this->passive_tasks->destroy(this->passive_tasks); + + DESTROY_IF(this->queued); + DESTROY_IF(this->responding.packet); + DESTROY_IF(this->initiating.packet); + DESTROY_IF(this->rng); + free(this); +} + +/* + * see header file + */ +task_manager_v1_t *task_manager_v1_create(ike_sa_t *ike_sa) +{ + private_task_manager_t *this; + + INIT(this, + .public = { + .task_manager = { + .process_message = _process_message, + .queue_task = _queue_task, + .initiate = _initiate, + .retransmit = _retransmit, + .incr_mid = _incr_mid, + .reset = _reset, + .adopt_tasks = _adopt_tasks, + .busy = _busy, + .create_task_enumerator = _create_task_enumerator, + .destroy = _destroy, + }, + }, + .ike_sa = ike_sa, + .initiating.type = EXCHANGE_TYPE_UNDEFINED, + .rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK), + .queued_tasks = linked_list_create(), + .active_tasks = linked_list_create(), + .passive_tasks = linked_list_create(), + .retransmit_tries = lib->settings->get_int(lib->settings, + "charon.retransmit_tries", RETRANSMIT_TRIES), + .retransmit_timeout = lib->settings->get_double(lib->settings, + "charon.retransmit_timeout", RETRANSMIT_TIMEOUT), + .retransmit_base = lib->settings->get_double(lib->settings, + "charon.retransmit_base", RETRANSMIT_BASE), + ); + + return &this->public; +} diff --git a/src/libcharon/sa/ikev1/task_manager_v1.h b/src/libcharon/sa/ikev1/task_manager_v1.h new file mode 100644 index 000000000..99cd35e32 --- /dev/null +++ b/src/libcharon/sa/ikev1/task_manager_v1.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup task_manager_v1 task_manager_v1 + * @{ @ingroup sa + */ + +#ifndef TASK_MANAGER_V1_H_ +#define TASK_MANAGER_V1_H_ + +typedef struct task_manager_v1_t task_manager_v1_t; + +#include <sa/task_manager.h> + +/** + * Task manager, IKEv1 variant. + */ +struct task_manager_v1_t { + + /** + * Implements task_manager_t. + */ + task_manager_t task_manager; +}; + +/** + * Create an instance of the task manager. + * + * @param ike_sa IKE_SA to manage. + */ +task_manager_v1_t *task_manager_v1_create(ike_sa_t *ike_sa); + +#endif /** TASK_MANAGER_V1_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/tasks/informational.c b/src/libcharon/sa/ikev1/tasks/informational.c new file mode 100644 index 000000000..9de5c2e71 --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/informational.c @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "informational.h" + +#include <daemon.h> +#include <sa/ikev1/tasks/isakmp_delete.h> +#include <sa/ikev1/tasks/quick_delete.h> +#include <encoding/payloads/delete_payload.h> + +typedef struct private_informational_t private_informational_t; + +/** + * Private members of a informational_t task. + */ +struct private_informational_t { + + /** + * Public methods and task_t interface. + */ + informational_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; + + /** + * Notify payload to send + */ + notify_payload_t *notify; + + /** + * Delete subtask + */ + task_t *del; +}; + +METHOD(task_t, build_i, status_t, + private_informational_t *this, message_t *message) +{ + message->add_payload(message, &this->notify->payload_interface); + this->notify = NULL; + return SUCCESS; +} + +METHOD(task_t, process_r, status_t, + private_informational_t *this, message_t *message) +{ + enumerator_t *enumerator; + delete_payload_t *delete; + notify_payload_t *notify; + notify_type_t type; + payload_t *payload; + status_t status = SUCCESS; + + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + switch (payload->get_type(payload)) + { + case NOTIFY_V1: + notify = (notify_payload_t*)payload; + type = notify->get_notify_type(notify); + + if (type == INITIAL_CONTACT_IKEV1) + { + this->ike_sa->set_condition(this->ike_sa, + COND_INIT_CONTACT_SEEN, TRUE); + } + else if (type < 16384) + { + DBG1(DBG_IKE, "received %N error notify", + notify_type_names, notify->get_notify_type(notify)); + if (this->ike_sa->get_state(this->ike_sa) == IKE_CONNECTING) + { /* only critical during main mode */ + status = FAILED; + } + break; + } + else + { + DBG1(DBG_IKE, "received %N notify", + notify_type_names, notify->get_notify_type(notify)); + } + continue; + case DELETE_V1: + if (!this->del) + { + delete = (delete_payload_t*)payload; + if (delete->get_protocol_id(delete) == PROTO_IKE) + { + this->del = (task_t*)isakmp_delete_create(this->ike_sa, + FALSE); + } + else + { + this->del = (task_t*)quick_delete_create(this->ike_sa, + PROTO_NONE, 0, FALSE); + } + } + break; + default: + continue; + } + break; + } + enumerator->destroy(enumerator); + + if (this->del && status == SUCCESS) + { + return this->del->process(this->del, message); + } + return status; +} + +METHOD(task_t, build_r, status_t, + private_informational_t *this, message_t *message) +{ + if (this->del) + { + return this->del->build(this->del, message); + } + return FAILED; +} + +METHOD(task_t, process_i, status_t, + private_informational_t *this, message_t *message) +{ + return FAILED; +} + +METHOD(task_t, get_type, task_type_t, + private_informational_t *this) +{ + return TASK_INFORMATIONAL; +} + +METHOD(task_t, migrate, void, + private_informational_t *this, ike_sa_t *ike_sa) +{ + this->ike_sa = ike_sa; +} + +METHOD(task_t, destroy, void, + private_informational_t *this) +{ + DESTROY_IF(this->notify); + DESTROY_IF(this->del); + free(this); +} + +/* + * Described in header. + */ +informational_t *informational_create(ike_sa_t *ike_sa, notify_payload_t *notify) +{ + private_informational_t *this; + + INIT(this, + .public = { + .task = { + .get_type = _get_type, + .migrate = _migrate, + .destroy = _destroy, + }, + }, + .ike_sa = ike_sa, + .notify = notify, + ); + + if (notify) + { + this->public.task.build = _build_i; + this->public.task.process = _process_i; + } + else + { + this->public.task.build = _build_r; + this->public.task.process = _process_r; + } + + return &this->public; +} diff --git a/src/libcharon/sa/ikev1/tasks/informational.h b/src/libcharon/sa/ikev1/tasks/informational.h new file mode 100644 index 000000000..f1543dc58 --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/informational.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup informational informational + * @{ @ingroup tasks + */ + +#ifndef INFORMATIONAL_H_ +#define informational_H_ + +typedef struct informational_t informational_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/task.h> +#include <encoding/payloads/notify_payload.h> + +/** + * IKEv1 informational exchange, negotiates errors. + */ +struct informational_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * Create a new informational task. + * + * @param ike_sa IKE_SA this task works for + * @param notify notify to send as initiator, NULL if responder + * @return task to handle by the task_manager + */ +informational_t *informational_create(ike_sa_t *ike_sa, notify_payload_t *notify); + +#endif /** INFORMATIONAL_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/tasks/isakmp_cert_post.c b/src/libcharon/sa/ikev1/tasks/isakmp_cert_post.c new file mode 100644 index 000000000..30e1c6d84 --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/isakmp_cert_post.c @@ -0,0 +1,348 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "isakmp_cert_post.h" + +#include <daemon.h> +#include <sa/ike_sa.h> +#include <encoding/payloads/cert_payload.h> +#include <encoding/payloads/certreq_payload.h> +#include <encoding/payloads/auth_payload.h> +#include <encoding/payloads/sa_payload.h> +#include <credentials/certificates/x509.h> + + +typedef struct private_isakmp_cert_post_t private_isakmp_cert_post_t; + +/** + * Private members of a isakmp_cert_post_t task. + */ +struct private_isakmp_cert_post_t { + + /** + * Public methods and task_t interface. + */ + isakmp_cert_post_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; + + /** + * Are we the initiator? + */ + bool initiator; + + /** + * States of ike cert pre + */ + enum { + CR_SA, + CR_KE, + CR_AUTH, + } state; +}; + +/** + * Check if we actually use certificates for authentication + */ +static bool use_certs(private_isakmp_cert_post_t *this, message_t *message) +{ + enumerator_t *enumerator; + payload_t *payload; + bool use = FALSE; + + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + if (payload->get_type(payload) == SECURITY_ASSOCIATION_V1) + { + sa_payload_t *sa_payload = (sa_payload_t*)payload; + + switch (sa_payload->get_auth_method(sa_payload)) + { + case AUTH_RSA: + case AUTH_XAUTH_INIT_RSA: + case AUTH_XAUTH_RESP_RSA: + case AUTH_HYBRID_INIT_RSA: + case AUTH_HYBRID_RESP_RSA: + use = TRUE; + break; + default: + break; + } + break; + } + } + enumerator->destroy(enumerator); + + return use; +} + +/** + * Add certificates to message + */ +static void build_certs(private_isakmp_cert_post_t *this, message_t *message) +{ + peer_cfg_t *peer_cfg; + + peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); + if (!peer_cfg) + { + return; + } + + switch (peer_cfg->get_cert_policy(peer_cfg)) + { + case CERT_NEVER_SEND: + break; + case CERT_SEND_IF_ASKED: + if (!this->ike_sa->has_condition(this->ike_sa, COND_CERTREQ_SEEN)) + { + break; + } + /* FALL */ + case CERT_ALWAYS_SEND: + { + cert_payload_t *payload; + enumerator_t *enumerator; + certificate_t *cert; + auth_rule_t type; + auth_cfg_t *auth; + + auth = this->ike_sa->get_auth_cfg(this->ike_sa, TRUE); + cert = auth->get(auth, AUTH_RULE_SUBJECT_CERT); + if (!cert) + { + break; + } + payload = cert_payload_create_from_cert(CERTIFICATE_V1, cert); + if (!payload) + { + break; + } + DBG1(DBG_IKE, "sending end entity cert \"%Y\"", + cert->get_subject(cert)); + message->add_payload(message, (payload_t*)payload); + + enumerator = auth->create_enumerator(auth); + while (enumerator->enumerate(enumerator, &type, &cert)) + { + if (type == AUTH_RULE_IM_CERT) + { + payload = cert_payload_create_from_cert(CERTIFICATE_V1, cert); + if (payload) + { + DBG1(DBG_IKE, "sending issuer cert \"%Y\"", + cert->get_subject(cert)); + message->add_payload(message, (payload_t*)payload); + } + } + } + enumerator->destroy(enumerator); + } + } +} + +METHOD(task_t, build_i, status_t, + private_isakmp_cert_post_t *this, message_t *message) +{ + switch (message->get_exchange_type(message)) + { + case ID_PROT: + if (this->state == CR_AUTH) + { + build_certs(this, message); + return SUCCESS; + } + return NEED_MORE; + case AGGRESSIVE: + if (this->state == CR_AUTH) + { + build_certs(this, message); + return SUCCESS; + } + return NEED_MORE; + default: + return FAILED; + } +} + +METHOD(task_t, process_r, status_t, + private_isakmp_cert_post_t *this, message_t *message) +{ + switch (message->get_exchange_type(message)) + { + case ID_PROT: + { + switch (this->state) + { + case CR_SA: + if (!use_certs(this, message)) + { + return SUCCESS; + } + return NEED_MORE; + case CR_KE: + return NEED_MORE; + case CR_AUTH: + return NEED_MORE; + } + } + case AGGRESSIVE: + { + switch (this->state) + { + case CR_SA: + if (!use_certs(this, message)) + { + return SUCCESS; + } + return NEED_MORE; + case CR_AUTH: + return SUCCESS; + default: + return FAILED; + } + } + default: + return FAILED; + } +} + +METHOD(task_t, build_r, status_t, + private_isakmp_cert_post_t *this, message_t *message) +{ + switch (message->get_exchange_type(message)) + { + case ID_PROT: + switch (this->state) + { + case CR_SA: + this->state = CR_KE; + return NEED_MORE; + case CR_KE: + this->state = CR_AUTH; + return NEED_MORE; + case CR_AUTH: + build_certs(this, message); + return SUCCESS; + } + case AGGRESSIVE: + switch (this->state) + { + case CR_SA: + build_certs(this, message); + this->state = CR_AUTH; + return NEED_MORE; + case CR_AUTH: + return SUCCESS; + default: + return FAILED; + } + default: + return FAILED; + } +} + +METHOD(task_t, process_i, status_t, + private_isakmp_cert_post_t *this, message_t *message) +{ + switch (message->get_exchange_type(message)) + { + case ID_PROT: + { + switch (this->state) + { + case CR_SA: + if (!use_certs(this, message)) + { + return SUCCESS; + } + this->state = CR_KE; + return NEED_MORE; + case CR_KE: + this->state = CR_AUTH; + return NEED_MORE; + case CR_AUTH: + return SUCCESS; + default: + return FAILED; + } + break; + } + case AGGRESSIVE: + { + if (!use_certs(this, message)) + { + return SUCCESS; + } + return SUCCESS; + } + default: + return FAILED; + } +} + +METHOD(task_t, get_type, task_type_t, + private_isakmp_cert_post_t *this) +{ + return TASK_ISAKMP_CERT_POST; +} + +METHOD(task_t, migrate, void, + private_isakmp_cert_post_t *this, ike_sa_t *ike_sa) +{ + this->ike_sa = ike_sa; +} + +METHOD(task_t, destroy, void, + private_isakmp_cert_post_t *this) +{ + free(this); +} + +/* + * Described in header. + */ +isakmp_cert_post_t *isakmp_cert_post_create(ike_sa_t *ike_sa, bool initiator) +{ + private_isakmp_cert_post_t *this; + + INIT(this, + .public = { + .task = { + .get_type = _get_type, + .migrate = _migrate, + .destroy = _destroy, + }, + }, + .ike_sa = ike_sa, + .initiator = initiator, + .state = CR_SA, + ); + if (initiator) + { + this->public.task.process = _process_i; + this->public.task.build = _build_i; + } + else + { + this->public.task.process = _process_r; + this->public.task.build = _build_r; + } + return &this->public; +} diff --git a/src/libcharon/sa/ikev1/tasks/isakmp_cert_post.h b/src/libcharon/sa/ikev1/tasks/isakmp_cert_post.h new file mode 100644 index 000000000..2e38df89f --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/isakmp_cert_post.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup isakmp_cert_post isakmp_cert_post + * @{ @ingroup tasks + */ + +#ifndef ISAKMP_CERT_POST_H_ +#define ISAKMP_CERT_POST_H_ + +typedef struct isakmp_cert_post_t isakmp_cert_post_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/task.h> + +/** + * ISAKMP_CERT_POST, IKEv1 certificate processing after authentication. + */ +struct isakmp_cert_post_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * Create a new isakmp_cert_post task. + * + * The initiator parameter means the original initiator, not the initiator + * of the certificate request. + * + * @param ike_sa IKE_SA this task works for + * @param initiator TRUE if task is the original initiator + * @return isakmp_cert_post task to handle by the task_manager + */ +isakmp_cert_post_t *isakmp_cert_post_create(ike_sa_t *ike_sa, bool initiator); + +#endif /** ISAKMP_CERT_POST_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/tasks/isakmp_cert_pre.c b/src/libcharon/sa/ikev1/tasks/isakmp_cert_pre.c new file mode 100644 index 000000000..db25bf3a3 --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/isakmp_cert_pre.c @@ -0,0 +1,533 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "isakmp_cert_pre.h" + +#include <daemon.h> +#include <sa/ike_sa.h> +#include <encoding/payloads/cert_payload.h> +#include <encoding/payloads/sa_payload.h> +#include <encoding/payloads/certreq_payload.h> +#include <credentials/certificates/x509.h> + + +typedef struct private_isakmp_cert_pre_t private_isakmp_cert_pre_t; + +/** + * Private members of a isakmp_cert_pre_t task. + */ +struct private_isakmp_cert_pre_t { + + /** + * Public methods and task_t interface. + */ + isakmp_cert_pre_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; + + /** + * Are we the initiator? + */ + bool initiator; + + /** + * Send certificate requests? + */ + bool send_req; + + /** next message we expect */ + enum { + CR_SA, + CR_KE, + CR_AUTH, + } state; +}; + +/** + * Find the CA certificate for a given certreq payload + */ +static certificate_t* find_certificate(private_isakmp_cert_pre_t *this, + certreq_payload_t *certreq) +{ + identification_t *id; + certificate_t *cert; + + if (certreq->get_cert_type(certreq) != CERT_X509) + { + DBG1(DBG_IKE, "%N CERTREQ not supported - ignored", + certificate_type_names, certreq->get_cert_type(certreq)); + return NULL; + } + id = certreq->get_dn(certreq); + if (!id) + { + DBG1(DBG_IKE, "ignoring certificate request without data", + certificate_type_names, certreq->get_cert_type(certreq)); + return NULL; + } + cert = lib->credmgr->get_cert(lib->credmgr, CERT_X509, KEY_ANY, id, TRUE); + if (cert) + { + DBG1(DBG_IKE, "received cert request for '%Y'", + cert->get_subject(cert)); + } + else + { + DBG1(DBG_IKE, "received cert request for unknown ca '%Y'", id); + } + id->destroy(id); + + return cert; +} + +/** + * read certificate requests + */ +static void process_certreqs(private_isakmp_cert_pre_t *this, message_t *message) +{ + enumerator_t *enumerator; + payload_t *payload; + auth_cfg_t *auth; + + auth = this->ike_sa->get_auth_cfg(this->ike_sa, TRUE); + + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + switch (payload->get_type(payload)) + { + case CERTIFICATE_REQUEST_V1: + { + certificate_t *cert; + + this->ike_sa->set_condition(this->ike_sa, + COND_CERTREQ_SEEN, TRUE); + cert = find_certificate(this, (certreq_payload_t*)payload); + if (cert) + { + auth->add(auth, AUTH_RULE_CA_CERT, cert); + } + break; + } + default: + break; + } + } + enumerator->destroy(enumerator); +} + +/** + * Import receuved certificates + */ +static void process_certs(private_isakmp_cert_pre_t *this, message_t *message) +{ + enumerator_t *enumerator; + payload_t *payload; + auth_cfg_t *auth; + bool first = TRUE; + + auth = this->ike_sa->get_auth_cfg(this->ike_sa, FALSE); + + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + if (payload->get_type(payload) == CERTIFICATE_V1) + { + cert_payload_t *cert_payload; + cert_encoding_t encoding; + certificate_t *cert; + + cert_payload = (cert_payload_t*)payload; + encoding = cert_payload->get_cert_encoding(cert_payload); + + switch (encoding) + { + case ENC_X509_SIGNATURE: + { + cert = cert_payload->get_cert(cert_payload); + if (cert) + { + if (first) + { /* the first is an end entity certificate */ + DBG1(DBG_IKE, "received end entity cert \"%Y\"", + cert->get_subject(cert)); + auth->add(auth, AUTH_HELPER_SUBJECT_CERT, cert); + first = FALSE; + } + else + { + DBG1(DBG_IKE, "received issuer cert \"%Y\"", + cert->get_subject(cert)); + auth->add(auth, AUTH_HELPER_IM_CERT, cert); + } + } + break; + } + case ENC_CRL: + cert = cert_payload->get_cert(cert_payload); + if (cert) + { + DBG1(DBG_IKE, "received CRL \"%Y\"", + cert->get_subject(cert)); + auth->add(auth, AUTH_HELPER_REVOCATION_CERT, cert); + } + break; + case ENC_PKCS7_WRAPPED_X509: + case ENC_PGP: + case ENC_DNS_SIGNED_KEY: + case ENC_KERBEROS_TOKEN: + case ENC_ARL: + case ENC_SPKI: + case ENC_X509_ATTRIBUTE: + case ENC_RAW_RSA_KEY: + case ENC_X509_HASH_AND_URL_BUNDLE: + case ENC_OCSP_CONTENT: + default: + DBG1(DBG_ENC, "certificate encoding %N not supported", + cert_encoding_names, encoding); + } + } + } + enumerator->destroy(enumerator); +} + +/** + * Add the subject of a CA certificate a message + */ +static void add_certreq(private_isakmp_cert_pre_t *this, message_t *message, + certificate_t *cert) +{ + if (cert->get_type(cert) == CERT_X509) + { + x509_t *x509 = (x509_t*)cert; + + if (x509->get_flags(x509) & X509_CA) + { + DBG1(DBG_IKE, "sending cert request for \"%Y\"", + cert->get_subject(cert)); + message->add_payload(message, (payload_t*) + certreq_payload_create_dn(cert->get_subject(cert))); + } + } +} + +/** + * Add auth_cfg's CA certificates to the certificate request + */ +static void add_certreqs(private_isakmp_cert_pre_t *this, + auth_cfg_t *auth, message_t *message) +{ + enumerator_t *enumerator; + auth_rule_t type; + void *value; + + enumerator = auth->create_enumerator(auth); + while (enumerator->enumerate(enumerator, &type, &value)) + { + switch (type) + { + case AUTH_RULE_CA_CERT: + add_certreq(this, message, (certificate_t*)value); + break; + default: + break; + } + } + enumerator->destroy(enumerator); +} + +/** + * Build certificate requests + */ +static void build_certreqs(private_isakmp_cert_pre_t *this, message_t *message) +{ + enumerator_t *enumerator; + ike_cfg_t *ike_cfg; + peer_cfg_t *peer_cfg; + certificate_t *cert; + auth_cfg_t *auth; + + ike_cfg = this->ike_sa->get_ike_cfg(this->ike_sa); + if (!ike_cfg->send_certreq(ike_cfg)) + { + return; + } + /* check if we require a specific CA for that peer */ + peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); + if (peer_cfg) + { + enumerator = peer_cfg->create_auth_cfg_enumerator(peer_cfg, FALSE); + if (enumerator->enumerate(enumerator, &auth)) + { + add_certreqs(this, auth, message); + } + enumerator->destroy(enumerator); + } + if (!message->get_payload(message, CERTIFICATE_REQUEST_V1)) + { + /* otherwise add all trusted CA certificates */ + enumerator = lib->credmgr->create_cert_enumerator(lib->credmgr, + CERT_ANY, KEY_ANY, NULL, TRUE); + while (enumerator->enumerate(enumerator, &cert)) + { + add_certreq(this, message, cert); + } + enumerator->destroy(enumerator); + } +} + +/** + * Check if we actually use certificates for authentication + */ +static bool use_certs(private_isakmp_cert_pre_t *this, message_t *message) +{ + enumerator_t *enumerator; + payload_t *payload; + bool use = FALSE; + + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + if (payload->get_type(payload) == SECURITY_ASSOCIATION_V1) + { + sa_payload_t *sa_payload = (sa_payload_t*)payload; + + switch (sa_payload->get_auth_method(sa_payload)) + { + case AUTH_HYBRID_INIT_RSA: + case AUTH_HYBRID_RESP_RSA: + if (!this->initiator) + { + this->send_req = FALSE; + } + /* FALL */ + case AUTH_RSA: + case AUTH_XAUTH_INIT_RSA: + case AUTH_XAUTH_RESP_RSA: + use = TRUE; + break; + default: + break; + } + break; + } + } + enumerator->destroy(enumerator); + + return use; +} + +METHOD(task_t, build_i, status_t, + private_isakmp_cert_pre_t *this, message_t *message) +{ + switch (message->get_exchange_type(message)) + { + case ID_PROT: + if (this->state == CR_AUTH) + { + build_certreqs(this, message); + } + return NEED_MORE; + case AGGRESSIVE: + if (this->state == CR_SA) + { + build_certreqs(this, message); + } + return NEED_MORE; + default: + return FAILED; + } +} + +METHOD(task_t, process_r, status_t, + private_isakmp_cert_pre_t *this, message_t *message) +{ + switch (message->get_exchange_type(message)) + { + case ID_PROT: + { + switch (this->state) + { + case CR_SA: + if (!use_certs(this, message)) + { + return SUCCESS; + } + return NEED_MORE; + case CR_KE: + process_certreqs(this, message); + return NEED_MORE; + case CR_AUTH: + process_certreqs(this, message); + process_certs(this, message); + return SUCCESS; + } + } + case AGGRESSIVE: + { + switch (this->state) + { + case CR_SA: + if (!use_certs(this, message)) + { + return SUCCESS; + } + process_certreqs(this, message); + return NEED_MORE; + case CR_AUTH: + process_certs(this, message); + return SUCCESS; + default: + return FAILED; + } + } + default: + return FAILED; + } +} + +METHOD(task_t, build_r, status_t, + private_isakmp_cert_pre_t *this, message_t *message) +{ + switch (message->get_exchange_type(message)) + { + case ID_PROT: + switch (this->state) + { + case CR_SA: + this->state = CR_KE; + return NEED_MORE; + case CR_KE: + if (this->send_req) + { + build_certreqs(this, message); + } + this->state = CR_AUTH; + return NEED_MORE; + case CR_AUTH: + return NEED_MORE; + } + case AGGRESSIVE: + switch (this->state) + { + case CR_SA: + this->state = CR_AUTH; + return NEED_MORE; + case CR_AUTH: + return SUCCESS; + default: + return FAILED; + } + default: + return FAILED; + } +} + +METHOD(task_t, process_i, status_t, + private_isakmp_cert_pre_t *this, message_t *message) +{ + switch (message->get_exchange_type(message)) + { + case ID_PROT: + { + switch (this->state) + { + case CR_SA: + if (!use_certs(this, message)) + { + return SUCCESS; + } + this->state = CR_KE; + return NEED_MORE; + case CR_KE: + process_certreqs(this, message); + this->state = CR_AUTH; + return NEED_MORE; + case CR_AUTH: + process_certs(this, message); + return SUCCESS; + default: + return FAILED; + } + break; + } + case AGGRESSIVE: + { + if (!use_certs(this, message)) + { + return SUCCESS; + } + process_certreqs(this, message); + process_certs(this, message); + return SUCCESS; + } + default: + return FAILED; + } +} + +METHOD(task_t, get_type, task_type_t, + private_isakmp_cert_pre_t *this) +{ + return TASK_ISAKMP_CERT_PRE; +} + +METHOD(task_t, migrate, void, + private_isakmp_cert_pre_t *this, ike_sa_t *ike_sa) +{ + this->ike_sa = ike_sa; +} + +METHOD(task_t, destroy, void, + private_isakmp_cert_pre_t *this) +{ + free(this); +} + +/* + * Described in header. + */ +isakmp_cert_pre_t *isakmp_cert_pre_create(ike_sa_t *ike_sa, bool initiator) +{ + private_isakmp_cert_pre_t *this; + + INIT(this, + .public = { + .task = { + .get_type = _get_type, + .migrate = _migrate, + .destroy = _destroy, + }, + }, + .ike_sa = ike_sa, + .initiator = initiator, + .state = CR_SA, + .send_req = TRUE, + ); + if (initiator) + { + this->public.task.build = _build_i; + this->public.task.process = _process_i; + } + else + { + this->public.task.build = _build_r; + this->public.task.process = _process_r; + } + return &this->public; +} diff --git a/src/libcharon/sa/ikev1/tasks/isakmp_cert_pre.h b/src/libcharon/sa/ikev1/tasks/isakmp_cert_pre.h new file mode 100644 index 000000000..908cff020 --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/isakmp_cert_pre.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup isakmp_cert_pre isakmp_cert_pre + * @{ @ingroup tasks + */ + +#ifndef ISAKMP_CERT_PRE_H_ +#define ISAKMP_CERT_PRE_H_ + +typedef struct isakmp_cert_pre_t isakmp_cert_pre_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/task.h> + +/** + * ISAKMP_CERT_PRE task, IKEv1 certificate processing before authentication. + */ +struct isakmp_cert_pre_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * Create a new ISAKMP_CERT_PRE task. + * + * The initiator parameter means the original initiator, not the initiator + * of the certificate request. + * + * @param ike_sa IKE_SA this task works for + * @param initiator TRUE if task is the original initiator + * @return isakmp_cert_pre task to handle by the task_manager + */ +isakmp_cert_pre_t *isakmp_cert_pre_create(ike_sa_t *ike_sa, bool initiator); + +#endif /** ISAKMP_CERT_PRE_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/tasks/isakmp_delete.c b/src/libcharon/sa/ikev1/tasks/isakmp_delete.c new file mode 100644 index 000000000..0640d13b1 --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/isakmp_delete.c @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "isakmp_delete.h" + +#include <daemon.h> +#include <encoding/payloads/delete_payload.h> + +typedef struct private_isakmp_delete_t private_isakmp_delete_t; + +/** + * Private members of a isakmp_delete_t task. + */ +struct private_isakmp_delete_t { + + /** + * Public methods and task_t interface. + */ + isakmp_delete_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; +}; + +METHOD(task_t, build_i, status_t, + private_isakmp_delete_t *this, message_t *message) +{ + delete_payload_t *delete_payload; + ike_sa_id_t *id; + + DBG0(DBG_IKE, "deleting IKE_SA %s[%d] between %H[%Y]...%H[%Y]", + this->ike_sa->get_name(this->ike_sa), + this->ike_sa->get_unique_id(this->ike_sa), + this->ike_sa->get_my_host(this->ike_sa), + this->ike_sa->get_my_id(this->ike_sa), + this->ike_sa->get_other_host(this->ike_sa), + this->ike_sa->get_other_id(this->ike_sa)); + + delete_payload = delete_payload_create(DELETE_V1, PROTO_IKE); + id = this->ike_sa->get_id(this->ike_sa); + delete_payload->set_ike_spi(delete_payload, id->get_initiator_spi(id), + id->get_responder_spi(id)); + message->add_payload(message, (payload_t*)delete_payload); + + DBG1(DBG_IKE, "sending DELETE for IKE_SA %s[%d]", + this->ike_sa->get_name(this->ike_sa), + this->ike_sa->get_unique_id(this->ike_sa)); + + this->ike_sa->set_state(this->ike_sa, IKE_DELETING); + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + return SUCCESS; +} + +METHOD(task_t, process_i, status_t, + private_isakmp_delete_t *this, message_t *message) +{ + return FAILED; +} + +METHOD(task_t, process_r, status_t, + private_isakmp_delete_t *this, message_t *message) +{ + DBG1(DBG_IKE, "received DELETE for IKE_SA %s[%d]", + this->ike_sa->get_name(this->ike_sa), + this->ike_sa->get_unique_id(this->ike_sa)); + DBG0(DBG_IKE, "deleting IKE_SA %s[%d] between %H[%Y]...%H[%Y]", + this->ike_sa->get_name(this->ike_sa), + this->ike_sa->get_unique_id(this->ike_sa), + this->ike_sa->get_my_host(this->ike_sa), + this->ike_sa->get_my_id(this->ike_sa), + this->ike_sa->get_other_host(this->ike_sa), + this->ike_sa->get_other_id(this->ike_sa)); + + this->ike_sa->set_state(this->ike_sa, IKE_DELETING); + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + return DESTROY_ME; +} + +METHOD(task_t, build_r, status_t, + private_isakmp_delete_t *this, message_t *message) +{ + return FAILED; +} + +METHOD(task_t, get_type, task_type_t, + private_isakmp_delete_t *this) +{ + return TASK_ISAKMP_DELETE; +} + +METHOD(task_t, migrate, void, + private_isakmp_delete_t *this, ike_sa_t *ike_sa) +{ + this->ike_sa = ike_sa; +} + +METHOD(task_t, destroy, void, + private_isakmp_delete_t *this) +{ + free(this); +} + +/* + * Described in header. + */ +isakmp_delete_t *isakmp_delete_create(ike_sa_t *ike_sa, bool initiator) +{ + private_isakmp_delete_t *this; + + INIT(this, + .public = { + .task = { + .get_type = _get_type, + .migrate = _migrate, + .destroy = _destroy, + }, + }, + .ike_sa = ike_sa, + ); + + if (initiator) + { + this->public.task.build = _build_i; + this->public.task.process = _process_i; + } + else + { + this->public.task.build = _build_r; + this->public.task.process = _process_r; + } + + return &this->public; +} diff --git a/src/libcharon/sa/ikev1/tasks/isakmp_delete.h b/src/libcharon/sa/ikev1/tasks/isakmp_delete.h new file mode 100644 index 000000000..3b7b40c11 --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/isakmp_delete.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup isakmp_delete isakmp_delete + * @{ @ingroup tasks + */ + +#ifndef ISAKMP_DELETE_H_ +#define ISAKMP_DELETE_H_ + +typedef struct isakmp_delete_t isakmp_delete_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/task.h> + +/** + * Task of type ISAKMP_DELETE, delete an IKEv1 IKE_SA. + */ +struct isakmp_delete_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * Create a new isakmp_delete task. + * + * @param ike_sa IKE_SA this task works for + * @param initiator TRUE if we initiate the delete + * @return isakmp_delete task to handle by the task_manager + */ +isakmp_delete_t *isakmp_delete_create(ike_sa_t *ike_sa, bool initiator); + +#endif /** ISAKMP_DELETE_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/tasks/isakmp_natd.c b/src/libcharon/sa/ikev1/tasks/isakmp_natd.c new file mode 100644 index 000000000..88ee327ba --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/isakmp_natd.c @@ -0,0 +1,440 @@ +/* + * Copyright (C) 2006-2011 Tobias Brunner, + * Copyright (C) 2006-2007 Martin Willi + * Copyright (C) 2006 Daniel Roethlisberger + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "isakmp_natd.h" + +#include <string.h> + +#include <hydra.h> +#include <daemon.h> +#include <sa/ikev1/keymat_v1.h> +#include <config/peer_cfg.h> +#include <crypto/hashers/hasher.h> +#include <encoding/payloads/hash_payload.h> + +typedef struct private_isakmp_natd_t private_isakmp_natd_t; + +/** + * Private members of a ike_natt_t task. + */ +struct private_isakmp_natd_t { + + /** + * Public interface. + */ + isakmp_natd_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; + + /** + * Are we the initiator? + */ + bool initiator; + + /** + * Keymat derivation (from SA) + */ + keymat_v1_t *keymat; + + /** + * Did we process any NAT detection payloads for a source address? + */ + bool src_seen; + + /** + * Did we process any NAT detection payloads for a destination address? + */ + bool dst_seen; + + /** + * Have we found a matching source address NAT hash? + */ + bool src_matched; + + /** + * Have we found a matching destination address NAT hash? + */ + bool dst_matched; +}; + +/** + * Build NAT detection hash for a host. + */ +static chunk_t generate_natd_hash(private_isakmp_natd_t *this, + ike_sa_id_t *ike_sa_id, host_t *host) +{ + hasher_t *hasher; + chunk_t natd_chunk, natd_hash; + u_int64_t spi_i, spi_r; + u_int16_t port; + + hasher = this->keymat->get_hasher(this->keymat); + if (!hasher) + { + DBG1(DBG_IKE, "no hasher available to build NAT-D payload"); + return chunk_empty; + } + + spi_i = ike_sa_id->get_initiator_spi(ike_sa_id); + spi_r = ike_sa_id->get_responder_spi(ike_sa_id); + port = htons(host->get_port(host)); + + /* natd_hash = HASH(CKY-I | CKY-R | IP | Port) */ + natd_chunk = chunk_cata("cccc", chunk_from_thing(spi_i), + chunk_from_thing(spi_r), host->get_address(host), + chunk_from_thing(port)); + hasher->allocate_hash(hasher, natd_chunk, &natd_hash); + DBG3(DBG_IKE, "natd_chunk %B", &natd_chunk); + DBG3(DBG_IKE, "natd_hash %B", &natd_hash); + + return natd_hash; +} + +/** + * Build a faked NAT-D payload to enforce UDP encapsulation. + */ +static chunk_t generate_natd_hash_faked(private_isakmp_natd_t *this) +{ + hasher_t *hasher; + chunk_t chunk; + rng_t *rng; + + hasher = this->keymat->get_hasher(this->keymat); + if (!hasher) + { + DBG1(DBG_IKE, "no hasher available to build NAT-D payload"); + return chunk_empty; + } + rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK); + if (!rng) + { + DBG1(DBG_IKE, "unable to get random bytes for NAT-D fake"); + return chunk_empty; + } + rng->allocate_bytes(rng, hasher->get_hash_size(hasher), &chunk); + rng->destroy(rng); + return chunk; +} + +/** + * Build a NAT-D payload. + */ +static hash_payload_t *build_natd_payload(private_isakmp_natd_t *this, bool src, + host_t *host) +{ + hash_payload_t *payload; + ike_cfg_t *config; + chunk_t hash; + + config = this->ike_sa->get_ike_cfg(this->ike_sa); + if (src && config->force_encap(config)) + { + hash = generate_natd_hash_faked(this); + } + else + { + ike_sa_id_t *ike_sa_id = this->ike_sa->get_id(this->ike_sa); + hash = generate_natd_hash(this, ike_sa_id, host); + } + payload = hash_payload_create(NAT_D_V1); + payload->set_hash(payload, hash); + chunk_free(&hash); + return payload; +} + +/** + * Add NAT-D payloads to the message. + */ +static void add_natd_payloads(private_isakmp_natd_t *this, message_t *message) +{ + hash_payload_t *payload; + host_t *host; + + /* destination has to be added first */ + host = message->get_destination(message); + payload = build_natd_payload(this, FALSE, host); + message->add_payload(message, (payload_t*)payload); + + /* source is added second, compared with IKEv2 we always know the source, + * as these payloads are added in the second Phase 1 exchange or the + * response to the first */ + host = message->get_source(message); + payload = build_natd_payload(this, TRUE, host); + message->add_payload(message, (payload_t*)payload); +} + +/** + * Read NAT-D payloads from message and evaluate them. + */ +static void process_payloads(private_isakmp_natd_t *this, message_t *message) +{ + enumerator_t *enumerator; + payload_t *payload; + hash_payload_t *hash_payload; + chunk_t hash, src_hash, dst_hash; + ike_sa_id_t *ike_sa_id; + host_t *me, *other; + ike_cfg_t *config; + + /* precompute hashes for incoming NAT-D comparison */ + ike_sa_id = message->get_ike_sa_id(message); + me = message->get_destination(message); + other = message->get_source(message); + dst_hash = generate_natd_hash(this, ike_sa_id, me); + src_hash = generate_natd_hash(this, ike_sa_id, other); + + DBG3(DBG_IKE, "precalculated src_hash %B", &src_hash); + DBG3(DBG_IKE, "precalculated dst_hash %B", &dst_hash); + + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + if (payload->get_type(payload) != NAT_D_V1) + { + continue; + } + hash_payload = (hash_payload_t*)payload; + if (!this->dst_seen) + { /* the first NAT-D payload contains the destination hash */ + this->dst_seen = TRUE; + hash = hash_payload->get_hash(hash_payload); + DBG3(DBG_IKE, "received dst_hash %B", &hash); + if (chunk_equals(hash, dst_hash)) + { + this->dst_matched = TRUE; + } + continue; + } + /* the other NAT-D payloads contain source hashes */ + this->src_seen = TRUE; + if (!this->src_matched) + { + hash = hash_payload->get_hash(hash_payload); + DBG3(DBG_IKE, "received src_hash %B", &hash); + if (chunk_equals(hash, src_hash)) + { + this->src_matched = TRUE; + } + } + } + enumerator->destroy(enumerator); + + chunk_free(&src_hash); + chunk_free(&dst_hash); + + if (this->src_seen && this->dst_seen) + { + this->ike_sa->set_condition(this->ike_sa, COND_NAT_HERE, + !this->dst_matched); + this->ike_sa->set_condition(this->ike_sa, COND_NAT_THERE, + !this->src_matched); + config = this->ike_sa->get_ike_cfg(this->ike_sa); + if (this->dst_matched && this->src_matched && + config->force_encap(config)) + { + this->ike_sa->set_condition(this->ike_sa, COND_NAT_FAKE, TRUE); + } + } +} + +METHOD(task_t, build_i, status_t, + private_isakmp_natd_t *this, message_t *message) +{ + status_t result = NEED_MORE; + + switch (message->get_exchange_type(message)) + { + case AGGRESSIVE: + { /* add NAT-D payloads to the second request, already processed + * those by the responder contained in the first response */ + result = SUCCESS; + /* fall */ + } + case ID_PROT: + { /* add NAT-D payloads to the second request, need to process + * those by the responder contained in the second response */ + if (message->get_payload(message, SECURITY_ASSOCIATION_V1)) + { /* wait for the second exchange */ + return NEED_MORE; + } + add_natd_payloads(this, message); + return result; + } + default: + break; + } + return SUCCESS; +} + +METHOD(task_t, process_i, status_t, + private_isakmp_natd_t *this, message_t *message) +{ + status_t result = NEED_MORE; + + if (!this->ike_sa->supports_extension(this->ike_sa, EXT_NATT)) + { /* we didn't receive VIDs inidcating support for NAT-T */ + return SUCCESS; + } + + switch (message->get_exchange_type(message)) + { + case ID_PROT: + { /* process NAT-D payloads in the second response, added them in the + * second request already, so we're done afterwards */ + if (message->get_payload(message, SECURITY_ASSOCIATION_V1)) + { /* wait for the second exchange */ + return NEED_MORE; + } + result = SUCCESS; + /* fall */ + } + case AGGRESSIVE: + { /* process NAT-D payloads in the first response, add them in the + * following second request */ + process_payloads(this, message); + + if (this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY)) + { + this->ike_sa->float_ports(this->ike_sa); + } + return result; + } + default: + break; + } + return SUCCESS; +} + +METHOD(task_t, process_r, status_t, + private_isakmp_natd_t *this, message_t *message) +{ + status_t result = NEED_MORE; + + if (!this->ike_sa->supports_extension(this->ike_sa, EXT_NATT)) + { /* we didn't receive VIDs indicating NAT-T support */ + return SUCCESS; + } + + switch (message->get_exchange_type(message)) + { + case AGGRESSIVE: + { /* proccess NAT-D payloads in the second request, already added ours + * in the first response */ + result = SUCCESS; + /* fall */ + } + case ID_PROT: + { /* process NAT-D payloads in the second request, need to add ours + * to the second response */ + if (message->get_payload(message, SECURITY_ASSOCIATION_V1)) + { /* wait for the second exchange */ + return NEED_MORE; + } + process_payloads(this, message); + return result; + } + default: + break; + } + return SUCCESS; +} + +METHOD(task_t, build_r, status_t, + private_isakmp_natd_t *this, message_t *message) +{ + switch (message->get_exchange_type(message)) + { + case ID_PROT: + { /* add NAT-D payloads to second response, already processed those + * contained in the second request */ + if (message->get_payload(message, SECURITY_ASSOCIATION_V1)) + { /* wait for the second exchange */ + return NEED_MORE; + } + add_natd_payloads(this, message); + return SUCCESS; + } + case AGGRESSIVE: + { /* add NAT-D payloads to the first response, process those contained + * in the following second request */ + add_natd_payloads(this, message); + return NEED_MORE; + } + default: + break; + } + return SUCCESS; +} + +METHOD(task_t, get_type, task_type_t, + private_isakmp_natd_t *this) +{ + return TASK_ISAKMP_NATD; +} + +METHOD(task_t, migrate, void, + private_isakmp_natd_t *this, ike_sa_t *ike_sa) +{ + this->ike_sa = ike_sa; + this->src_seen = FALSE; + this->dst_seen = FALSE; + this->src_matched = FALSE; + this->dst_matched = FALSE; +} + +METHOD(task_t, destroy, void, + private_isakmp_natd_t *this) +{ + free(this); +} + +/* + * Described in header. + */ +isakmp_natd_t *isakmp_natd_create(ike_sa_t *ike_sa, bool initiator) +{ + private_isakmp_natd_t *this; + + INIT(this, + .public = { + .task = { + .get_type = _get_type, + .migrate = _migrate, + .destroy = _destroy, + }, + }, + .ike_sa = ike_sa, + .keymat = (keymat_v1_t*)ike_sa->get_keymat(ike_sa), + .initiator = initiator, + ); + + if (initiator) + { + this->public.task.build = _build_i; + this->public.task.process = _process_i; + } + else + { + this->public.task.build = _build_r; + this->public.task.process = _process_r; + } + + return &this->public; +} diff --git a/src/libcharon/sa/ikev1/tasks/isakmp_natd.h b/src/libcharon/sa/ikev1/tasks/isakmp_natd.h new file mode 100644 index 000000000..b83b07805 --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/isakmp_natd.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2011 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup isakmp_natd isakmp_natd + * @{ @ingroup tasks + */ + +#ifndef ISAKMP_NATD_H_ +#define ISAKMP_NATD_H_ + +typedef struct isakmp_natd_t isakmp_natd_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/task.h> + +/** + * Task of type ISAKMP_NATD, detects NAT situation in IKEv1 Phase 1. + */ +struct isakmp_natd_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * Create a new ISAKMP_NATD task. + * + * @param ike_sa IKE_SA this task works for + * @param initiator TRUE if task is the original initiator + * @return isakmp_natd task to handle by the task_manager + */ +isakmp_natd_t *isakmp_natd_create(ike_sa_t *ike_sa, bool initiator); + +#endif /** ISAKMP_NATD_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/tasks/isakmp_vendor.c b/src/libcharon/sa/ikev1/tasks/isakmp_vendor.c new file mode 100644 index 000000000..a1d64863c --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/isakmp_vendor.c @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2009 Martin Willi + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "isakmp_vendor.h" + +#include <daemon.h> +#include <encoding/payloads/vendor_id_payload.h> + +typedef struct private_isakmp_vendor_t private_isakmp_vendor_t; + +/** + * Private data of an isakmp_vendor_t object. + */ +struct private_isakmp_vendor_t { + + /** + * Public isakmp_vendor_t interface. + */ + isakmp_vendor_t public; + + /** + * Associated IKE_SA + */ + ike_sa_t *ike_sa; + + /** + * Are we the inititator of this task + */ + bool initiator; +}; + +/** + * IKEv1 Vendor ID database + */ +static struct { + /* Description */ + char *desc; + /* extension flag negotiated with vendor ID, if any */ + ike_extension_t extension; + /* send yourself? */ + bool send; + /* length of vendor ID string */ + int len; + /* vendor ID string */ + char *id; +} vendor_ids[] = { + + /* strongSwan MD5("strongSwan") */ + { "strongSwan", EXT_STRONGSWAN, FALSE, 16, + "\x88\x2f\xe5\x6d\x6f\xd2\x0d\xbc\x22\x51\x61\x3b\x2e\xbe\x5b\xeb"}, + + /* XAuth, MD5("draft-ietf-ipsra-isakmp-xauth-06.txt") */ + { "XAuth", EXT_XAUTH, TRUE, 8, + "\x09\x00\x26\x89\xdf\xd6\xb7\x12"}, + + /* NAT-Traversal, MD5("RFC 3947") */ + { "NAT-T (RFC 3947)", EXT_NATT, TRUE, 16, + "\x4a\x13\x1c\x81\x07\x03\x58\x45\x5c\x57\x28\xf2\x0e\x95\x45\x2f"}, + + /* draft-ietf-ipsec-dpd-00 */ + { "DPD", 0, FALSE, 16, + "\xaf\xca\xd7\x13\x68\xa1\xf1\xc9\x6b\x86\x96\xfc\x77\x57\x01\x00"}, + + { "draft-stenberg-ipsec-nat-traversal-01", 0, FALSE, 16, + "\x27\xba\xb5\xdc\x01\xea\x07\x60\xea\x4e\x31\x90\xac\x27\xc0\xd0"}, + + { "draft-stenberg-ipsec-nat-traversal-02", 0, FALSE, 16, + "\x61\x05\xc4\x22\xe7\x68\x47\xe4\x3f\x96\x84\x80\x12\x92\xae\xcd"}, + + { "draft-ietf-ipsec-nat-t-ike-00", 0, FALSE, 16, + "\x44\x85\x15\x2d\x18\xb6\xbb\xcd\x0b\xe8\xa8\x46\x95\x79\xdd\xcc"}, + + { "draft-ietf-ipsec-nat-t-ike-02", 0, FALSE, 16, + "\xcd\x60\x46\x43\x35\xdf\x21\xf8\x7c\xfd\xb2\xfc\x68\xb6\xa4\x48"}, + + { "draft-ietf-ipsec-nat-t-ike-02", 0, FALSE, 16, + "\x90\xcb\x80\x91\x3e\xbb\x69\x6e\x08\x63\x81\xb5\xec\x42\x7b\x1f"}, + + { "draft-ietf-ipsec-nat-t-ike-03", 0, FALSE, 16, + "\x7d\x94\x19\xa6\x53\x10\xca\x6f\x2c\x17\x9d\x92\x15\x52\x9d\x56"}, + + { "Cisco Unity", 0, FALSE, 16, + "\x12\xf5\xf2\x8c\x45\x71\x68\xa9\x70\x2d\x9f\xe2\x74\xcc\x01\x00"}, +}; + +METHOD(task_t, build, status_t, + private_isakmp_vendor_t *this, message_t *message) +{ + vendor_id_payload_t *vid_payload; + bool strongswan; + int i; + + strongswan = lib->settings->get_bool(lib->settings, + "charon.send_vendor_id", FALSE); + for (i = 0; i < countof(vendor_ids); i++) + { + if (vendor_ids[i].send || + (vendor_ids[i].extension == EXT_STRONGSWAN && strongswan)) + { + vid_payload = vendor_id_payload_create_data(VENDOR_ID_V1, + chunk_clone(chunk_create(vendor_ids[i].id, vendor_ids[i].len))); + message->add_payload(message, &vid_payload->payload_interface); + } + } + return this->initiator ? NEED_MORE : SUCCESS; +} + +METHOD(task_t, process, status_t, + private_isakmp_vendor_t *this, message_t *message) +{ + enumerator_t *enumerator; + payload_t *payload; + int i; + + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + if (payload->get_type(payload) == VENDOR_ID_V1) + { + vendor_id_payload_t *vid; + bool found = FALSE; + chunk_t data; + + vid = (vendor_id_payload_t*)payload; + data = vid->get_data(vid); + + for (i = 0; i < countof(vendor_ids); i++) + { + if (chunk_equals(data, chunk_create(vendor_ids[i].id, + vendor_ids[i].len))) + { + DBG1(DBG_IKE, "received %s vendor id", vendor_ids[i].desc); + if (vendor_ids[i].extension) + { + this->ike_sa->enable_extension(this->ike_sa, + vendor_ids[i].extension); + } + found = TRUE; + } + } + if (!found) + { + DBG1(DBG_ENC, "received unknown vendor id: %#B", &data); + } + } + } + enumerator->destroy(enumerator); + + return this->initiator ? SUCCESS : NEED_MORE; +} + +METHOD(task_t, migrate, void, + private_isakmp_vendor_t *this, ike_sa_t *ike_sa) +{ + this->ike_sa = ike_sa; +} + +METHOD(task_t, get_type, task_type_t, + private_isakmp_vendor_t *this) +{ + return TASK_ISAKMP_VENDOR; +} + +METHOD(task_t, destroy, void, + private_isakmp_vendor_t *this) +{ + free(this); +} + +/** + * See header + */ +isakmp_vendor_t *isakmp_vendor_create(ike_sa_t *ike_sa, bool initiator) +{ + private_isakmp_vendor_t *this; + + INIT(this, + .public = { + .task = { + .build = _build, + .process = _process, + .migrate = _migrate, + .get_type = _get_type, + .destroy = _destroy, + }, + }, + .initiator = initiator, + .ike_sa = ike_sa, + ); + + return &this->public; +} diff --git a/src/libcharon/sa/ikev1/tasks/isakmp_vendor.h b/src/libcharon/sa/ikev1/tasks/isakmp_vendor.h new file mode 100644 index 000000000..b81d79034 --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/isakmp_vendor.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup isakmp_vendor isakmp_vendor + * @{ @ingroup tasks + */ + +#ifndef ISAKMP_VENDOR_H_ +#define ISAKMP_VENDOR_H_ + +typedef struct isakmp_vendor_t isakmp_vendor_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/task.h> + +/** + * Vendor ID processing task for IKEv1. + */ +struct isakmp_vendor_t { + + /** + * Implements task interface. + */ + task_t task; +}; + +/** + * Create a isakmp_vendor instance. + * + * @param ike_sa IKE_SA this task works for + * @param initiator TRUE if task is the original initiator + */ +isakmp_vendor_t *isakmp_vendor_create(ike_sa_t *ike_sa, bool initiator); + +#endif /** ISAKMP_VENDOR_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/tasks/main_mode.c b/src/libcharon/sa/ikev1/tasks/main_mode.c new file mode 100644 index 000000000..7f263260c --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/main_mode.c @@ -0,0 +1,1155 @@ +/* + * Copyright (C) 2011 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "main_mode.h" + +#include <string.h> + +#include <daemon.h> +#include <sa/ikev1/keymat_v1.h> +#include <crypto/diffie_hellman.h> +#include <encoding/payloads/sa_payload.h> +#include <encoding/payloads/ke_payload.h> +#include <encoding/payloads/nonce_payload.h> +#include <encoding/payloads/id_payload.h> +#include <encoding/payloads/hash_payload.h> +#include <sa/ikev1/tasks/xauth.h> +#include <sa/ikev1/tasks/mode_config.h> +#include <sa/ikev1/tasks/informational.h> +#include <sa/ikev1/tasks/isakmp_delete.h> + +typedef struct private_main_mode_t private_main_mode_t; + +/** + * Private members of a main_mode_t task. + */ +struct private_main_mode_t { + + /** + * Public methods and task_t interface. + */ + main_mode_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; + + /** + * Are we the initiator? + */ + bool initiator; + + /** + * IKE config to establish + */ + ike_cfg_t *ike_cfg; + + /** + * Peer config to use + */ + peer_cfg_t *peer_cfg; + + /** + * Local authentication configuration + */ + auth_cfg_t *my_auth; + + /** + * Remote authentication configuration + */ + auth_cfg_t *other_auth; + + /** + * selected IKE proposal + */ + proposal_t *proposal; + + /** + * DH exchange + */ + diffie_hellman_t *dh; + + /** + * Keymat derivation (from SA) + */ + keymat_v1_t *keymat; + + /** + * Received public DH value from peer + */ + chunk_t dh_value; + + /** + * Initiators nonce + */ + chunk_t nonce_i; + + /** + * Responder nonce + */ + chunk_t nonce_r; + + /** + * Encoded SA initiator payload used for authentication + */ + chunk_t sa_payload; + + /** + * Negotiated SA lifetime + */ + u_int32_t lifetime; + + /** + * Negotiated authentication method + */ + auth_method_t auth_method; + + /** states of main mode */ + enum { + MM_INIT, + MM_SA, + MM_KE, + MM_AUTH, + } state; +}; + +/** + * Get the first authentcation config from peer config + */ +static auth_cfg_t *get_auth_cfg(peer_cfg_t *peer_cfg, bool local) +{ + enumerator_t *enumerator; + auth_cfg_t *cfg = NULL; + + enumerator = peer_cfg->create_auth_cfg_enumerator(peer_cfg, local); + enumerator->enumerate(enumerator, &cfg); + enumerator->destroy(enumerator); + return cfg; +} + +/** + * Create an authenticator, if supported + */ +static authenticator_t *create_authenticator(private_main_mode_t *this, + id_payload_t *id) +{ + authenticator_t *authenticator; + authenticator = authenticator_create_v1(this->ike_sa, this->initiator, + this->auth_method, this->dh, + this->dh_value, this->sa_payload, + id->get_encoded(id)); + if (!authenticator) + { + DBG1(DBG_IKE, "negotiated authentication method %N not supported", + auth_method_names, this->auth_method); + } + return authenticator; +} + +/** + * Save the encoded SA payload of a message + */ +static bool save_sa_payload(private_main_mode_t *this, message_t *message) +{ + enumerator_t *enumerator; + payload_t *payload, *sa = NULL; + chunk_t data; + size_t offset = IKE_HEADER_LENGTH; + + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + if (payload->get_type(payload) == SECURITY_ASSOCIATION_V1) + { + sa = payload; + break; + } + else + { + offset += payload->get_length(payload); + } + } + enumerator->destroy(enumerator); + + data = message->get_packet_data(message); + if (sa && data.len >= offset + sa->get_length(sa)) + { + /* Get SA payload without 4 byte fixed header */ + data = chunk_skip(data, offset); + data.len = sa->get_length(sa); + data = chunk_skip(data, 4); + this->sa_payload = chunk_clone(data); + return TRUE; + } + return FALSE; +} + +/** + * Generate and add NONCE, KE payload + */ +static bool add_nonce_ke(private_main_mode_t *this, chunk_t *nonce, + message_t *message) +{ + nonce_payload_t *nonce_payload; + ke_payload_t *ke_payload; + rng_t *rng; + + ke_payload = ke_payload_create_from_diffie_hellman(KEY_EXCHANGE_V1, + this->dh); + message->add_payload(message, &ke_payload->payload_interface); + + rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK); + if (!rng) + { + DBG1(DBG_IKE, "no RNG found to create nonce"); + return FALSE; + } + rng->allocate_bytes(rng, NONCE_SIZE, nonce); + rng->destroy(rng); + + nonce_payload = nonce_payload_create(NONCE_V1); + nonce_payload->set_nonce(nonce_payload, *nonce); + message->add_payload(message, &nonce_payload->payload_interface); + + return TRUE; +} + +/** + * Extract nonce from NONCE payload, process KE payload + */ +static bool get_nonce_ke(private_main_mode_t *this, chunk_t *nonce, + message_t *message) +{ + nonce_payload_t *nonce_payload; + ke_payload_t *ke_payload; + + ke_payload = (ke_payload_t*)message->get_payload(message, KEY_EXCHANGE_V1); + if (!ke_payload) + { + DBG1(DBG_IKE, "KE payload missing in message"); + return FALSE; + } + this->dh_value = chunk_clone(ke_payload->get_key_exchange_data(ke_payload)); + this->dh->set_other_public_value(this->dh, this->dh_value); + + nonce_payload = (nonce_payload_t*)message->get_payload(message, NONCE_V1); + if (!nonce_payload) + { + DBG1(DBG_IKE, "NONCE payload missing in message"); + return FALSE; + } + *nonce = nonce_payload->get_nonce(nonce_payload); + + return TRUE; +} + +/** + * Get the two auth classes from local or remote config + */ +static void get_auth_class(peer_cfg_t *peer_cfg, bool local, + auth_class_t *c1, auth_class_t *c2) +{ + enumerator_t *enumerator; + auth_cfg_t *auth; + + *c1 = *c2 = AUTH_CLASS_ANY; + + enumerator = peer_cfg->create_auth_cfg_enumerator(peer_cfg, local); + while (enumerator->enumerate(enumerator, &auth)) + { + if (*c1 == AUTH_CLASS_ANY) + { + *c1 = (uintptr_t)auth->get(auth, AUTH_RULE_AUTH_CLASS); + } + else + { + *c2 = (uintptr_t)auth->get(auth, AUTH_RULE_AUTH_CLASS); + break; + } + } + enumerator->destroy(enumerator); +} + +/** + * Get auth method to use from a peer config + */ +static auth_method_t get_auth_method(private_main_mode_t *this, + peer_cfg_t *peer_cfg) +{ + auth_class_t i1, i2, r1, r2; + + get_auth_class(peer_cfg, this->initiator, &i1, &i2); + get_auth_class(peer_cfg, !this->initiator, &r1, &r2); + + if (i1 == AUTH_CLASS_PUBKEY && r1 == AUTH_CLASS_PUBKEY) + { + if (i2 == AUTH_CLASS_ANY && r2 == AUTH_CLASS_ANY) + { + /* TODO-IKEv1: ECDSA? */ + return AUTH_RSA; + } + if (i2 == AUTH_CLASS_XAUTH) + { + return AUTH_XAUTH_INIT_RSA; + } + if (r2 == AUTH_CLASS_XAUTH) + { + return AUTH_XAUTH_RESP_RSA; + } + } + if (i1 == AUTH_CLASS_PSK && r1 == AUTH_CLASS_PSK) + { + if (i2 == AUTH_CLASS_ANY && r2 == AUTH_CLASS_ANY) + { + return AUTH_PSK; + } + if (i2 == AUTH_CLASS_XAUTH) + { + return AUTH_XAUTH_INIT_PSK; + } + if (r2 == AUTH_CLASS_XAUTH) + { + return AUTH_XAUTH_RESP_PSK; + } + } + if (i1 == AUTH_CLASS_XAUTH && r1 == AUTH_CLASS_PUBKEY && + i2 == AUTH_CLASS_ANY && r2 == AUTH_CLASS_ANY) + { + return AUTH_HYBRID_INIT_RSA; + } + return AUTH_NONE; +} + +/** + * Check if a peer skipped authentication by using Hybrid authentication + */ +static bool skipped_auth(private_main_mode_t *this, bool local) +{ + bool initiator; + + initiator = local == this->initiator; + if (initiator && this->auth_method == AUTH_HYBRID_INIT_RSA) + { + return TRUE; + } + if (!initiator && this->auth_method == AUTH_HYBRID_RESP_RSA) + { + return TRUE; + } + return FALSE; +} + +/** + * Check if remote authentication constraints fulfilled + */ +static bool check_constraints(private_main_mode_t *this) +{ + identification_t *id; + auth_cfg_t *auth; + + auth = this->ike_sa->get_auth_cfg(this->ike_sa, FALSE); + /* auth identity to comply */ + id = this->ike_sa->get_other_id(this->ike_sa); + auth->add(auth, AUTH_RULE_IDENTITY, id->clone(id)); + if (skipped_auth(this, FALSE)) + { + return TRUE; + } + return auth->complies(auth, this->other_auth, TRUE); +} + +/** + * Save authentication information after authentication succeeded + */ +static void save_auth_cfg(private_main_mode_t *this, bool local) +{ + auth_cfg_t *auth; + + if (skipped_auth(this, local)) + { + return; + } + auth = auth_cfg_create(); + /* for local config, we _copy_ entires from the config, as it contains + * certificates we must send later. */ + auth->merge(auth, this->ike_sa->get_auth_cfg(this->ike_sa, local), local); + this->ike_sa->add_auth_cfg(this->ike_sa, local, auth); +} + +/** + * Select the best configuration as responder + */ +static peer_cfg_t *select_config(private_main_mode_t *this, identification_t *id) +{ + enumerator_t *enumerator; + peer_cfg_t *current, *found = NULL; + host_t *me, *other; + + me = this->ike_sa->get_my_host(this->ike_sa); + other = this->ike_sa->get_other_host(this->ike_sa); + DBG1(DBG_CFG, "looking for %N peer configs matching %H...%H[%Y]", + auth_method_names, this->auth_method, me, other, id); + enumerator = charon->backends->create_peer_cfg_enumerator(charon->backends, + me, other, NULL, id, IKEV1); + while (enumerator->enumerate(enumerator, ¤t)) + { + if (get_auth_method(this, current) == this->auth_method) + { + found = current->get_ref(current); + break; + } + } + enumerator->destroy(enumerator); + + if (found) + { + DBG2(DBG_CFG, "selected peer config \"%s\"", found->get_name(found)); + } + return found; +} + +/** + * Check for notify errors, return TRUE if error found + */ +static bool has_notify_errors(private_main_mode_t *this, message_t *message) +{ + enumerator_t *enumerator; + payload_t *payload; + bool err = FALSE; + + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + if (payload->get_type(payload) == NOTIFY_V1) + { + notify_payload_t *notify; + notify_type_t type; + + notify = (notify_payload_t*)payload; + type = notify->get_notify_type(notify); + if (type < 16384) + { + DBG1(DBG_IKE, "received %N error notify", + notify_type_names, type); + err = TRUE; + } + else if (type == INITIAL_CONTACT_IKEV1) + { + if (!this->initiator && this->state == MM_AUTH) + { + /* If authenticated and received INITIAL_CONTACT, + * delete any existing IKE_SAs with that peer. + * The delete takes place when the SA is checked in due + * to other id not known until the 3rd message.*/ + this->ike_sa->set_condition(this->ike_sa, + COND_INIT_CONTACT_SEEN, TRUE); + } + } + else + { + DBG1(DBG_IKE, "received %N notify", notify_type_names, type); + } + } + } + enumerator->destroy(enumerator); + + return err; +} + +/** + * Queue a task sending a notify in an INFORMATIONAL exchange + */ +static status_t send_notify(private_main_mode_t *this, notify_type_t type) +{ + notify_payload_t *notify; + ike_sa_id_t *ike_sa_id; + u_int64_t spi_i, spi_r; + chunk_t spi; + + notify = notify_payload_create_from_protocol_and_type(NOTIFY_V1, + PROTO_IKE, type); + ike_sa_id = this->ike_sa->get_id(this->ike_sa); + spi_i = ike_sa_id->get_initiator_spi(ike_sa_id); + spi_r = ike_sa_id->get_responder_spi(ike_sa_id); + spi = chunk_cata("cc", chunk_from_thing(spi_i), chunk_from_thing(spi_r)); + notify->set_spi_data(notify, spi); + + this->ike_sa->queue_task(this->ike_sa, + (task_t*)informational_create(this->ike_sa, notify)); + /* cancel all active/passive tasks in favour of informational */ + return ALREADY_DONE; +} + +/** + * Queue a delete task if authentication failed as initiator + */ +static status_t send_delete(private_main_mode_t *this) +{ + this->ike_sa->queue_task(this->ike_sa, + (task_t*)isakmp_delete_create(this->ike_sa, TRUE)); + /* cancel all active tasks in favour of informational */ + return ALREADY_DONE; +} + +METHOD(task_t, build_i, status_t, + private_main_mode_t *this, message_t *message) +{ + switch (this->state) + { + case MM_INIT: + { + sa_payload_t *sa_payload; + linked_list_t *proposals; + packet_t *packet; + + this->ike_cfg = this->ike_sa->get_ike_cfg(this->ike_sa); + DBG0(DBG_IKE, "initiating IKE_SA %s[%d] to %H", + this->ike_sa->get_name(this->ike_sa), + this->ike_sa->get_unique_id(this->ike_sa), + this->ike_sa->get_other_host(this->ike_sa)); + this->ike_sa->set_state(this->ike_sa, IKE_CONNECTING); + + this->peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); + this->peer_cfg->get_ref(this->peer_cfg); + + this->my_auth = get_auth_cfg(this->peer_cfg, TRUE); + this->other_auth = get_auth_cfg(this->peer_cfg, FALSE); + if (!this->my_auth || !this->other_auth) + { + DBG1(DBG_CFG, "no auth config found"); + return FAILED; + } + this->auth_method = get_auth_method(this, this->peer_cfg); + if (this->auth_method == AUTH_NONE) + { + DBG1(DBG_CFG, "configuration uses unsupported authentication"); + return FAILED; + } + this->lifetime = this->peer_cfg->get_reauth_time(this->peer_cfg, + FALSE); + if (!this->lifetime) + { /* fall back to rekey time of no rekey time configured */ + this->lifetime = this->peer_cfg->get_rekey_time(this->peer_cfg, + FALSE); + } + proposals = this->ike_cfg->get_proposals(this->ike_cfg); + sa_payload = sa_payload_create_from_proposals_v1(proposals, + this->lifetime, 0, this->auth_method, MODE_NONE, FALSE); + proposals->destroy_offset(proposals, offsetof(proposal_t, destroy)); + + message->add_payload(message, &sa_payload->payload_interface); + + /* pregenerate message to store SA payload */ + if (this->ike_sa->generate_message(this->ike_sa, message, + &packet) != SUCCESS) + { + DBG1(DBG_IKE, "pregenerating SA payload failed"); + return FAILED; + } + packet->destroy(packet); + if (!save_sa_payload(this, message)) + { + DBG1(DBG_IKE, "SA payload invalid"); + return FAILED; + } + + this->state = MM_SA; + return NEED_MORE; + } + case MM_SA: + { + u_int16_t group; + + if (!this->keymat->create_hasher(this->keymat, this->proposal)) + { + return send_notify(this, NO_PROPOSAL_CHOSEN); + } + if (!this->proposal->get_algorithm(this->proposal, + DIFFIE_HELLMAN_GROUP, &group, NULL)) + { + DBG1(DBG_IKE, "DH group selection failed"); + return send_notify(this, NO_PROPOSAL_CHOSEN); + } + this->dh = this->keymat->keymat.create_dh(&this->keymat->keymat, + group); + if (!this->dh) + { + DBG1(DBG_IKE, "negotiated DH group not supported"); + return send_notify(this, INVALID_KEY_INFORMATION); + } + if (!add_nonce_ke(this, &this->nonce_i, message)) + { + return send_notify(this, INVALID_KEY_INFORMATION); + } + this->state = MM_KE; + return NEED_MORE; + } + case MM_KE: + { + authenticator_t *authenticator; + id_payload_t *id_payload; + identification_t *id; + + id = this->my_auth->get(this->my_auth, AUTH_RULE_IDENTITY); + if (!id) + { + DBG1(DBG_CFG, "own identity not known"); + return send_notify(this, INVALID_ID_INFORMATION); + } + + this->ike_sa->set_my_id(this->ike_sa, id->clone(id)); + + id_payload = id_payload_create_from_identification(ID_V1, id); + message->add_payload(message, &id_payload->payload_interface); + + authenticator = create_authenticator(this, id_payload); + if (!authenticator || authenticator->build(authenticator, + message) != SUCCESS) + { + DESTROY_IF(authenticator); + return send_notify(this, AUTHENTICATION_FAILED); + } + authenticator->destroy(authenticator); + save_auth_cfg(this, TRUE); + + this->state = MM_AUTH; + return NEED_MORE; + } + default: + return FAILED; + } +} + +METHOD(task_t, process_r, status_t, + private_main_mode_t *this, message_t *message) +{ + switch (this->state) + { + case MM_INIT: + { + linked_list_t *list; + sa_payload_t *sa_payload; + + this->ike_cfg = this->ike_sa->get_ike_cfg(this->ike_sa); + DBG0(DBG_IKE, "%H is initiating a Main Mode", + message->get_source(message)); + this->ike_sa->set_state(this->ike_sa, IKE_CONNECTING); + + this->ike_sa->update_hosts(this->ike_sa, + message->get_destination(message), + message->get_source(message), TRUE); + + sa_payload = (sa_payload_t*)message->get_payload(message, + SECURITY_ASSOCIATION_V1); + if (!sa_payload || !save_sa_payload(this, message)) + { + DBG1(DBG_IKE, "SA payload missing or invalid"); + return send_notify(this, INVALID_PAYLOAD_TYPE); + } + + list = sa_payload->get_proposals(sa_payload); + this->proposal = this->ike_cfg->select_proposal(this->ike_cfg, + list, FALSE); + list->destroy_offset(list, offsetof(proposal_t, destroy)); + if (!this->proposal) + { + DBG1(DBG_IKE, "no proposal found"); + return send_notify(this, NO_PROPOSAL_CHOSEN); + } + + this->auth_method = sa_payload->get_auth_method(sa_payload); + this->lifetime = sa_payload->get_lifetime(sa_payload); + + this->state = MM_SA; + return NEED_MORE; + } + case MM_SA: + { + u_int16_t group; + + if (!this->keymat->create_hasher(this->keymat, this->proposal)) + { + return send_notify(this, INVALID_KEY_INFORMATION); + } + if (!this->proposal->get_algorithm(this->proposal, + DIFFIE_HELLMAN_GROUP, &group, NULL)) + { + DBG1(DBG_IKE, "DH group selection failed"); + return send_notify(this, INVALID_KEY_INFORMATION); + } + this->dh = lib->crypto->create_dh(lib->crypto, group); + if (!this->dh) + { + DBG1(DBG_IKE, "negotiated DH group not supported"); + return send_notify(this, INVALID_KEY_INFORMATION); + } + if (!get_nonce_ke(this, &this->nonce_i, message)) + { + return send_notify(this, INVALID_PAYLOAD_TYPE); + } + this->state = MM_KE; + return NEED_MORE; + } + case MM_KE: + { + authenticator_t *authenticator; + id_payload_t *id_payload; + identification_t *id; + + id_payload = (id_payload_t*)message->get_payload(message, ID_V1); + if (!id_payload) + { + DBG1(DBG_IKE, "IDii payload missing"); + return send_notify(this, INVALID_PAYLOAD_TYPE); + } + + id = id_payload->get_identification(id_payload); + this->ike_sa->set_other_id(this->ike_sa, id); + this->peer_cfg = select_config(this, id); + if (!this->peer_cfg) + { + DBG1(DBG_IKE, "no peer config found"); + return send_notify(this, AUTHENTICATION_FAILED); + } + this->ike_sa->set_peer_cfg(this->ike_sa, this->peer_cfg); + + this->my_auth = get_auth_cfg(this->peer_cfg, TRUE); + this->other_auth = get_auth_cfg(this->peer_cfg, FALSE); + if (!this->my_auth || !this->other_auth) + { + DBG1(DBG_IKE, "auth config missing"); + return send_notify(this, AUTHENTICATION_FAILED); + } + + authenticator = create_authenticator(this, id_payload); + if (!authenticator || authenticator->process(authenticator, + message) != SUCCESS) + { + DESTROY_IF(authenticator); + return send_notify(this, AUTHENTICATION_FAILED); + } + authenticator->destroy(authenticator); + if (!check_constraints(this)) + { + return send_notify(this, AUTHENTICATION_FAILED); + } + save_auth_cfg(this, FALSE); + + this->state = MM_AUTH; + if (has_notify_errors(this, message)) + { + return FAILED; + } + return NEED_MORE; + } + default: + return FAILED; + } +} + +/** + * Lookup a shared secret for this IKE_SA + */ +static shared_key_t *lookup_shared_key(private_main_mode_t *this) +{ + host_t *me, *other; + identification_t *my_id, *other_id; + shared_key_t *shared_key = NULL; + + /* try to get a PSK for IP addresses */ + me = this->ike_sa->get_my_host(this->ike_sa); + other = this->ike_sa->get_other_host(this->ike_sa); + my_id = identification_create_from_sockaddr(me->get_sockaddr(me)); + other_id = identification_create_from_sockaddr(other->get_sockaddr(other)); + if (my_id && other_id) + { + shared_key = lib->credmgr->get_shared(lib->credmgr, SHARED_IKE, + my_id, other_id); + } + DESTROY_IF(my_id); + DESTROY_IF(other_id); + if (shared_key) + { + return shared_key; + } + + if (this->my_auth && this->other_auth) + { /* as initiator, use identities from configuraiton */ + my_id = this->my_auth->get(this->my_auth, AUTH_RULE_IDENTITY); + other_id = this->other_auth->get(this->other_auth, AUTH_RULE_IDENTITY); + if (my_id && other_id) + { + shared_key = lib->credmgr->get_shared(lib->credmgr, SHARED_IKE, + my_id, other_id); + } + else + { + DBG1(DBG_IKE, "no shared key found for '%Y'[%H] - '%Y'[%H]", + my_id, me, other_id, other); + } + } + else + { /* as responder, we try to find a config by IP */ + enumerator_t *enumerator; + auth_cfg_t *my_auth, *other_auth; + peer_cfg_t *peer_cfg = NULL; + + enumerator = charon->backends->create_peer_cfg_enumerator( + charon->backends, me, other, NULL, NULL, IKEV1); + while (enumerator->enumerate(enumerator, &peer_cfg)) + { + my_auth = get_auth_cfg(peer_cfg, TRUE); + other_auth = get_auth_cfg(peer_cfg, FALSE); + if (my_auth && other_auth) + { + my_id = my_auth->get(my_auth, AUTH_RULE_IDENTITY); + other_id = other_auth->get(other_auth, AUTH_RULE_IDENTITY); + if (my_id && other_id) + { + shared_key = lib->credmgr->get_shared(lib->credmgr, + SHARED_IKE, my_id, other_id); + if (shared_key) + { + break; + } + else + { + DBG1(DBG_IKE, "no shared key found for " + "'%Y'[%H] - '%Y'[%H]", my_id, me, other_id, other); + } + } + } + } + enumerator->destroy(enumerator); + if (!peer_cfg) + { + DBG1(DBG_IKE, "no shared key found for %H - %H", me, other); + } + } + return shared_key; +} + +/** + * Derive key material for this IKE_SA + */ +static bool derive_keys(private_main_mode_t *this, chunk_t nonce_i, + chunk_t nonce_r) +{ + ike_sa_id_t *id = this->ike_sa->get_id(this->ike_sa); + shared_key_t *shared_key = NULL; + + switch (this->auth_method) + { + case AUTH_PSK: + case AUTH_XAUTH_INIT_PSK: + case AUTH_XAUTH_RESP_PSK: + shared_key = lookup_shared_key(this); + break; + default: + break; + } + if (!this->keymat->derive_ike_keys(this->keymat, this->proposal, this->dh, + this->dh_value, nonce_i, nonce_r, id, this->auth_method, shared_key)) + { + DESTROY_IF(shared_key); + DBG1(DBG_IKE, "key derivation for %N failed", + auth_method_names, this->auth_method); + return FALSE; + } + DESTROY_IF(shared_key); + charon->bus->ike_keys(charon->bus, this->ike_sa, this->dh, nonce_i, nonce_r, + NULL); + + return TRUE; +} + +/** + * Set IKE_SA to established state + */ +static void establish(private_main_mode_t *this) +{ + DBG0(DBG_IKE, "IKE_SA %s[%d] established between %H[%Y]...%H[%Y]", + this->ike_sa->get_name(this->ike_sa), + this->ike_sa->get_unique_id(this->ike_sa), + this->ike_sa->get_my_host(this->ike_sa), + this->ike_sa->get_my_id(this->ike_sa), + this->ike_sa->get_other_host(this->ike_sa), + this->ike_sa->get_other_id(this->ike_sa)); + + this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED); + charon->bus->ike_updown(charon->bus, this->ike_sa, TRUE); +} + +METHOD(task_t, build_r, status_t, + private_main_mode_t *this, message_t *message) +{ + switch (this->state) + { + case MM_SA: + { + sa_payload_t *sa_payload; + + sa_payload = sa_payload_create_from_proposal_v1(this->proposal, + this->lifetime, 0, this->auth_method, MODE_NONE, FALSE); + message->add_payload(message, &sa_payload->payload_interface); + + return NEED_MORE; + } + case MM_KE: + { + if (!add_nonce_ke(this, &this->nonce_r, message)) + { + return send_notify(this, INVALID_KEY_INFORMATION); + } + if (!derive_keys(this, this->nonce_i, this->nonce_r)) + { + return send_notify(this, INVALID_KEY_INFORMATION); + } + return NEED_MORE; + } + case MM_AUTH: + { + authenticator_t *authenticator; + id_payload_t *id_payload; + identification_t *id; + + id = this->my_auth->get(this->my_auth, AUTH_RULE_IDENTITY); + if (!id) + { + DBG1(DBG_CFG, "own identity not known"); + return send_notify(this, INVALID_ID_INFORMATION); + } + this->ike_sa->set_my_id(this->ike_sa, id->clone(id)); + + id_payload = id_payload_create_from_identification(ID_V1, id); + message->add_payload(message, &id_payload->payload_interface); + + authenticator = create_authenticator(this, id_payload); + if (!authenticator || authenticator->build(authenticator, + message) != SUCCESS) + { + DESTROY_IF(authenticator); + return send_notify(this, AUTHENTICATION_FAILED); + } + authenticator->destroy(authenticator); + save_auth_cfg(this, TRUE); + + if (this->peer_cfg->get_virtual_ip(this->peer_cfg)) + { + this->ike_sa->queue_task(this->ike_sa, + (task_t*)mode_config_create(this->ike_sa, TRUE)); + } + + switch (this->auth_method) + { + case AUTH_XAUTH_INIT_PSK: + case AUTH_XAUTH_INIT_RSA: + case AUTH_HYBRID_INIT_RSA: + this->ike_sa->queue_task(this->ike_sa, + (task_t*)xauth_create(this->ike_sa, TRUE)); + return SUCCESS; + case AUTH_XAUTH_RESP_PSK: + case AUTH_XAUTH_RESP_RSA: + case AUTH_HYBRID_RESP_RSA: + /* TODO-IKEv1: not yet supported */ + return FAILED; + default: + establish(this); + return SUCCESS; + } + } + default: + return FAILED; + } +} + +METHOD(task_t, process_i, status_t, + private_main_mode_t *this, message_t *message) +{ + switch (this->state) + { + case MM_SA: + { + linked_list_t *list; + sa_payload_t *sa_payload; + auth_method_t auth_method; + u_int32_t lifetime; + + sa_payload = (sa_payload_t*)message->get_payload(message, + SECURITY_ASSOCIATION_V1); + if (!sa_payload) + { + DBG1(DBG_IKE, "SA payload missing"); + return send_notify(this, INVALID_PAYLOAD_TYPE); + } + list = sa_payload->get_proposals(sa_payload); + this->proposal = this->ike_cfg->select_proposal(this->ike_cfg, + list, FALSE); + list->destroy_offset(list, offsetof(proposal_t, destroy)); + if (!this->proposal) + { + DBG1(DBG_IKE, "no proposal found"); + return send_notify(this, NO_PROPOSAL_CHOSEN); + } + + lifetime = sa_payload->get_lifetime(sa_payload); + if (lifetime != this->lifetime) + { + DBG1(DBG_IKE, "received lifetime %us does not match configured " + "%us, using lower value", lifetime, this->lifetime); + } + this->lifetime = min(this->lifetime, lifetime); + auth_method = sa_payload->get_auth_method(sa_payload); + if (auth_method != this->auth_method) + { + DBG1(DBG_IKE, "received %N authentication, but configured %N, " + "continue with configured", auth_method_names, auth_method, + auth_method_names, this->auth_method); + } + return NEED_MORE; + } + case MM_KE: + { + if (!get_nonce_ke(this, &this->nonce_r, message)) + { + return send_notify(this, INVALID_PAYLOAD_TYPE); + } + if (!derive_keys(this, this->nonce_i, this->nonce_r)) + { + return send_notify(this, INVALID_KEY_INFORMATION); + } + return NEED_MORE; + } + case MM_AUTH: + { + authenticator_t *authenticator; + id_payload_t *id_payload; + identification_t *id; + + id_payload = (id_payload_t*)message->get_payload(message, ID_V1); + if (!id_payload) + { + DBG1(DBG_IKE, "IDir payload missing"); + return send_delete(this); + } + id = id_payload->get_identification(id_payload); + if (!id->matches(id, this->other_auth->get(this->other_auth, + AUTH_RULE_IDENTITY))) + { + DBG1(DBG_IKE, "IDir does not match"); + id->destroy(id); + return send_delete(this); + } + this->ike_sa->set_other_id(this->ike_sa, id); + + authenticator = create_authenticator(this, id_payload); + if (!authenticator || authenticator->process(authenticator, + message) != SUCCESS) + { + DESTROY_IF(authenticator); + return send_delete(this); + } + authenticator->destroy(authenticator); + if (!check_constraints(this)) + { + return send_delete(this); + } + save_auth_cfg(this, FALSE); + + switch (this->auth_method) + { + case AUTH_XAUTH_INIT_PSK: + case AUTH_XAUTH_INIT_RSA: + case AUTH_HYBRID_INIT_RSA: + /* wait for XAUTH request */ + return SUCCESS; + case AUTH_XAUTH_RESP_PSK: + case AUTH_XAUTH_RESP_RSA: + case AUTH_HYBRID_RESP_RSA: + /* TODO-IKEv1: not yet */ + return FAILED; + default: + establish(this); + return SUCCESS; + } + } + default: + return FAILED; + } +} + +METHOD(task_t, get_type, task_type_t, + private_main_mode_t *this) +{ + return TASK_MAIN_MODE; +} + +METHOD(task_t, migrate, void, + private_main_mode_t *this, ike_sa_t *ike_sa) +{ + this->ike_sa = ike_sa; +} + +METHOD(task_t, destroy, void, + private_main_mode_t *this) +{ + DESTROY_IF(this->peer_cfg); + DESTROY_IF(this->proposal); + DESTROY_IF(this->dh); + free(this->dh_value.ptr); + free(this->nonce_i.ptr); + free(this->nonce_r.ptr); + free(this->sa_payload.ptr); + free(this); +} + +/* + * Described in header. + */ +main_mode_t *main_mode_create(ike_sa_t *ike_sa, bool initiator) +{ + private_main_mode_t *this; + + INIT(this, + .public = { + .task = { + .get_type = _get_type, + .migrate = _migrate, + .destroy = _destroy, + }, + }, + .ike_sa = ike_sa, + .keymat = (keymat_v1_t*)ike_sa->get_keymat(ike_sa), + .initiator = initiator, + .state = MM_INIT, + ); + + if (initiator) + { + this->public.task.build = _build_i; + this->public.task.process = _process_i; + } + else + { + this->public.task.build = _build_r; + this->public.task.process = _process_r; + } + + return &this->public; +} diff --git a/src/libcharon/sa/ikev1/tasks/main_mode.h b/src/libcharon/sa/ikev1/tasks/main_mode.h new file mode 100644 index 000000000..d266b6e63 --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/main_mode.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup main_mode main_mode + * @{ @ingroup tasks + */ + +#ifndef MAIN_MODE_H_ +#define MAIN_MODE_H_ + +typedef struct main_mode_t main_mode_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/task.h> + +/** + * IKEv1 main mode, establishes a mainmode including authentication. + */ +struct main_mode_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * Create a new main_mode task. + * + * @param ike_sa IKE_SA this task works for + * @param initiator TRUE if task initiated locally + * @return task to handle by the task_manager + */ +main_mode_t *main_mode_create(ike_sa_t *ike_sa, bool initiator); + +#endif /** MAIN_MODE_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/tasks/mode_config.c b/src/libcharon/sa/ikev1/tasks/mode_config.c new file mode 100644 index 000000000..e99d07428 --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/mode_config.c @@ -0,0 +1,417 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "mode_config.h" + +#include <daemon.h> +#include <hydra.h> +#include <encoding/payloads/cp_payload.h> + +typedef struct private_mode_config_t private_mode_config_t; + +/** + * Private members of a mode_config_t task. + */ +struct private_mode_config_t { + + /** + * Public methods and task_t interface. + */ + mode_config_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; + + /** + * Are we the initiator? + */ + bool initiator; + + /** + * virtual ip + */ + host_t *virtual_ip; + + /** + * list of attributes requested and its handler, entry_t + */ + linked_list_t *requested; +}; + +/** + * Entry for a requested attribute and the requesting handler + */ +typedef struct { + /** attribute requested */ + configuration_attribute_type_t type; + /** handler requesting this attribute */ + attribute_handler_t *handler; +} entry_t; + +/** + * build INTERNAL_IPV4/6_ADDRESS attribute from virtual ip + */ +static configuration_attribute_t *build_vip(host_t *vip) +{ + configuration_attribute_type_t type; + chunk_t chunk, prefix; + + if (vip->get_family(vip) == AF_INET) + { + type = INTERNAL_IP4_ADDRESS; + if (vip->is_anyaddr(vip)) + { + chunk = chunk_empty; + } + else + { + chunk = vip->get_address(vip); + } + } + else + { + type = INTERNAL_IP6_ADDRESS; + if (vip->is_anyaddr(vip)) + { + chunk = chunk_empty; + } + else + { + prefix = chunk_alloca(1); + *prefix.ptr = 64; + chunk = vip->get_address(vip); + chunk = chunk_cata("cc", chunk, prefix); + } + } + return configuration_attribute_create_chunk(CONFIGURATION_ATTRIBUTE_V1, + type, chunk); +} + +/** + * Handle a received attribute as initiator + */ +static void handle_attribute(private_mode_config_t *this, + configuration_attribute_t *ca) +{ + attribute_handler_t *handler = NULL; + enumerator_t *enumerator; + entry_t *entry; + + /* find the handler which requested this attribute */ + enumerator = this->requested->create_enumerator(this->requested); + while (enumerator->enumerate(enumerator, &entry)) + { + if (entry->type == ca->get_type(ca)) + { + handler = entry->handler; + this->requested->remove_at(this->requested, enumerator); + free(entry); + break; + } + } + enumerator->destroy(enumerator); + + /* and pass it to the handle function */ + handler = hydra->attributes->handle(hydra->attributes, + this->ike_sa->get_other_id(this->ike_sa), handler, + ca->get_type(ca), ca->get_chunk(ca)); + if (handler) + { + this->ike_sa->add_configuration_attribute(this->ike_sa, + handler, ca->get_type(ca), ca->get_chunk(ca)); + } +} + +/** + * process a single configuration attribute + */ +static void process_attribute(private_mode_config_t *this, + configuration_attribute_t *ca) +{ + host_t *ip; + chunk_t addr; + int family = AF_INET6; + + switch (ca->get_type(ca)) + { + case INTERNAL_IP4_ADDRESS: + family = AF_INET; + /* fall */ + case INTERNAL_IP6_ADDRESS: + { + addr = ca->get_chunk(ca); + if (addr.len == 0) + { + ip = host_create_any(family); + } + else + { + /* skip prefix byte in IPv6 payload*/ + if (family == AF_INET6) + { + addr.len--; + } + ip = host_create_from_chunk(family, addr, 0); + } + if (ip) + { + DESTROY_IF(this->virtual_ip); + this->virtual_ip = ip; + } + break; + } + default: + { + if (this->initiator) + { + handle_attribute(this, ca); + } + } + } +} + +/** + * Scan for configuration payloads and attributes + */ +static void process_payloads(private_mode_config_t *this, message_t *message) +{ + enumerator_t *enumerator, *attributes; + payload_t *payload; + + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + if (payload->get_type(payload) == CONFIGURATION_V1) + { + cp_payload_t *cp = (cp_payload_t*)payload; + configuration_attribute_t *ca; + + switch (cp->get_type(cp)) + { + case CFG_REQUEST: + case CFG_REPLY: + attributes = cp->create_attribute_enumerator(cp); + while (attributes->enumerate(attributes, &ca)) + { + DBG2(DBG_IKE, "processing %N attribute", + configuration_attribute_type_names, ca->get_type(ca)); + process_attribute(this, ca); + } + attributes->destroy(attributes); + break; + default: + DBG1(DBG_IKE, "ignoring %N config payload", + config_type_names, cp->get_type(cp)); + break; + } + } + } + enumerator->destroy(enumerator); +} + +METHOD(task_t, build_i, status_t, + private_mode_config_t *this, message_t *message) +{ + cp_payload_t *cp = NULL; + enumerator_t *enumerator; + attribute_handler_t *handler; + peer_cfg_t *config; + configuration_attribute_type_t type; + chunk_t data; + host_t *vip; + + /* reuse virtual IP if we already have one */ + vip = this->ike_sa->get_virtual_ip(this->ike_sa, TRUE); + if (!vip) + { + config = this->ike_sa->get_peer_cfg(this->ike_sa); + vip = config->get_virtual_ip(config); + } + if (vip) + { + cp = cp_payload_create_type(CONFIGURATION_V1, CFG_REQUEST); + cp->add_attribute(cp, build_vip(vip)); + } + + enumerator = hydra->attributes->create_initiator_enumerator(hydra->attributes, + this->ike_sa->get_other_id(this->ike_sa), vip); + while (enumerator->enumerate(enumerator, &handler, &type, &data)) + { + configuration_attribute_t *ca; + entry_t *entry; + + DBG2(DBG_IKE, "building %N attribute", + configuration_attribute_type_names, type); + ca = configuration_attribute_create_chunk(CONFIGURATION_ATTRIBUTE_V1, + type, data); + if (!cp) + { + cp = cp_payload_create_type(CONFIGURATION_V1, CFG_REQUEST); + } + cp->add_attribute(cp, ca); + + INIT(entry, + .type = type, + .handler = handler, + ); + this->requested->insert_last(this->requested, entry); + } + enumerator->destroy(enumerator); + + if (cp) + { + message->add_payload(message, (payload_t*)cp); + } + return NEED_MORE; +} + +METHOD(task_t, process_r, status_t, + private_mode_config_t *this, message_t *message) +{ + process_payloads(this, message); + return NEED_MORE; +} + +METHOD(task_t, build_r, status_t, + private_mode_config_t *this, message_t *message) +{ + enumerator_t *enumerator; + configuration_attribute_type_t type; + chunk_t value; + host_t *vip = NULL; + cp_payload_t *cp = NULL; + peer_cfg_t *config; + identification_t *id; + + id = this->ike_sa->get_other_eap_id(this->ike_sa); + + config = this->ike_sa->get_peer_cfg(this->ike_sa); + if (this->virtual_ip) + { + DBG1(DBG_IKE, "peer requested virtual IP %H", this->virtual_ip); + if (config->get_pool(config)) + { + vip = hydra->attributes->acquire_address(hydra->attributes, + config->get_pool(config), id, this->virtual_ip); + } + cp = cp_payload_create_type(CONFIGURATION_V1, CFG_REPLY); + if (vip) + { + DBG1(DBG_IKE, "assigning virtual IP %H to peer '%Y'", vip, id); + this->ike_sa->set_virtual_ip(this->ike_sa, FALSE, vip); + cp->add_attribute(cp, build_vip(vip)); + } + else + { + DBG1(DBG_IKE, "no virtual IP found, sending empty config payload"); + } + } + /* query registered providers for additional attributes to include */ + enumerator = hydra->attributes->create_responder_enumerator( + hydra->attributes, config->get_pool(config), id, vip); + while (enumerator->enumerate(enumerator, &type, &value)) + { + if (!cp) + { + cp = cp_payload_create_type(CONFIGURATION_V1, CFG_REPLY); + } + DBG2(DBG_IKE, "building %N attribute", + configuration_attribute_type_names, type); + cp->add_attribute(cp, + configuration_attribute_create_chunk(CONFIGURATION_ATTRIBUTE_V1, + type, value)); + } + enumerator->destroy(enumerator); + + if (cp) + { + message->add_payload(message, (payload_t*)cp); + } + DESTROY_IF(vip); + return SUCCESS; +} + +METHOD(task_t, process_i, status_t, + private_mode_config_t *this, message_t *message) +{ + process_payloads(this, message); + + if (this->virtual_ip) + { + this->ike_sa->set_virtual_ip(this->ike_sa, TRUE, this->virtual_ip); + } + return SUCCESS; +} + +METHOD(task_t, get_type, task_type_t, + private_mode_config_t *this) +{ + return TASK_MODE_CONFIG; +} + +METHOD(task_t, migrate, void, + private_mode_config_t *this, ike_sa_t *ike_sa) +{ + DESTROY_IF(this->virtual_ip); + + this->ike_sa = ike_sa; + this->virtual_ip = NULL; + this->requested->destroy_function(this->requested, free); + this->requested = linked_list_create(); +} + +METHOD(task_t, destroy, void, + private_mode_config_t *this) +{ + DESTROY_IF(this->virtual_ip); + this->requested->destroy_function(this->requested, free); + free(this); +} + +/* + * Described in header. + */ +mode_config_t *mode_config_create(ike_sa_t *ike_sa, bool initiator) +{ + private_mode_config_t *this; + + INIT(this, + .public = { + .task = { + .get_type = _get_type, + .migrate = _migrate, + .destroy = _destroy, + }, + }, + .initiator = initiator, + .ike_sa = ike_sa, + .requested = linked_list_create(), + ); + + if (initiator) + { + this->public.task.build = _build_i; + this->public.task.process = _process_i; + } + else + { + this->public.task.build = _build_r; + this->public.task.process = _process_r; + } + + return &this->public; +} diff --git a/src/libcharon/sa/ikev1/tasks/mode_config.h b/src/libcharon/sa/ikev1/tasks/mode_config.h new file mode 100644 index 000000000..026545eba --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/mode_config.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup mode_config mode_config + * @{ @ingroup tasks + */ + +#ifndef MODE_CONFIG_H_ +#define MODE_CONFIG_H_ + +typedef struct mode_config_t mode_config_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/task.h> + +/** + * Task of type TASK_MODE_COFNIG, IKEv1 configuration attribute exchange. + */ +struct mode_config_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * Create a new mode_config task. + * + * @param ike_sa IKE_SA this task works for + * @param initiator TRUE for initiator + * @return mode_config task to handle by the task_manager + */ +mode_config_t *mode_config_create(ike_sa_t *ike_sa, bool initiator); + +#endif /** MODE_CONFIG_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/tasks/quick_delete.c b/src/libcharon/sa/ikev1/tasks/quick_delete.c new file mode 100644 index 000000000..a5f27c5e0 --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/quick_delete.c @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "quick_delete.h" + +#include <daemon.h> +#include <encoding/payloads/delete_payload.h> + +typedef struct private_quick_delete_t private_quick_delete_t; + +/** + * Private members of a quick_delete_t task. + */ +struct private_quick_delete_t { + + /** + * Public methods and task_t interface. + */ + quick_delete_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; + + /** + * Are we the initiator? + */ + bool initiator; + + /** + * Protocol of CHILD_SA to delete + */ + protocol_id_t protocol; + + /** + * Inbound SPI of CHILD_SA to delete + */ + u_int32_t spi; + + /** + * Send delete even if SA does not exist + */ + bool force; +}; + +/** + * Delete the specified CHILD_SA, if found + */ +static bool delete_child(private_quick_delete_t *this, + protocol_id_t protocol, u_int32_t spi) +{ + u_int64_t bytes_in, bytes_out; + child_sa_t *child_sa; + + child_sa = this->ike_sa->get_child_sa(this->ike_sa, protocol, spi, TRUE); + if (!child_sa) + { /* fallback and check for outbound SA */ + child_sa = this->ike_sa->get_child_sa(this->ike_sa, protocol, spi, FALSE); + if (!child_sa) + { + return FALSE; + } + this->spi = spi = child_sa->get_spi(child_sa, TRUE); + } + + child_sa->set_state(child_sa, CHILD_DELETING); + + child_sa->get_usestats(child_sa, TRUE, NULL, &bytes_in); + child_sa->get_usestats(child_sa, FALSE, NULL, &bytes_out); + + DBG0(DBG_IKE, "closing CHILD_SA %s{%d} " + "with SPIs %.8x_i (%llu bytes) %.8x_o (%llu bytes) and TS %#R=== %#R", + child_sa->get_name(child_sa), child_sa->get_reqid(child_sa), + ntohl(child_sa->get_spi(child_sa, TRUE)), bytes_in, + ntohl(child_sa->get_spi(child_sa, FALSE)), bytes_out, + child_sa->get_traffic_selectors(child_sa, TRUE), + child_sa->get_traffic_selectors(child_sa, FALSE)); + + charon->bus->child_updown(charon->bus, child_sa, FALSE); + + this->ike_sa->destroy_child_sa(this->ike_sa, protocol, spi); + + /* TODO-IKEv1: handle close action? */ + + return TRUE; +} + +METHOD(task_t, build_i, status_t, + private_quick_delete_t *this, message_t *message) +{ + if (delete_child(this, this->protocol, this->spi) || this->force) + { + delete_payload_t *delete_payload; + + DBG1(DBG_IKE, "sending DELETE for %N CHILD_SA with SPI %.8x", + protocol_id_names, this->protocol, ntohl(this->spi)); + + delete_payload = delete_payload_create(DELETE_V1, PROTO_ESP); + delete_payload->add_spi(delete_payload, this->spi); + message->add_payload(message, &delete_payload->payload_interface); + + return SUCCESS; + } + return FAILED; +} + +METHOD(task_t, process_i, status_t, + private_quick_delete_t *this, message_t *message) +{ + return FAILED; +} + +METHOD(task_t, process_r, status_t, + private_quick_delete_t *this, message_t *message) +{ + enumerator_t *payloads, *spis; + payload_t *payload; + delete_payload_t *delete_payload; + protocol_id_t protocol; + u_int32_t spi; + + payloads = message->create_payload_enumerator(message); + while (payloads->enumerate(payloads, &payload)) + { + if (payload->get_type(payload) == DELETE_V1) + { + delete_payload = (delete_payload_t*)payload; + protocol = delete_payload->get_protocol_id(delete_payload); + if (protocol != PROTO_ESP && protocol != PROTO_AH) + { + continue; + } + spis = delete_payload->create_spi_enumerator(delete_payload); + while (spis->enumerate(spis, &spi)) + { + DBG1(DBG_IKE, "received DELETE for %N CHILD_SA with SPI %.8x", + protocol_id_names, protocol, ntohl(spi)); + if (!delete_child(this, protocol, spi)) + { + DBG1(DBG_IKE, "CHILD_SA not found, ignored"); + continue; + } + } + spis->destroy(spis); + } + } + payloads->destroy(payloads); + + return SUCCESS; +} + +METHOD(task_t, build_r, status_t, + private_quick_delete_t *this, message_t *message) +{ + return FAILED; +} + +METHOD(task_t, get_type, task_type_t, + private_quick_delete_t *this) +{ + return TASK_QUICK_DELETE; +} + +METHOD(task_t, migrate, void, + private_quick_delete_t *this, ike_sa_t *ike_sa) +{ + this->ike_sa = ike_sa; +} + +METHOD(task_t, destroy, void, + private_quick_delete_t *this) +{ + free(this); +} + +/* + * Described in header. + */ +quick_delete_t *quick_delete_create(ike_sa_t *ike_sa, protocol_id_t protocol, + u_int32_t spi, bool force) +{ + private_quick_delete_t *this; + + INIT(this, + .public = { + .task = { + .get_type = _get_type, + .migrate = _migrate, + .destroy = _destroy, + }, + }, + .ike_sa = ike_sa, + .protocol = protocol, + .spi = spi, + .force = force, + ); + + if (protocol != PROTO_NONE) + { + this->public.task.build = _build_i; + this->public.task.process = _process_i; + } + else + { + this->public.task.build = _build_r; + this->public.task.process = _process_r; + } + return &this->public; +} diff --git a/src/libcharon/sa/ikev1/tasks/quick_delete.h b/src/libcharon/sa/ikev1/tasks/quick_delete.h new file mode 100644 index 000000000..1cdf07c48 --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/quick_delete.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup quick_delete quick_delete + * @{ @ingroup tasks + */ + +#ifndef QUICK_DELETE_H_ +#define QUICK_DELETE_H_ + +typedef struct quick_delete_t quick_delete_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/task.h> +#include <sa/child_sa.h> + +/** + * Task of type QUICK_DELETE, delete an IKEv1 quick mode SA. + */ +struct quick_delete_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * Create a new quick_delete task. + * + * @param ike_sa IKE_SA this task works for + * @param protocol protocol of CHILD_SA to delete, PROTO_NONE as responder + * @param spi inbound SPI of CHILD_SA to delete + * @param force send delete even if SA does not exist + * @return quick_delete task to handle by the task_manager + */ +quick_delete_t *quick_delete_create(ike_sa_t *ike_sa, protocol_id_t protocol, + u_int32_t spi, bool force); + +#endif /** QUICK_DELETE_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/tasks/quick_mode.c b/src/libcharon/sa/ikev1/tasks/quick_mode.c new file mode 100644 index 000000000..9e71642af --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/quick_mode.c @@ -0,0 +1,947 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "quick_mode.h" + +#include <string.h> + +#include <daemon.h> +#include <sa/ikev1/keymat_v1.h> +#include <encoding/payloads/sa_payload.h> +#include <encoding/payloads/nonce_payload.h> +#include <encoding/payloads/ke_payload.h> +#include <encoding/payloads/id_payload.h> +#include <encoding/payloads/payload.h> +#include <sa/ikev1/tasks/informational.h> +#include <sa/ikev1/tasks/quick_delete.h> + +typedef struct private_quick_mode_t private_quick_mode_t; + +/** + * Private members of a quick_mode_t task. + */ +struct private_quick_mode_t { + + /** + * Public methods and task_t interface. + */ + quick_mode_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; + + /** + * TRUE if we are initiating quick mode + */ + bool initiator; + + /** + * Traffic selector of initiator + */ + traffic_selector_t *tsi; + + /** + * Traffic selector of responder + */ + traffic_selector_t *tsr; + + /** + * Initiators nonce + */ + chunk_t nonce_i; + + /** + * Responder nonce + */ + chunk_t nonce_r; + + /** + * Initiators ESP SPI + */ + u_int32_t spi_i; + + /** + * Responder ESP SPI + */ + u_int32_t spi_r; + + /** + * selected CHILD_SA proposal + */ + proposal_t *proposal; + + /** + * Config of CHILD_SA to establish + */ + child_cfg_t *config; + + /** + * CHILD_SA we are about to establish + */ + child_sa_t *child_sa; + + /** + * IKEv1 keymat + */ + keymat_v1_t *keymat; + + /** + * DH exchange, when PFS is in use + */ + diffie_hellman_t *dh; + + /** + * Negotiated lifetime of new SA + */ + u_int32_t lifetime; + + /** + * Negotaited lifebytes of new SA + */ + u_int64_t lifebytes; + + /** + * Notify type in case of error + */ + notify_type_t notify_type; + + /** states of quick mode */ + enum { + QM_INIT, + QM_NEGOTIATED, + } state; +}; + +/** + * Install negotiated CHILD_SA + */ +static bool install(private_quick_mode_t *this) +{ + status_t status, status_i, status_o; + chunk_t encr_i, encr_r, integ_i, integ_r; + linked_list_t *tsi, *tsr; + + this->child_sa->set_proposal(this->child_sa, this->proposal); + this->child_sa->set_state(this->child_sa, CHILD_INSTALLING); + this->child_sa->set_mode(this->child_sa, MODE_TUNNEL); + this->child_sa->set_protocol(this->child_sa, + this->proposal->get_protocol(this->proposal)); + + status_i = status_o = FAILED; + encr_i = encr_r = integ_i = integ_r = chunk_empty; + tsi = linked_list_create(); + tsr = linked_list_create(); + tsi->insert_last(tsi, this->tsi); + tsr->insert_last(tsr, this->tsr); + if (this->keymat->derive_child_keys(this->keymat, this->proposal, this->dh, + this->spi_i, this->spi_r, this->nonce_i, this->nonce_r, + &encr_i, &integ_i, &encr_r, &integ_r)) + { + if (this->initiator) + { + status_i = this->child_sa->install(this->child_sa, encr_r, integ_r, + this->spi_i, 0, TRUE, FALSE, tsi, tsr); + status_o = this->child_sa->install(this->child_sa, encr_i, integ_i, + this->spi_r, 0, FALSE, FALSE, tsi, tsr); + } + else + { + status_i = this->child_sa->install(this->child_sa, encr_i, integ_i, + this->spi_r, 0, TRUE, FALSE, tsr, tsi); + status_o = this->child_sa->install(this->child_sa, encr_r, integ_r, + this->spi_i, 0, FALSE, FALSE, tsr, tsi); + } + } + chunk_clear(&integ_i); + chunk_clear(&integ_r); + chunk_clear(&encr_i); + chunk_clear(&encr_r); + + if (status_i != SUCCESS || status_o != SUCCESS) + { + DBG1(DBG_IKE, "unable to install %s%s%sIPsec SA (SAD) in kernel", + (status_i != SUCCESS) ? "inbound " : "", + (status_i != SUCCESS && status_o != SUCCESS) ? "and ": "", + (status_o != SUCCESS) ? "outbound " : ""); + tsi->destroy(tsi); + tsr->destroy(tsr); + return FALSE; + } + + if (this->initiator) + { + status = this->child_sa->add_policies(this->child_sa, tsi, tsr); + } + else + { + status = this->child_sa->add_policies(this->child_sa, tsr, tsi); + } + tsi->destroy(tsi); + tsr->destroy(tsr); + if (status != SUCCESS) + { + DBG1(DBG_IKE, "unable to install IPsec policies (SPD) in kernel"); + return FALSE; + } + + charon->bus->child_keys(charon->bus, this->child_sa, this->initiator, + this->dh, this->nonce_i, this->nonce_r); + + /* add to IKE_SA, and remove from task */ + this->child_sa->set_state(this->child_sa, CHILD_INSTALLED); + this->ike_sa->add_child_sa(this->ike_sa, this->child_sa); + + DBG0(DBG_IKE, "CHILD_SA %s{%d} established " + "with SPIs %.8x_i %.8x_o and TS %#R=== %#R", + this->child_sa->get_name(this->child_sa), + this->child_sa->get_reqid(this->child_sa), + ntohl(this->child_sa->get_spi(this->child_sa, TRUE)), + ntohl(this->child_sa->get_spi(this->child_sa, FALSE)), + this->child_sa->get_traffic_selectors(this->child_sa, TRUE), + this->child_sa->get_traffic_selectors(this->child_sa, FALSE)); + + charon->bus->child_updown(charon->bus, this->child_sa, TRUE); + + this->child_sa = NULL; + + return TRUE; +} + +/** + * Generate and add NONCE + */ +static bool add_nonce(private_quick_mode_t *this, chunk_t *nonce, + message_t *message) +{ + nonce_payload_t *nonce_payload; + rng_t *rng; + + rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK); + if (!rng) + { + DBG1(DBG_IKE, "no RNG found to create nonce"); + return FALSE; + } + rng->allocate_bytes(rng, NONCE_SIZE, nonce); + rng->destroy(rng); + + nonce_payload = nonce_payload_create(NONCE_V1); + nonce_payload->set_nonce(nonce_payload, *nonce); + message->add_payload(message, &nonce_payload->payload_interface); + + return TRUE; +} + +/** + * Extract nonce from NONCE payload + */ +static bool get_nonce(private_quick_mode_t *this, chunk_t *nonce, + message_t *message) +{ + nonce_payload_t *nonce_payload; + + nonce_payload = (nonce_payload_t*)message->get_payload(message, NONCE_V1); + if (!nonce_payload) + { + DBG1(DBG_IKE, "NONCE payload missing in message"); + return FALSE; + } + *nonce = nonce_payload->get_nonce(nonce_payload); + + return TRUE; +} + +/** + * Add KE payload to message + */ +static void add_ke(private_quick_mode_t *this, message_t *message) +{ + ke_payload_t *ke_payload; + + ke_payload = ke_payload_create_from_diffie_hellman(KEY_EXCHANGE_V1, this->dh); + message->add_payload(message, &ke_payload->payload_interface); +} + +/** + * Get DH value from a KE payload + */ +static bool get_ke(private_quick_mode_t *this, message_t *message) +{ + ke_payload_t *ke_payload; + + ke_payload = (ke_payload_t*)message->get_payload(message, KEY_EXCHANGE_V1); + if (!ke_payload) + { + DBG1(DBG_IKE, "KE payload missing"); + return FALSE; + } + this->dh->set_other_public_value(this->dh, + ke_payload->get_key_exchange_data(ke_payload)); + return TRUE; +} + +/** + * Select a traffic selector from configuration + */ +static traffic_selector_t* select_ts(private_quick_mode_t *this, bool local, + linked_list_t *supplied) +{ + traffic_selector_t *ts; + linked_list_t *list; + host_t *host; + + host = this->ike_sa->get_virtual_ip(this->ike_sa, local); + if (!host) + { + if (local) + { + host = this->ike_sa->get_my_host(this->ike_sa); + } + else + { + host = this->ike_sa->get_other_host(this->ike_sa); + } + } + list = this->config->get_traffic_selectors(this->config, local, + supplied, host); + if (list->get_first(list, (void**)&ts) == SUCCESS) + { + if (list->get_count(list) > 1) + { + DBG1(DBG_IKE, "configuration has more than one %s traffic selector," + " using first only", local ? "local" : "remote"); + } + ts = ts->clone(ts); + } + else + { + DBG1(DBG_IKE, "%s traffic selector missing in configuration", + local ? "local" : "local"); + ts = NULL; + } + list->destroy_offset(list, offsetof(traffic_selector_t, destroy)); + return ts; +} + +/** + * Add selected traffic selectors to message + */ +static void add_ts(private_quick_mode_t *this, message_t *message) +{ + id_payload_t *id_payload; + host_t *hsi, *hsr; + + if (this->initiator) + { + hsi = this->ike_sa->get_my_host(this->ike_sa); + hsr = this->ike_sa->get_other_host(this->ike_sa); + } + else + { + hsr = this->ike_sa->get_my_host(this->ike_sa); + hsi = this->ike_sa->get_other_host(this->ike_sa); + } + /* add ID payload only if negotiating non host2host tunnels */ + if (!this->tsi->is_host(this->tsi, hsi) || + !this->tsr->is_host(this->tsr, hsr) || + this->tsi->get_protocol(this->tsi) || + this->tsr->get_protocol(this->tsr) || + this->tsi->get_from_port(this->tsi) || + this->tsr->get_from_port(this->tsr) || + this->tsi->get_to_port(this->tsi) != 65535 || + this->tsr->get_to_port(this->tsr) != 65535) + { + id_payload = id_payload_create_from_ts(this->tsi); + message->add_payload(message, &id_payload->payload_interface); + id_payload = id_payload_create_from_ts(this->tsr); + message->add_payload(message, &id_payload->payload_interface); + } +} + +/** + * Get traffic selectors from received message + */ +static bool get_ts(private_quick_mode_t *this, message_t *message) +{ + traffic_selector_t *tsi = NULL, *tsr = NULL; + enumerator_t *enumerator; + id_payload_t *id_payload; + payload_t *payload; + host_t *hsi, *hsr; + bool first = TRUE; + + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + if (payload->get_type(payload) == ID_V1) + { + id_payload = (id_payload_t*)payload; + + if (first) + { + tsi = id_payload->get_ts(id_payload); + first = FALSE; + } + else + { + tsr = id_payload->get_ts(id_payload); + break; + } + } + } + enumerator->destroy(enumerator); + + /* create host2host selectors if ID payloads missing */ + if (this->initiator) + { + hsi = this->ike_sa->get_my_host(this->ike_sa); + hsr = this->ike_sa->get_other_host(this->ike_sa); + } + else + { + hsr = this->ike_sa->get_my_host(this->ike_sa); + hsi = this->ike_sa->get_other_host(this->ike_sa); + } + if (!tsi) + { + tsi = traffic_selector_create_from_subnet(hsi->clone(hsi), + hsi->get_family(hsi) == AF_INET ? 32 : 128, 0, 0); + } + if (!tsr) + { + tsr = traffic_selector_create_from_subnet(hsr->clone(hsr), + hsr->get_family(hsr) == AF_INET ? 32 : 128, 0, 0); + } + if (this->initiator) + { + /* check if peer selection valid */ + if (!tsr->is_contained_in(tsr, this->tsr) || + !tsi->is_contained_in(tsi, this->tsi)) + { + DBG1(DBG_IKE, "peer selected invalid traffic selectors: ", + "%R for %R, %R for %R", tsi, this->tsi, tsr, this->tsr); + tsi->destroy(tsi); + tsr->destroy(tsr); + return FALSE; + } + this->tsi->destroy(this->tsi); + this->tsr->destroy(this->tsr); + this->tsi = tsi; + this->tsr = tsr; + } + else + { + this->tsi = tsi; + this->tsr = tsr; + } + return TRUE; +} + +/** + * Add NAT-OA payloads + */ +static void add_nat_oa_payloads(private_quick_mode_t *this, message_t *message) +{ + identification_t *id; + id_payload_t *nat_oa; + host_t *src, *dst; + + src = message->get_source(message); + dst = message->get_destination(message); + + src = this->initiator ? src : dst; + dst = this->initiator ? dst : src; + + /* first NAT-OA is the initiator's address */ + id = identification_create_from_sockaddr(src->get_sockaddr(src)); + nat_oa = id_payload_create_from_identification(NAT_OA_V1, id); + message->add_payload(message, (payload_t*)nat_oa); + id->destroy(id); + + /* second NAT-OA is that of the responder */ + id = identification_create_from_sockaddr(dst->get_sockaddr(dst)); + nat_oa = id_payload_create_from_identification(NAT_OA_V1, id); + message->add_payload(message, (payload_t*)nat_oa); + id->destroy(id); +} + +/** + * Look up lifetimes + */ +static void get_lifetimes(private_quick_mode_t *this) +{ + lifetime_cfg_t *lft; + + lft = this->config->get_lifetime(this->config); + if (lft->time.life) + { + this->lifetime = lft->time.life; + } + else if (lft->bytes.life) + { + this->lifebytes = lft->bytes.life; + } + free(lft); +} + +/** + * Check and apply lifetimes + */ +static void apply_lifetimes(private_quick_mode_t *this, sa_payload_t *sa_payload) +{ + u_int32_t lifetime; + u_int64_t lifebytes; + + lifetime = sa_payload->get_lifetime(sa_payload); + lifebytes = sa_payload->get_lifebytes(sa_payload); + if (this->lifetime != lifetime) + { + DBG1(DBG_IKE, "received %us lifetime, configured %us, using lower", + lifetime, this->lifetime); + this->lifetime = min(this->lifetime, lifetime); + } + if (this->lifebytes != lifebytes) + { + DBG1(DBG_IKE, "received %llu lifebytes, configured %llu, using lower", + lifebytes, this->lifebytes); + this->lifebytes = min(this->lifebytes, lifebytes); + } +} + +/** + * Set the task ready to build notify error message + */ +static status_t send_notify(private_quick_mode_t *this, notify_type_t type) +{ + notify_payload_t *notify; + + notify = notify_payload_create_from_protocol_and_type(NOTIFY_V1, + PROTO_ESP, type); + notify->set_spi(notify, this->spi_i); + + this->ike_sa->queue_task(this->ike_sa, + (task_t*)informational_create(this->ike_sa, notify)); + /* cancel all active/passive tasks in favour of informational */ + return ALREADY_DONE; +} + +METHOD(task_t, build_i, status_t, + private_quick_mode_t *this, message_t *message) +{ + switch (this->state) + { + case QM_INIT: + { + enumerator_t *enumerator; + sa_payload_t *sa_payload; + linked_list_t *list; + proposal_t *proposal; + ipsec_mode_t mode; + diffie_hellman_group_t group; + bool udp = this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY); + + 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, 0, udp); + + list = this->config->get_proposals(this->config, FALSE); + + this->spi_i = this->child_sa->alloc_spi(this->child_sa, PROTO_ESP); + if (!this->spi_i) + { + DBG1(DBG_IKE, "allocating SPI from kernel failed"); + return FAILED; + } + enumerator = list->create_enumerator(list); + while (enumerator->enumerate(enumerator, &proposal)) + { + proposal->set_spi(proposal, this->spi_i); + } + enumerator->destroy(enumerator); + + mode = this->config->get_mode(this->config); + if (udp && mode == MODE_TRANSPORT) + { + /* TODO-IKEv1: disable NAT-T for TRANSPORT mode by default? */ + add_nat_oa_payloads(this, message); + } + + get_lifetimes(this); + sa_payload = sa_payload_create_from_proposals_v1(list, + this->lifetime, this->lifebytes, AUTH_NONE, + mode, udp); + list->destroy_offset(list, offsetof(proposal_t, destroy)); + message->add_payload(message, &sa_payload->payload_interface); + + if (!add_nonce(this, &this->nonce_i, message)) + { + return FAILED; + } + + group = this->config->get_dh_group(this->config); + if (group != MODP_NONE) + { + this->dh = this->keymat->keymat.create_dh(&this->keymat->keymat, + group); + if (!this->dh) + { + DBG1(DBG_IKE, "configured DH group %N not supported", + diffie_hellman_group_names, group); + return FAILED; + } + add_ke(this, message); + } + this->tsi = select_ts(this, TRUE, NULL); + this->tsr = select_ts(this, FALSE, NULL); + if (!this->tsi || !this->tsr) + { + return FAILED; + } + add_ts(this, message); + return NEED_MORE; + } + case QM_NEGOTIATED: + { + return SUCCESS; + } + default: + return FAILED; + } +} + +/** + * Check for notify errors, return TRUE if error found + */ +static bool has_notify_errors(private_quick_mode_t *this, message_t *message) +{ + enumerator_t *enumerator; + payload_t *payload; + bool err = FALSE; + + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + if (payload->get_type(payload) == NOTIFY_V1) + { + notify_payload_t *notify; + notify_type_t type; + + notify = (notify_payload_t*)payload; + type = notify->get_notify_type(notify); + if (type < 16384) + { + + DBG1(DBG_IKE, "received %N error notify", + notify_type_names, type); + err = TRUE; + } + else + { + DBG1(DBG_IKE, "received %N notify", notify_type_names, type); + } + } + } + enumerator->destroy(enumerator); + + return err; +} + +METHOD(task_t, process_r, status_t, + private_quick_mode_t *this, message_t *message) +{ + switch (this->state) + { + case QM_INIT: + { + sa_payload_t *sa_payload; + linked_list_t *tsi, *tsr, *list; + peer_cfg_t *peer_cfg; + host_t *me, *other; + u_int16_t group; + bool udp = this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY); + + if (!get_ts(this, message)) + { + return FAILED; + } + me = this->ike_sa->get_virtual_ip(this->ike_sa, TRUE); + if (!me) + { + me = this->ike_sa->get_my_host(this->ike_sa); + } + other = this->ike_sa->get_virtual_ip(this->ike_sa, FALSE); + if (!other) + { + other = this->ike_sa->get_other_host(this->ike_sa); + } + peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); + tsi = linked_list_create(); + tsr = linked_list_create(); + tsi->insert_last(tsi, this->tsi); + tsr->insert_last(tsr, this->tsr); + this->tsi = this->tsr = NULL; + this->config = peer_cfg->select_child_cfg(peer_cfg, tsr, tsi, + me, other); + this->tsi = select_ts(this, FALSE, tsi); + this->tsr = select_ts(this, TRUE, tsr); + tsi->destroy_offset(tsi, offsetof(traffic_selector_t, destroy)); + tsr->destroy_offset(tsr, offsetof(traffic_selector_t, destroy)); + if (!this->config) + { + DBG1(DBG_IKE, "no child config found"); + return send_notify(this, NO_PROPOSAL_CHOSEN); + } + + sa_payload = (sa_payload_t*)message->get_payload(message, + SECURITY_ASSOCIATION_V1); + if (!sa_payload) + { + DBG1(DBG_IKE, "sa payload missing"); + return send_notify(this, INVALID_PAYLOAD_TYPE); + } + list = sa_payload->get_proposals(sa_payload); + this->proposal = this->config->select_proposal(this->config, + list, FALSE, FALSE); + list->destroy_offset(list, offsetof(proposal_t, destroy)); + + get_lifetimes(this); + apply_lifetimes(this, sa_payload); + + if (!this->proposal) + { + DBG1(DBG_IKE, "no matching proposal found, sending %N", + notify_type_names, NO_PROPOSAL_CHOSEN); + return send_notify(this, NO_PROPOSAL_CHOSEN); + } + this->spi_i = this->proposal->get_spi(this->proposal); + + if (!get_nonce(this, &this->nonce_i, message)) + { + return send_notify(this, INVALID_PAYLOAD_TYPE); + } + + if (this->proposal->get_algorithm(this->proposal, + DIFFIE_HELLMAN_GROUP, &group, NULL)) + { + this->dh = this->keymat->keymat.create_dh(&this->keymat->keymat, + group); + if (!this->dh) + { + DBG1(DBG_IKE, "negotiated DH group %N not supported", + diffie_hellman_group_names, group); + return send_notify(this, INVALID_KEY_INFORMATION); + } + if (!get_ke(this, message)) + { + return send_notify(this, INVALID_PAYLOAD_TYPE); + } + } + + 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, 0, udp); + return NEED_MORE; + } + case QM_NEGOTIATED: + { + if (has_notify_errors(this, message)) + { + return SUCCESS; + } + if (!install(this)) + { + this->ike_sa->queue_task(this->ike_sa, + (task_t*)quick_delete_create(this->ike_sa, + this->proposal->get_protocol(this->proposal), + this->spi_i, TRUE)); + return ALREADY_DONE; + } + return SUCCESS; + } + default: + return FAILED; + } +} + +METHOD(task_t, build_r, status_t, + private_quick_mode_t *this, message_t *message) +{ + switch (this->state) + { + case QM_INIT: + { + sa_payload_t *sa_payload; + ipsec_mode_t mode; + bool udp = this->child_sa->has_encap(this->child_sa); + + this->spi_r = this->child_sa->alloc_spi(this->child_sa, PROTO_ESP); + if (!this->spi_r) + { + DBG1(DBG_IKE, "allocating SPI from kernel failed"); + return send_notify(this, NO_PROPOSAL_CHOSEN); + } + this->proposal->set_spi(this->proposal, this->spi_r); + + mode = this->config->get_mode(this->config); + if (udp && mode == MODE_TRANSPORT) + { + /* TODO-IKEv1: disable NAT-T for TRANSPORT mode by default? */ + add_nat_oa_payloads(this, message); + } + + sa_payload = sa_payload_create_from_proposal_v1(this->proposal, + this->lifetime, this->lifebytes, AUTH_NONE, + mode, udp); + message->add_payload(message, &sa_payload->payload_interface); + + if (!add_nonce(this, &this->nonce_r, message)) + { + return FAILED; + } + if (this->dh) + { + add_ke(this, message); + } + + add_ts(this, message); + + this->state = QM_NEGOTIATED; + return NEED_MORE; + } + default: + return FAILED; + } +} + +METHOD(task_t, process_i, status_t, + private_quick_mode_t *this, message_t *message) +{ + switch (this->state) + { + case QM_INIT: + { + sa_payload_t *sa_payload; + linked_list_t *list; + + sa_payload = (sa_payload_t*)message->get_payload(message, + SECURITY_ASSOCIATION_V1); + if (!sa_payload) + { + DBG1(DBG_IKE, "sa payload missing"); + return send_notify(this, NO_PROPOSAL_CHOSEN); + } + list = sa_payload->get_proposals(sa_payload); + this->proposal = this->config->select_proposal(this->config, + list, FALSE, FALSE); + list->destroy_offset(list, offsetof(proposal_t, destroy)); + if (!this->proposal) + { + DBG1(DBG_IKE, "no matching proposal found"); + return send_notify(this, NO_PROPOSAL_CHOSEN); + } + this->spi_r = this->proposal->get_spi(this->proposal); + + apply_lifetimes(this, sa_payload); + + if (!get_nonce(this, &this->nonce_r, message)) + { + return send_notify(this, INVALID_PAYLOAD_TYPE); + } + if (this->dh && !get_ke(this, message)) + { + return send_notify(this, INVALID_KEY_INFORMATION); + } + if (!get_ts(this, message)) + { + return send_notify(this, INVALID_PAYLOAD_TYPE); + } + if (!install(this)) + { + return send_notify(this, NO_PROPOSAL_CHOSEN); + } + this->state = QM_NEGOTIATED; + return NEED_MORE; + } + default: + return FAILED; + } +} + +METHOD(task_t, get_type, task_type_t, + private_quick_mode_t *this) +{ + return TASK_QUICK_MODE; +} + +METHOD(task_t, migrate, void, + private_quick_mode_t *this, ike_sa_t *ike_sa) +{ + this->ike_sa = ike_sa; +} + +METHOD(task_t, destroy, void, + private_quick_mode_t *this) +{ + chunk_free(&this->nonce_i); + chunk_free(&this->nonce_r); + DESTROY_IF(this->tsi); + DESTROY_IF(this->tsr); + DESTROY_IF(this->proposal); + DESTROY_IF(this->child_sa); + DESTROY_IF(this->config); + DESTROY_IF(this->dh); + free(this); +} + +/* + * Described in header. + */ +quick_mode_t *quick_mode_create(ike_sa_t *ike_sa, child_cfg_t *config, + traffic_selector_t *tsi, traffic_selector_t *tsr) +{ + private_quick_mode_t *this; + + INIT(this, + .public = { + .task = { + .get_type = _get_type, + .migrate = _migrate, + .destroy = _destroy, + }, + }, + .ike_sa = ike_sa, + .initiator = config != NULL, + .config = config, + .keymat = (keymat_v1_t*)ike_sa->get_keymat(ike_sa), + .state = QM_INIT, + ); + + if (config) + { + this->public.task.build = _build_i; + this->public.task.process = _process_i; + } + else + { + this->public.task.build = _build_r; + this->public.task.process = _process_r; + } + + return &this->public; +} diff --git a/src/libcharon/sa/ikev1/tasks/quick_mode.h b/src/libcharon/sa/ikev1/tasks/quick_mode.h new file mode 100644 index 000000000..82790c768 --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/quick_mode.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup quick_mode quick_mode + * @{ @ingroup tasks + */ + +#ifndef QUICK_MODE_H_ +#define QUICK_MODE_H_ + +typedef struct quick_mode_t quick_mode_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/task.h> + +/** + * IKEv1 quick mode, establishes a CHILD_SA in IKEv1. + */ +struct quick_mode_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * Create a new quick_mode task. + * + * @param ike_sa IKE_SA this task works for + * @param config child_cfg if task initiator, NULL if responder + * @param tsi source of triggering packet, or NULL + * @param tsr destination of triggering packet, or NULL + * @return task to handle by the task_manager + */ +quick_mode_t *quick_mode_create(ike_sa_t *ike_sa, child_cfg_t *config, + traffic_selector_t *tsi, traffic_selector_t *tsr); + +#endif /** QUICK_MODE_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/tasks/xauth.c b/src/libcharon/sa/ikev1/tasks/xauth.c new file mode 100644 index 000000000..b4d690094 --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/xauth.c @@ -0,0 +1,399 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "xauth.h" + +#include <daemon.h> +#include <hydra.h> +#include <encoding/payloads/cp_payload.h> + +typedef struct private_xauth_t private_xauth_t; + +/** + * Status types exchanged + */ +typedef enum { + XAUTH_FAILED = 0, + XAUTH_OK = 1, +} xauth_status_t; + +/** + * Private members of a xauth_t task. + */ +struct private_xauth_t { + + /** + * Public methods and task_t interface. + */ + xauth_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; + + /** + * Are we the XAUTH initiator? + */ + bool initiator; + + /** + * XAuth backend to use + */ + xauth_method_t *xauth; + + /** + * XAuth username + */ + identification_t *user; + + /** + * Generated configuration payload + */ + cp_payload_t *cp; + + /** + * status of Xauth exchange + */ + xauth_status_t status; +}; + +/** + * Load XAuth backend + */ +static xauth_method_t *load_method(private_xauth_t* this) +{ + identification_t *server, *peer; + enumerator_t *enumerator; + xauth_method_t *xauth; + xauth_role_t role; + peer_cfg_t *peer_cfg; + auth_cfg_t *auth; + char *name; + + if (this->initiator) + { + server = this->ike_sa->get_my_id(this->ike_sa); + peer = this->ike_sa->get_other_id(this->ike_sa); + role = XAUTH_SERVER; + } + else + { + peer = this->ike_sa->get_my_id(this->ike_sa); + server = this->ike_sa->get_other_id(this->ike_sa); + role = XAUTH_PEER; + } + peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); + enumerator = peer_cfg->create_auth_cfg_enumerator(peer_cfg, !this->initiator); + if (!enumerator->enumerate(enumerator, &auth) || + (uintptr_t)auth->get(auth, AUTH_RULE_AUTH_CLASS) != AUTH_CLASS_XAUTH) + { + if (!enumerator->enumerate(enumerator, &auth) || + (uintptr_t)auth->get(auth, AUTH_RULE_AUTH_CLASS) != AUTH_CLASS_XAUTH) + { + DBG1(DBG_CFG, "no XAuth authentication round found"); + enumerator->destroy(enumerator); + return NULL; + } + } + name = auth->get(auth, AUTH_RULE_XAUTH_BACKEND); + this->user = auth->get(auth, AUTH_RULE_XAUTH_IDENTITY); + if (!this->initiator && this->user) + { /* use XAUTH username, if configured */ + peer = this->user; + } + enumerator->destroy(enumerator); + xauth = charon->xauth->create_instance(charon->xauth, name, role, + server, peer); + if (!xauth) + { + if (name) + { + DBG1(DBG_CFG, "no XAuth method found named '%s'"); + } + else + { + DBG1(DBG_CFG, "no XAuth method found"); + } + } + return xauth; +} + +/** + * Set IKE_SA to established state + */ +static void establish(private_xauth_t *this) +{ + DBG0(DBG_IKE, "IKE_SA %s[%d] established between %H[%Y]...%H[%Y]", + this->ike_sa->get_name(this->ike_sa), + this->ike_sa->get_unique_id(this->ike_sa), + this->ike_sa->get_my_host(this->ike_sa), + this->ike_sa->get_my_id(this->ike_sa), + this->ike_sa->get_other_host(this->ike_sa), + this->ike_sa->get_other_id(this->ike_sa)); + + this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED); + charon->bus->ike_updown(charon->bus, this->ike_sa, TRUE); +} + +METHOD(task_t, build_i_status, status_t, + private_xauth_t *this, message_t *message) +{ + cp_payload_t *cp; + + cp = cp_payload_create_type(CONFIGURATION_V1, CFG_SET); + cp->add_attribute(cp, + configuration_attribute_create_value(XAUTH_STATUS, this->status)); + + message->add_payload(message, (payload_t *)cp); + + return NEED_MORE; +} + +METHOD(task_t, build_i, status_t, + private_xauth_t *this, message_t *message) +{ + if (!this->xauth) + { + cp_payload_t *cp; + + this->xauth = load_method(this); + if (!this->xauth) + { + return FAILED; + } + if (this->xauth->initiate(this->xauth, &cp) != NEED_MORE) + { + return FAILED; + } + message->add_payload(message, (payload_t *)cp); + return NEED_MORE; + } + + if (this->cp) + { /* send previously generated payload */ + message->add_payload(message, (payload_t *)this->cp); + this->cp = NULL; + return NEED_MORE; + } + return FAILED; +} + +METHOD(task_t, build_r_ack, status_t, + private_xauth_t *this, message_t *message) +{ + cp_payload_t *cp; + + cp = cp_payload_create_type(CONFIGURATION_V1, CFG_ACK); + cp->add_attribute(cp, + configuration_attribute_create_chunk( + CONFIGURATION_ATTRIBUTE_V1, XAUTH_STATUS, chunk_empty)); + + message->add_payload(message, (payload_t *)cp); + + if (this->status == XAUTH_OK) + { + establish(this); + return SUCCESS; + } + return FAILED; +} + +METHOD(task_t, process_r, status_t, + private_xauth_t *this, message_t *message) +{ + cp_payload_t *cp; + + if (!this->xauth) + { + this->xauth = load_method(this); + if (!this->xauth) + { /* send empty reply */ + return NEED_MORE; + } + } + cp = (cp_payload_t*)message->get_payload(message, CONFIGURATION_V1); + if (!cp) + { + DBG1(DBG_IKE, "configuration payload missing in XAuth request"); + return FAILED; + } + if (cp->get_type(cp) == CFG_REQUEST) + { + switch (this->xauth->process(this->xauth, cp, &this->cp)) + { + case NEED_MORE: + return NEED_MORE; + case SUCCESS: + case FAILED: + default: + break; + } + this->cp = NULL; + return NEED_MORE; + } + if (cp->get_type(cp) == CFG_SET) + { + configuration_attribute_t *attribute; + enumerator_t *enumerator; + + enumerator = cp->create_attribute_enumerator(cp); + while (enumerator->enumerate(enumerator, &attribute)) + { + if (attribute->get_type(attribute) == XAUTH_STATUS) + { + this->status = attribute->get_value(attribute); + } + } + enumerator->destroy(enumerator); + if (this->status == XAUTH_OK) + { + DBG1(DBG_IKE, "XAuth authentication of '%Y' (myself) successful", + this->xauth->get_identity(this->xauth)); + establish(this); + } + else + { + DBG1(DBG_IKE, "XAuth authentication of '%Y' (myself) failed", + this->xauth->get_identity(this->xauth)); + } + } + this->public.task.build = _build_r_ack; + return NEED_MORE; +} + +METHOD(task_t, build_r, status_t, + private_xauth_t *this, message_t *message) +{ + if (!this->cp) + { /* send empty reply if building data failed */ + this->cp = cp_payload_create_type(CONFIGURATION_V1, CFG_REPLY); + } + message->add_payload(message, (payload_t *)this->cp); + this->cp = NULL; + return NEED_MORE; +} + +METHOD(task_t, process_i_status, status_t, + private_xauth_t *this, message_t *message) +{ + cp_payload_t *cp; + + cp = (cp_payload_t*)message->get_payload(message, CONFIGURATION_V1); + if (!cp || cp->get_type(cp) != CFG_ACK) + { + DBG1(DBG_IKE, "received invalid XAUTH status response"); + return FAILED; + } + if (this->status != XAUTH_OK) + { + DBG1(DBG_IKE, "destroying IKE_SA after failed XAuth authentication"); + return FAILED; + } + establish(this); + return SUCCESS; +} + +METHOD(task_t, process_i, status_t, + private_xauth_t *this, message_t *message) +{ + identification_t *id; + cp_payload_t *cp; + + cp = (cp_payload_t*)message->get_payload(message, CONFIGURATION_V1); + if (!cp) + { + DBG1(DBG_IKE, "configuration payload missing in XAuth response"); + return FAILED; + } + switch (this->xauth->process(this->xauth, cp, &this->cp)) + { + case NEED_MORE: + return NEED_MORE; + case SUCCESS: + id = this->xauth->get_identity(this->xauth); + if (this->user && !id->matches(id, this->user)) + { + DBG1(DBG_IKE, "XAuth username '%Y' does not match to " + "configured username '%Y'", id, this->user); + break; + } + DBG1(DBG_IKE, "XAuth authentication of '%Y' successful", id); + this->status = XAUTH_OK; + break; + case FAILED: + DBG1(DBG_IKE, "XAuth authentication of '%Y' failed", + this->xauth->get_identity(this->xauth)); + break; + default: + return FAILED; + } + this->public.task.build = _build_i_status; + this->public.task.process = _process_i_status; + return NEED_MORE; +} + +METHOD(task_t, get_type, task_type_t, + private_xauth_t *this) +{ + return TASK_XAUTH; +} + +METHOD(task_t, migrate, void, + private_xauth_t *this, ike_sa_t *ike_sa) +{ + this->ike_sa = ike_sa; +} + +METHOD(task_t, destroy, void, + private_xauth_t *this) +{ + DESTROY_IF(this->xauth); + DESTROY_IF(this->cp); + free(this); +} + +/* + * Described in header. + */ +xauth_t *xauth_create(ike_sa_t *ike_sa, bool initiator) +{ + private_xauth_t *this; + + INIT(this, + .public = { + .task = { + .get_type = _get_type, + .migrate = _migrate, + .destroy = _destroy, + }, + }, + .initiator = initiator, + .ike_sa = ike_sa, + .status = XAUTH_FAILED, + ); + + if (initiator) + { + this->public.task.build = _build_i; + this->public.task.process = _process_i; + } + else + { + this->public.task.build = _build_r; + this->public.task.process = _process_r; + } + return &this->public; +} diff --git a/src/libcharon/sa/ikev1/tasks/xauth.h b/src/libcharon/sa/ikev1/tasks/xauth.h new file mode 100644 index 000000000..c1528ccbe --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/xauth.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup xauth xauth + * @{ @ingroup tasks + */ + +#ifndef XAUTH_H_ +#define XAUTH_H_ + +typedef struct xauth_t xauth_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/task.h> + +/** + * Task of type TASK_XAUTH, additional authentication after main/aggressive mode. + */ +struct xauth_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * Create a new xauth task. + * + * @param ike_sa IKE_SA this task works for + * @param initiator TRUE for initiator + * @return xauth task to handle by the task_manager + */ +xauth_t *xauth_create(ike_sa_t *ike_sa, bool initiator); + +#endif /** XAUTH_H_ @}*/ |