diff options
author | Rene Mayrhofer <rene@mayrhofer.eu.org> | 2010-05-25 19:01:36 +0000 |
---|---|---|
committer | Rene Mayrhofer <rene@mayrhofer.eu.org> | 2010-05-25 19:01:36 +0000 |
commit | 1ac70afcc1f7d6d2738a34308810719b0976d29f (patch) | |
tree | 805f6ce2a15d1a717781d7cbceac8408a74b6b0c /src/libcharon/sa | |
parent | ed7d79f96177044949744da10f4431c1d6242241 (diff) | |
download | vyos-strongswan-1ac70afcc1f7d6d2738a34308810719b0976d29f.tar.gz vyos-strongswan-1ac70afcc1f7d6d2738a34308810719b0976d29f.zip |
[svn-upgrade] Integrating new upstream version, strongswan (4.4.0)
Diffstat (limited to 'src/libcharon/sa')
68 files changed, 24296 insertions, 0 deletions
diff --git a/src/libcharon/sa/authenticators/authenticator.c b/src/libcharon/sa/authenticators/authenticator.c new file mode 100644 index 000000000..13586a23e --- /dev/null +++ b/src/libcharon/sa/authenticators/authenticator.c @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2006-2009 Martin Willi + * Copyright (C) 2008 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 <string.h> + +#include "authenticator.h" + +#include <sa/authenticators/pubkey_authenticator.h> +#include <sa/authenticators/psk_authenticator.h> +#include <sa/authenticators/eap_authenticator.h> +#include <encoding/payloads/auth_payload.h> + + +ENUM_BEGIN(auth_method_names, AUTH_RSA, AUTH_DSS, + "RSA signature", + "pre-shared key", + "DSS signature"); +ENUM_NEXT(auth_method_names, AUTH_ECDSA_256, AUTH_ECDSA_521, AUTH_DSS, + "ECDSA-256 signature", + "ECDSA-384 signature", + "ECDSA-521 signature"); +ENUM_END(auth_method_names, AUTH_ECDSA_521); + +ENUM(auth_class_names, AUTH_CLASS_ANY, AUTH_CLASS_EAP, + "any", + "public key", + "pre-shared key", + "EAP", +); + +/** + * Described in header. + */ +authenticator_t *authenticator_create_builder(ike_sa_t *ike_sa, auth_cfg_t *cfg, + chunk_t received_nonce, chunk_t sent_nonce, + chunk_t received_init, chunk_t sent_init) +{ + switch ((uintptr_t)cfg->get(cfg, AUTH_RULE_AUTH_CLASS)) + { + case AUTH_CLASS_ANY: + /* defaults to PUBKEY */ + case AUTH_CLASS_PUBKEY: + return (authenticator_t*)pubkey_authenticator_create_builder(ike_sa, + received_nonce, sent_init); + case AUTH_CLASS_PSK: + return (authenticator_t*)psk_authenticator_create_builder(ike_sa, + received_nonce, sent_init); + case AUTH_CLASS_EAP: + return (authenticator_t*)eap_authenticator_create_builder(ike_sa, + received_nonce, sent_nonce, received_init, sent_init); + default: + return NULL; + } +} + +/** + * Described in header. + */ +authenticator_t *authenticator_create_verifier( + ike_sa_t *ike_sa, message_t *message, + chunk_t received_nonce, chunk_t sent_nonce, + chunk_t received_init, chunk_t sent_init) +{ + auth_payload_t *auth_payload; + + auth_payload = (auth_payload_t*)message->get_payload(message, AUTHENTICATION); + if (auth_payload == NULL) + { + return (authenticator_t*)eap_authenticator_create_verifier(ike_sa, + received_nonce, sent_nonce, received_init, sent_init); + } + switch (auth_payload->get_auth_method(auth_payload)) + { + case AUTH_RSA: + case AUTH_ECDSA_256: + case AUTH_ECDSA_384: + case AUTH_ECDSA_521: + return (authenticator_t*)pubkey_authenticator_create_verifier(ike_sa, + sent_nonce, received_init); + case AUTH_PSK: + return (authenticator_t*)psk_authenticator_create_verifier(ike_sa, + sent_nonce, received_init); + default: + return NULL; + } +} + diff --git a/src/libcharon/sa/authenticators/authenticator.h b/src/libcharon/sa/authenticators/authenticator.h new file mode 100644 index 000000000..fff91ed34 --- /dev/null +++ b/src/libcharon/sa/authenticators/authenticator.h @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2005-2009 Martin Willi + * Copyright (C) 2008 Tobias Brunner + * Copyright (C) 2005 Jan Hutter + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup authenticator authenticator + * @{ @ingroup authenticators + */ + +#ifndef AUTHENTICATOR_H_ +#define AUTHENTICATOR_H_ + +typedef enum auth_method_t auth_method_t; +typedef enum auth_class_t auth_class_t; +typedef struct authenticator_t authenticator_t; + +#include <library.h> +#include <config/auth_cfg.h> +#include <sa/ike_sa.h> + +/** + * Method to use for authentication, as defined in IKEv2. + */ +enum auth_method_t { + /** + * Computed as specified in section 2.15 of RFC using + * an RSA private key over a PKCS#1 padded hash. + */ + AUTH_RSA = 1, + + /** + * Computed as specified in section 2.15 of RFC using the + * shared key associated with the identity in the ID payload + * and the negotiated prf function + */ + AUTH_PSK = 2, + + /** + * Computed as specified in section 2.15 of RFC using a + * DSS private key over a SHA-1 hash. + */ + AUTH_DSS = 3, + + /** + * ECDSA with SHA-256 on the P-256 curve as specified in RFC 4754 + */ + AUTH_ECDSA_256 = 9, + + /** + * ECDSA with SHA-384 on the P-384 curve as specified in RFC 4754 + */ + AUTH_ECDSA_384 = 10, + + /** + * ECDSA with SHA-512 on the P-521 curve as specified in RFC 4754 + */ + AUTH_ECDSA_521 = 11, +}; + +/** + * enum names for auth_method_t. + */ +extern enum_name_t *auth_method_names; + +/** + * Class of authentication to use. This is different to auth_method_t in that + * it does not specify a method, but a class of acceptable methods. The found + * certificate finally dictates wich method is used. + */ +enum auth_class_t { + /** any class acceptable */ + AUTH_CLASS_ANY = 0, + /** authentication using public keys (RSA, ECDSA) */ + AUTH_CLASS_PUBKEY = 1, + /** authentication using a pre-shared secrets */ + AUTH_CLASS_PSK = 2, + /** authentication using EAP */ + AUTH_CLASS_EAP = 3, +}; + +/** + * enum strings for auth_class_t + */ +extern enum_name_t *auth_class_names; + +/** + * Authenticator interface implemented by the various authenticators. + * + * An authenticator implementation handles AUTH and EAP payloads. Received + * messages are passed to the process() method, to send authentication data + * the message is passed to the build() method. + */ +struct authenticator_t { + + /** + * Process an incoming message using the authenticator. + * + * @param message message containing authentication payloads + * @return + * - SUCCESS if authentication successful + * - FAILED if authentication failed + * - NEED_MORE if another exchange required + */ + status_t (*process)(authenticator_t *this, message_t *message); + + /** + * Attach authentication data to an outgoing message. + * + * @param message message to add authentication data to + * @return + * - SUCCESS if authentication successful + * - FAILED if authentication failed + * - NEED_MORE if another exchange required + */ + status_t (*build)(authenticator_t *this, message_t *message); + + /** + * Check if the authenticator is capable of mutual authentication. + * + * Some authenticator authenticate both peers, e.g. EAP. To support + * mutual authentication with only a single authenticator (EAP-only + * authentication), it must be mutual. This method is invoked in ike_auth + * to check if the given authenticator is capable of doing so. + */ + bool (*is_mutual)(authenticator_t *this); + + /** + * Destroy authenticator instance. + */ + void (*destroy) (authenticator_t *this); +}; + +/** + * Create an authenticator to build signatures. + * + * @param ike_sa associated ike_sa + * @param cfg authentication configuration + * @param received_nonce nonce received in IKE_SA_INIT + * @param sent_nonce nonce sent in IKE_SA_INIT + * @param received_init received IKE_SA_INIT message data + * @param sent_init sent IKE_SA_INIT message data + * @return authenticator, NULL if not supported + */ +authenticator_t *authenticator_create_builder( + ike_sa_t *ike_sa, auth_cfg_t *cfg, + chunk_t received_nonce, chunk_t sent_nonce, + chunk_t received_init, chunk_t sent_init); + +/** + * Create an authenticator to verify signatures. + * + * @param ike_sa associated ike_sa + * @param message message containing authentication data + * @param received_nonce nonce received in IKE_SA_INIT + * @param sent_nonce nonce sent in IKE_SA_INIT + * @param received_init received IKE_SA_INIT message data + * @param sent_init sent IKE_SA_INIT message data + * @return authenticator, NULL if not supported + */ +authenticator_t *authenticator_create_verifier( + ike_sa_t *ike_sa, message_t *message, + chunk_t received_nonce, chunk_t sent_nonce, + chunk_t received_init, chunk_t sent_init); + +#endif /** AUTHENTICATOR_H_ @}*/ diff --git a/src/libcharon/sa/authenticators/eap/eap_manager.c b/src/libcharon/sa/authenticators/eap/eap_manager.c new file mode 100644 index 000000000..f795183f0 --- /dev/null +++ b/src/libcharon/sa/authenticators/eap/eap_manager.c @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2008 Martin Willi + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "eap_manager.h" + +#include <utils/linked_list.h> +#include <threading/rwlock.h> + +typedef struct private_eap_manager_t private_eap_manager_t; +typedef struct eap_entry_t eap_entry_t; + +/** + * EAP constructor entry + */ +struct eap_entry_t { + + /** + * EAP method type, vendor specific if vendor is set + */ + eap_type_t type; + + /** + * vendor ID, 0 for default EAP methods + */ + u_int32_t vendor; + + /** + * Role of the method returned by the constructor, EAP_SERVER or EAP_PEER + */ + eap_role_t role; + + /** + * constructor function to create instance + */ + eap_constructor_t constructor; +}; + +/** + * private data of eap_manager + */ +struct private_eap_manager_t { + + /** + * public functions + */ + eap_manager_t public; + + /** + * list of eap_entry_t's + */ + linked_list_t *methods; + + /** + * rwlock to lock methods + */ + rwlock_t *lock; +}; + +/** + * Implementation of eap_manager_t.add_method. + */ +static void add_method(private_eap_manager_t *this, eap_type_t type, + u_int32_t vendor, eap_role_t role, + eap_constructor_t constructor) +{ + eap_entry_t *entry = malloc_thing(eap_entry_t); + + entry->type = type; + entry->vendor = vendor; + entry->role = role; + entry->constructor = constructor; + + this->lock->write_lock(this->lock); + this->methods->insert_last(this->methods, entry); + this->lock->unlock(this->lock); +} + +/** + * Implementation of eap_manager_t.remove_method. + */ +static void remove_method(private_eap_manager_t *this, eap_constructor_t constructor) +{ + enumerator_t *enumerator; + eap_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); +} + +/** + * Implementation of eap_manager_t.create_instance. + */ +static eap_method_t* create_instance(private_eap_manager_t *this, + eap_type_t type, u_int32_t vendor, + eap_role_t role, identification_t *server, + identification_t *peer) +{ + enumerator_t *enumerator; + eap_entry_t *entry; + eap_method_t *method = NULL; + + this->lock->read_lock(this->lock); + enumerator = this->methods->create_enumerator(this->methods); + while (enumerator->enumerate(enumerator, &entry)) + { + if (type == entry->type && vendor == entry->vendor && + role == entry->role) + { + method = entry->constructor(server, peer); + if (method) + { + break; + } + } + } + enumerator->destroy(enumerator); + this->lock->unlock(this->lock); + return method; +} + +/** + * Implementation of 2008_t.destroy + */ +static void destroy(private_eap_manager_t *this) +{ + this->methods->destroy_function(this->methods, free); + this->lock->destroy(this->lock); + free(this); +} + +/* + * see header file + */ +eap_manager_t *eap_manager_create() +{ + private_eap_manager_t *this = malloc_thing(private_eap_manager_t); + + this->public.add_method = (void(*)(eap_manager_t*, eap_type_t type, u_int32_t vendor, eap_role_t role, eap_constructor_t constructor))add_method; + this->public.remove_method = (void(*)(eap_manager_t*, eap_constructor_t constructor))remove_method; + this->public.create_instance = (eap_method_t*(*)(eap_manager_t*, eap_type_t type, u_int32_t vendor, eap_role_t role, identification_t*,identification_t*))create_instance; + this->public.destroy = (void(*)(eap_manager_t*))destroy; + + this->methods = linked_list_create(); + this->lock = rwlock_create(RWLOCK_TYPE_DEFAULT); + + return &this->public; +} + diff --git a/src/libcharon/sa/authenticators/eap/eap_manager.h b/src/libcharon/sa/authenticators/eap/eap_manager.h new file mode 100644 index 000000000..0333fb6da --- /dev/null +++ b/src/libcharon/sa/authenticators/eap/eap_manager.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2008 Martin Willi + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup eap_manager eap_manager + * @{ @ingroup eap + */ + +#ifndef EAP_MANAGER_H_ +#define EAP_MANAGER_H_ + +#include <sa/authenticators/eap/eap_method.h> + +typedef struct eap_manager_t eap_manager_t; + +/** + * The EAP manager manages all EAP implementations and creates instances. + * + * A plugin registers it's implemented EAP method at the manager by + * providing type and a contructor function. The manager then instanciates + * eap_method_t instances through the provided constructor to handle + * EAP authentication. + */ +struct eap_manager_t { + + /** + * Register a EAP method implementation. + * + * @param method vendor specific method, if vendor != 0 + * @param vendor vendor ID, 0 for non-vendor (default) EAP methods + * @param role EAP role of the registered method + * @param constructor constructor function, returns an eap_method_t + */ + void (*add_method)(eap_manager_t *this, eap_type_t type, u_int32_t vendor, + eap_role_t role, eap_constructor_t constructor); + + /** + * Unregister a EAP method implementation using it's constructor. + * + * @param constructor constructor function to remove, as added in add_method + */ + void (*remove_method)(eap_manager_t *this, eap_constructor_t constructor); + + /** + * Create a new EAP method instance. + * + * @param type type of the EAP method + * @param vendor vendor ID, 0 for non-vendor (default) EAP methods + * @param role role of EAP method, either EAP_SERVER or EAP_PEER + * @param server identity of the server + * @param peer identity of the peer (client) + * @return EAP method instance, NULL if no constructor found + */ + eap_method_t* (*create_instance)(eap_manager_t *this, eap_type_t type, + u_int32_t vendor, eap_role_t role, + identification_t *server, + identification_t *peer); + + /** + * Destroy a eap_manager instance. + */ + void (*destroy)(eap_manager_t *this); +}; + +/** + * Create a eap_manager instance. + */ +eap_manager_t *eap_manager_create(); + +#endif /** EAP_MANAGER_H_ @}*/ diff --git a/src/libcharon/sa/authenticators/eap/eap_method.c b/src/libcharon/sa/authenticators/eap/eap_method.c new file mode 100644 index 000000000..91fa5305f --- /dev/null +++ b/src/libcharon/sa/authenticators/eap/eap_method.c @@ -0,0 +1,107 @@ +/* + * 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 "eap_method.h" + +ENUM_BEGIN(eap_type_names, EAP_IDENTITY, EAP_GTC, + "EAP_IDENTITY", + "EAP_NOTIFICATION", + "EAP_NAK", + "EAP_MD5", + "EAP_OTP", + "EAP_GTC"); +ENUM_NEXT(eap_type_names, EAP_SIM, EAP_SIM, EAP_GTC, + "EAP_SIM"); +ENUM_NEXT(eap_type_names, EAP_AKA, EAP_AKA, EAP_SIM, + "EAP_AKA"); +ENUM_NEXT(eap_type_names, EAP_MSCHAPV2, EAP_MSCHAPV2, EAP_AKA, + "EAP_MSCHAPV2"); +ENUM_NEXT(eap_type_names, EAP_RADIUS, EAP_EXPERIMENTAL, EAP_MSCHAPV2, + "EAP_RADIUS", + "EAP_EXPANDED", + "EAP_EXPERIMENTAL"); +ENUM_END(eap_type_names, EAP_EXPERIMENTAL); + +ENUM_BEGIN(eap_type_short_names, EAP_IDENTITY, EAP_GTC, + "ID", + "NTF", + "NAK", + "MD5", + "OTP", + "GTC"); +ENUM_NEXT(eap_type_short_names, EAP_SIM, EAP_SIM, EAP_GTC, + "SIM"); +ENUM_NEXT(eap_type_short_names, EAP_AKA, EAP_AKA, EAP_SIM, + "AKA"); +ENUM_NEXT(eap_type_short_names, EAP_MSCHAPV2, EAP_MSCHAPV2, EAP_AKA, + "MSCHAPV2"); +ENUM_NEXT(eap_type_short_names, EAP_RADIUS, EAP_EXPERIMENTAL, EAP_MSCHAPV2, + "RAD", + "EXP", + "XP"); +ENUM_END(eap_type_short_names, EAP_EXPERIMENTAL); + +/* + * See header + */ +eap_type_t eap_type_from_string(char *name) +{ + int i; + static struct { + char *name; + eap_type_t type; + } types[] = { + {"identity", EAP_IDENTITY}, + {"md5", EAP_MD5}, + {"otp", EAP_OTP}, + {"gtc", EAP_GTC}, + {"sim", EAP_SIM}, + {"aka", EAP_AKA}, + {"mschapv2", EAP_MSCHAPV2}, + {"radius", EAP_RADIUS}, + }; + + for (i = 0; i < countof(types); i++) + { + if (strcaseeq(name, types[i].name)) + { + return types[i].type; + } + } + return 0; +} + +ENUM(eap_code_names, EAP_REQUEST, EAP_FAILURE, + "EAP_REQUEST", + "EAP_RESPONSE", + "EAP_SUCCESS", + "EAP_FAILURE", +); + +ENUM(eap_code_short_names, EAP_REQUEST, EAP_FAILURE, + "REQ", + "RES", + "SUCC", + "FAIL", +); + +ENUM(eap_role_names, EAP_SERVER, EAP_PEER, + "EAP_SERVER", + "EAP_PEER", +); + + + + diff --git a/src/libcharon/sa/authenticators/eap/eap_method.h b/src/libcharon/sa/authenticators/eap/eap_method.h new file mode 100644 index 000000000..4cab84535 --- /dev/null +++ b/src/libcharon/sa/authenticators/eap/eap_method.h @@ -0,0 +1,205 @@ +/* + * 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 eap_method eap_method + * @{ @ingroup eap + */ + +#ifndef EAP_METHOD_H_ +#define EAP_METHOD_H_ + +typedef struct eap_method_t eap_method_t; +typedef enum eap_role_t eap_role_t; +typedef enum eap_type_t eap_type_t; +typedef enum eap_code_t eap_code_t; + +#include <library.h> +#include <utils/identification.h> +#include <encoding/payloads/eap_payload.h> + +/** + * Role of an eap_method, SERVER or PEER (client) + */ +enum eap_role_t { + EAP_SERVER, + EAP_PEER, +}; +/** + * enum names for eap_role_t. + */ +extern enum_name_t *eap_role_names; + +/** + * EAP types, defines the EAP method implementation + */ +enum eap_type_t { + EAP_IDENTITY = 1, + EAP_NOTIFICATION = 2, + EAP_NAK = 3, + EAP_MD5 = 4, + EAP_OTP = 5, + EAP_GTC = 6, + EAP_SIM = 18, + EAP_AKA = 23, + EAP_MSCHAPV2 = 26, + /** not a method, but an implementation providing different methods */ + EAP_RADIUS = 253, + EAP_EXPANDED = 254, + EAP_EXPERIMENTAL = 255, +}; + +/** + * enum names for eap_type_t. + */ +extern enum_name_t *eap_type_names; + +/** + * short string enum names for eap_type_t. + */ +extern enum_name_t *eap_type_short_names; + +/** + * Lookup the EAP method type from a string. + * + * @param name EAP method name (such as "md5", "aka") + * @return method type, 0 if unkown + */ +eap_type_t eap_type_from_string(char *name); + +/** + * EAP code, type of an EAP message + */ +enum eap_code_t { + EAP_REQUEST = 1, + EAP_RESPONSE = 2, + EAP_SUCCESS = 3, + EAP_FAILURE = 4, +}; + +/** + * enum names for eap_code_t. + */ +extern enum_name_t *eap_code_names; + +/** + * short string enum names for eap_code_t. + */ +extern enum_name_t *eap_code_short_names; + +/** + * Interface of an EAP method for server and client side. + * + * An EAP method initiates an EAP exchange and processes requests and + * responses. An EAP method may need multiple exchanges before succeeding, and + * the eap_authentication may use multiple EAP methods to authenticate a peer. + * To accomplish these requirements, all EAP methods have their own + * implementation while the eap_authenticatior uses one or more of these + * EAP methods. Sending of EAP(SUCCESS/FAILURE) message is not the job + * of the method, the eap_authenticator does this. + * An EAP method may establish a MSK, this is used the complete the + * authentication. Even if a mutual EAP method is used, the traditional + * AUTH payloads are required. Only these include the nonces and messages from + * ike_sa_init and therefore prevent man in the middle attacks. + * The EAP method must use an initial EAP identifier value != 0, as a preceding + * EAP-Identity exchange always uses identifier 0. + */ +struct eap_method_t { + + /** + * Initiate the EAP exchange. + * + * initiate() is only useable for server implementations, as clients only + * reply to server requests. + * A eap_payload is created in "out" if result is NEED_MORE. + * + * @param out eap_payload to send to the client + * @return + * - NEED_MORE, if an other exchange is required + * - FAILED, if unable to create eap request payload + */ + status_t (*initiate) (eap_method_t *this, eap_payload_t **out); + + /** + * Process a received EAP message. + * + * A eap_payload is created in "out" if result is NEED_MORE. + * + * @param in eap_payload response received + * @param out created eap_payload to send + * @return + * - NEED_MORE, if an other exchange is required + * - FAILED, if EAP method failed + * - SUCCESS, if EAP method succeeded + */ + status_t (*process) (eap_method_t *this, eap_payload_t *in, + eap_payload_t **out); + + /** + * Get the EAP type implemented in this method. + * + * @param vendor pointer receiving vendor identifier for type, 0 for none + * @return type of the EAP method + */ + eap_type_t (*get_type) (eap_method_t *this, u_int32_t *vendor); + + /** + * Check if this EAP method authenticates the server. + * + * Some EAP methods provide mutual authentication and + * allow authentication using only EAP, if the peer supports it. + * + * @return TRUE if methods provides mutual authentication + */ + bool (*is_mutual) (eap_method_t *this); + + /** + * Get the MSK established by this EAP method. + * + * Not all EAP methods establish a shared secret. For implementations of + * the EAP-Identity method, get_msk() returns the received identity. + * + * @param msk chunk receiving internal stored MSK + * @return + * - SUCCESS, or + * - FAILED, if MSK not established (yet) + */ + status_t (*get_msk) (eap_method_t *this, chunk_t *msk); + + /** + * Destroys a eap_method_t object. + */ + void (*destroy) (eap_method_t *this); +}; + +/** + * Constructor definition for a pluggable EAP method. + * + * Each EAP module must define a constructor function which will return + * an initialized object with the methods defined in eap_method_t. + * Constructors for server and peers are identical, to support both roles + * of a EAP method, a plugin needs register two constructors in the + * eap_manager_t. + * The passed identites are of type ID_EAP and valid only during the + * constructor invocation. + * + * @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 eap_method_t *(*eap_constructor_t)(identification_t *server, + identification_t *peer); + +#endif /** EAP_METHOD_H_ @}*/ diff --git a/src/libcharon/sa/authenticators/eap/sim_manager.c b/src/libcharon/sa/authenticators/eap/sim_manager.c new file mode 100644 index 000000000..157865083 --- /dev/null +++ b/src/libcharon/sa/authenticators/eap/sim_manager.c @@ -0,0 +1,534 @@ +/* + * Copyright (C) 2008 Martin Willi + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "sim_manager.h" + +#include <daemon.h> +#include <utils/linked_list.h> + +typedef struct private_sim_manager_t private_sim_manager_t; + +/** + * Private data of an sim_manager_t object. + */ +struct private_sim_manager_t { + + /** + * Public sim_manager_t interface. + */ + sim_manager_t public; + + /** + * list of added cards + */ + linked_list_t *cards; + + /** + * list of added provider + */ + linked_list_t *providers; + + /** + * list of added hooks + */ + linked_list_t *hooks; +}; + +/** + * Implementation of sim_manager_t.add_card + */ +static void add_card(private_sim_manager_t *this, sim_card_t *card) +{ + this->cards->insert_last(this->cards, card); +} + +/** + * Implementation of sim_manager_t.remove_card + */ +static void remove_card(private_sim_manager_t *this, sim_card_t *card) +{ + this->cards->remove(this->cards, card, NULL); +} + +/** + * Implementation of sim_manager_t.card_get_triplet + */ +static bool card_get_triplet(private_sim_manager_t *this, identification_t *id, + char rand[SIM_RAND_LEN], char sres[SIM_SRES_LEN], + char kc[SIM_KC_LEN]) +{ + enumerator_t *enumerator; + sim_card_t *card; + int tried = 0; + + enumerator = this->cards->create_enumerator(this->cards); + while (enumerator->enumerate(enumerator, &card)) + { + if (card->get_triplet(card, id, rand, sres, kc)) + { + enumerator->destroy(enumerator); + return TRUE; + } + tried++; + } + enumerator->destroy(enumerator); + DBG1(DBG_IKE, "tried %d SIM cards, but none has triplets for '%Y'", + tried, id); + return FALSE; +} + +/** + * Implementation of sim_manager_t.card_get_quintuplet + */ +static status_t card_get_quintuplet(private_sim_manager_t *this, + identification_t *id, char rand[AKA_RAND_LEN], + char autn[AKA_AUTN_LEN], char ck[AKA_CK_LEN], + char ik[AKA_IK_LEN], char res[AKA_RES_MAX], + int *res_len) +{ + enumerator_t *enumerator; + sim_card_t *card; + status_t status = NOT_FOUND; + int tried = 0; + + enumerator = this->cards->create_enumerator(this->cards); + while (enumerator->enumerate(enumerator, &card)) + { + status = card->get_quintuplet(card, id, rand, autn, ck, ik, res, res_len); + switch (status) + { /* try next on error, but not on INVALID_STATE */ + case SUCCESS: + case INVALID_STATE: + enumerator->destroy(enumerator); + return status; + case NOT_SUPPORTED: + case FAILED: + default: + tried++; + continue; + } + } + enumerator->destroy(enumerator); + DBG1(DBG_IKE, "tried %d SIM cards, but none has quintuplets for '%Y'", + tried, id); + return status; +} + +/** + * Implementation of sim_manager_t.card_resync + */ +static bool card_resync(private_sim_manager_t *this, identification_t *id, + char rand[AKA_RAND_LEN], char auts[AKA_AUTS_LEN]) +{ + enumerator_t *enumerator; + sim_card_t *card; + + enumerator = this->cards->create_enumerator(this->cards); + while (enumerator->enumerate(enumerator, &card)) + { + if (card->resync(card, id, rand, auts)) + { + enumerator->destroy(enumerator); + return TRUE; + } + } + enumerator->destroy(enumerator); + return FALSE; +} + +/** + * Implementation of sim_manager_t.card_set_pseudonym + */ +static void card_set_pseudonym(private_sim_manager_t *this, + identification_t *id, identification_t *pseudonym) +{ + enumerator_t *enumerator; + sim_card_t *card; + + DBG1(DBG_IKE, "storing pseudonym '%Y' for '%Y'", pseudonym, id); + + enumerator = this->cards->create_enumerator(this->cards); + while (enumerator->enumerate(enumerator, &card)) + { + card->set_pseudonym(card, id, pseudonym); + } + enumerator->destroy(enumerator); +} + +/** + * Implementation of sim_manager_t.card_get_pseudonym + */ +static identification_t* card_get_pseudonym(private_sim_manager_t *this, + identification_t *id) +{ + enumerator_t *enumerator; + sim_card_t *card; + identification_t *pseudonym = NULL; + + enumerator = this->cards->create_enumerator(this->cards); + while (enumerator->enumerate(enumerator, &card)) + { + pseudonym = card->get_pseudonym(card, id); + if (pseudonym) + { + DBG1(DBG_IKE, "using stored pseudonym identity '%Y' " + "instead of '%Y'", pseudonym, id); + break; + } + } + enumerator->destroy(enumerator); + return pseudonym; +} + +/** + * Implementation of sim_manager_t.card_set_reauth + */ +static void card_set_reauth(private_sim_manager_t *this, identification_t *id, + identification_t *next, char mk[HASH_SIZE_SHA1], + u_int16_t counter) +{ + enumerator_t *enumerator; + sim_card_t *card; + + DBG1(DBG_IKE, "storing next reauthentication identity '%Y' for '%Y'", + next, id); + + enumerator = this->cards->create_enumerator(this->cards); + while (enumerator->enumerate(enumerator, &card)) + { + card->set_reauth(card, id, next, mk, counter); + } + enumerator->destroy(enumerator); +} + +/** + * Implementation of sim_manager_t.card_get_reauth + */ +static identification_t* card_get_reauth(private_sim_manager_t *this, + identification_t *id, char mk[HASH_SIZE_SHA1], + u_int16_t *counter) +{ + enumerator_t *enumerator; + sim_card_t *card; + identification_t *reauth = NULL; + + enumerator = this->cards->create_enumerator(this->cards); + while (enumerator->enumerate(enumerator, &card)) + { + reauth = card->get_reauth(card, id, mk, counter); + if (reauth) + { + DBG1(DBG_IKE, "using stored reauthentication identity '%Y' " + "instead of '%Y'", reauth, id); + break; + } + } + enumerator->destroy(enumerator); + return reauth; +} + +/** + * Implementation of sim_manager_t.add_provider + */ +static void add_provider(private_sim_manager_t *this, sim_provider_t *provider) +{ + this->providers->insert_last(this->providers, provider); +} + +/** + * Implementation of sim_manager_t.remove_provider + */ +static void remove_provider(private_sim_manager_t *this, + sim_provider_t *provider) +{ + this->providers->remove(this->providers, provider, NULL); +} + +/** + * Implementation of sim_manager_t.provider_get_triplet + */ +static bool provider_get_triplet(private_sim_manager_t *this, + identification_t *id, char rand[SIM_RAND_LEN], + char sres[SIM_SRES_LEN], char kc[SIM_KC_LEN]) +{ + enumerator_t *enumerator; + sim_provider_t *provider; + int tried = 0; + + enumerator = this->providers->create_enumerator(this->providers); + while (enumerator->enumerate(enumerator, &provider)) + { + if (provider->get_triplet(provider, id, rand, sres, kc)) + { + enumerator->destroy(enumerator); + return TRUE; + } + tried++; + } + enumerator->destroy(enumerator); + DBG1(DBG_IKE, "tried %d SIM providers, but none had a triplet for '%Y'", + tried, id); + return FALSE; +} + +/** + * Implementation of sim_manager_t.provider_get_quintuplet + */ +static bool provider_get_quintuplet(private_sim_manager_t *this, + identification_t *id, char rand[AKA_RAND_LEN], + char xres[AKA_RES_MAX], int *xres_len, + char ck[AKA_CK_LEN], char ik[AKA_IK_LEN], + char autn[AKA_AUTN_LEN]) +{ + enumerator_t *enumerator; + sim_provider_t *provider; + int tried = 0; + + enumerator = this->providers->create_enumerator(this->providers); + while (enumerator->enumerate(enumerator, &provider)) + { + if (provider->get_quintuplet(provider, id, rand, xres, xres_len, + ck, ik, autn)) + { + enumerator->destroy(enumerator); + return TRUE; + } + } + enumerator->destroy(enumerator); + DBG1(DBG_IKE, "tried %d SIM providers, but none had a quintuplet for '%Y'", + tried, id); + return FALSE; +} + +/** + * Implementation of sim_manager_t.provider_resync + */ +static bool provider_resync(private_sim_manager_t *this, identification_t *id, + char rand[AKA_RAND_LEN], char auts[AKA_AUTS_LEN]) +{ + enumerator_t *enumerator; + sim_provider_t *provider; + + enumerator = this->providers->create_enumerator(this->providers); + while (enumerator->enumerate(enumerator, &provider)) + { + if (provider->resync(provider, id, rand, auts)) + { + enumerator->destroy(enumerator); + return TRUE; + } + } + enumerator->destroy(enumerator); + return FALSE; +} + +/** + * Implementation of sim_manager_t.provider_is_pseudonym + */ +static identification_t* provider_is_pseudonym(private_sim_manager_t *this, + identification_t *id) +{ + enumerator_t *enumerator; + sim_provider_t *provider; + identification_t *permanent = NULL; + + enumerator = this->providers->create_enumerator(this->providers); + while (enumerator->enumerate(enumerator, &provider)) + { + permanent = provider->is_pseudonym(provider, id); + if (permanent) + { + DBG1(DBG_IKE, "received pseudonym identity '%Y' " + "mapping to '%Y'", id, permanent); + break; + } + } + enumerator->destroy(enumerator); + return permanent; +} + +/** + * Implementation of sim_manager_t.provider_gen_pseudonym + */ +static identification_t* provider_gen_pseudonym(private_sim_manager_t *this, + identification_t *id) +{ + enumerator_t *enumerator; + sim_provider_t *provider; + identification_t *pseudonym = NULL; + + enumerator = this->providers->create_enumerator(this->providers); + while (enumerator->enumerate(enumerator, &provider)) + { + pseudonym = provider->gen_pseudonym(provider, id); + if (pseudonym) + { + DBG1(DBG_IKE, "proposing new pseudonym '%Y'", pseudonym); + break; + } + } + enumerator->destroy(enumerator); + return pseudonym; +} + +/** + * Implementation of sim_manager_t.provider_is_reauth + */ +static identification_t* provider_is_reauth(private_sim_manager_t *this, + identification_t *id, char mk[HASH_SIZE_SHA1], + u_int16_t *counter) +{ + enumerator_t *enumerator; + sim_provider_t *provider; + identification_t *permanent = NULL; + + enumerator = this->providers->create_enumerator(this->providers); + while (enumerator->enumerate(enumerator, &provider)) + { + permanent = provider->is_reauth(provider, id, mk, counter); + if (permanent) + { + DBG1(DBG_IKE, "received reauthentication identity '%Y' " + "mapping to '%Y'", id, permanent); + break; + } + } + enumerator->destroy(enumerator); + return permanent; +} + +/** + * Implementation of sim_manager_t.provider_gen_reauth + */ +static identification_t* provider_gen_reauth(private_sim_manager_t *this, + identification_t *id, char mk[HASH_SIZE_SHA1]) +{ + enumerator_t *enumerator; + sim_provider_t *provider; + identification_t *reauth = NULL; + + enumerator = this->providers->create_enumerator(this->providers); + while (enumerator->enumerate(enumerator, &provider)) + { + reauth = provider->gen_reauth(provider, id, mk); + if (reauth) + { + DBG1(DBG_IKE, "proposing new reauthentication identity '%Y'", reauth); + break; + } + } + enumerator->destroy(enumerator); + return reauth; +} + +/** + * Implementation of sim_manager_t.add_hooks + */ +static void add_hooks(private_sim_manager_t *this, sim_hooks_t *hooks) +{ + this->hooks->insert_last(this->hooks, hooks); +} + +/** + * Implementation of sim_manager_t.remove_hooks + */ +static void remove_hooks(private_sim_manager_t *this, sim_hooks_t *hooks) +{ + this->hooks->remove(this->hooks, hooks, NULL); +} + +/** + * Implementation of sim_manager_t.message_hook + */ +static void message_hook(private_sim_manager_t *this, + simaka_message_t *message, bool inbound, bool decrypted) +{ + enumerator_t *enumerator; + sim_hooks_t *hooks; + + enumerator = this->hooks->create_enumerator(this->hooks); + while (enumerator->enumerate(enumerator, &hooks)) + { + hooks->message(hooks, message, inbound, decrypted); + } + enumerator->destroy(enumerator); +} + +/** + * Implementation of sim_manager_t.key_hook + */ +static void key_hook(private_sim_manager_t *this, + chunk_t k_encr, chunk_t k_auth) +{ + enumerator_t *enumerator; + sim_hooks_t *hooks; + + enumerator = this->hooks->create_enumerator(this->hooks); + while (enumerator->enumerate(enumerator, &hooks)) + { + hooks->keys(hooks, k_encr, k_auth); + } + enumerator->destroy(enumerator); +} + +/** + * Implementation of sim_manager_t.destroy. + */ +static void destroy(private_sim_manager_t *this) +{ + this->cards->destroy(this->cards); + this->providers->destroy(this->providers); + this->hooks->destroy(this->hooks); + free(this); +} + +/** + * See header + */ +sim_manager_t *sim_manager_create() +{ + private_sim_manager_t *this = malloc_thing(private_sim_manager_t); + + this->public.add_card = (void(*)(sim_manager_t*, sim_card_t *card))add_card; + this->public.remove_card = (void(*)(sim_manager_t*, sim_card_t *card))remove_card; + this->public.card_get_triplet = (bool(*)(sim_manager_t*, identification_t *id, char rand[SIM_RAND_LEN], char sres[SIM_SRES_LEN], char kc[SIM_KC_LEN]))card_get_triplet; + this->public.card_get_quintuplet = (status_t(*)(sim_manager_t*, identification_t *id, char rand[AKA_RAND_LEN], char autn[AKA_AUTN_LEN], char ck[AKA_CK_LEN], char ik[AKA_IK_LEN], char res[AKA_RES_MAX], int *res_len))card_get_quintuplet; + this->public.card_resync = (bool(*)(sim_manager_t*, identification_t *id, char rand[AKA_RAND_LEN], char auts[AKA_AUTS_LEN]))card_resync; + this->public.card_set_pseudonym = (void(*)(sim_manager_t*, identification_t *id, identification_t *pseudonym))card_set_pseudonym; + this->public.card_get_pseudonym = (identification_t*(*)(sim_manager_t*, identification_t *id))card_get_pseudonym; + this->public.card_set_reauth = (void(*)(sim_manager_t*, identification_t *id, identification_t *next, char mk[HASH_SIZE_SHA1], u_int16_t counter))card_set_reauth; + this->public.card_get_reauth = (identification_t*(*)(sim_manager_t*, identification_t *id, char mk[HASH_SIZE_SHA1], u_int16_t *counter))card_get_reauth; + this->public.add_provider = (void(*)(sim_manager_t*, sim_provider_t *provider))add_provider; + this->public.remove_provider = (void(*)(sim_manager_t*, sim_provider_t *provider))remove_provider; + this->public.provider_get_triplet = (bool(*)(sim_manager_t*, identification_t *id, char rand[SIM_RAND_LEN], char sres[SIM_SRES_LEN], char kc[SIM_KC_LEN]))provider_get_triplet; + this->public.provider_get_quintuplet = (bool(*)(sim_manager_t*, identification_t *id, char rand[AKA_RAND_LEN], char xres[AKA_RES_MAX], int *xres_len, char ck[AKA_CK_LEN], char ik[AKA_IK_LEN], char autn[AKA_AUTN_LEN]))provider_get_quintuplet; + this->public.provider_resync = (bool(*)(sim_manager_t*, identification_t *id, char rand[AKA_RAND_LEN], char auts[AKA_AUTS_LEN]))provider_resync; + this->public.provider_is_pseudonym = (identification_t*(*)(sim_manager_t*, identification_t *id))provider_is_pseudonym; + this->public.provider_gen_pseudonym = (identification_t*(*)(sim_manager_t*, identification_t *id))provider_gen_pseudonym; + this->public.provider_is_reauth = (identification_t*(*)(sim_manager_t*, identification_t *id, char mk[HASH_SIZE_SHA1], u_int16_t *counter))provider_is_reauth; + this->public.provider_gen_reauth = (identification_t*(*)(sim_manager_t*, identification_t *id, char mk[HASH_SIZE_SHA1]))provider_gen_reauth; + this->public.add_hooks = (void(*)(sim_manager_t*, sim_hooks_t *hooks))add_hooks; + this->public.remove_hooks = (void(*)(sim_manager_t*, sim_hooks_t *hooks))remove_hooks; + this->public.message_hook = (void(*)(sim_manager_t*, simaka_message_t *message, bool inbound, bool decrypted))message_hook; + this->public.key_hook = (void(*)(sim_manager_t*, chunk_t k_encr, chunk_t k_auth))key_hook; + this->public.destroy = (void(*)(sim_manager_t*))destroy; + + this->cards = linked_list_create(); + this->providers = linked_list_create(); + this->hooks = linked_list_create(); + + return &this->public; +} + diff --git a/src/libcharon/sa/authenticators/eap/sim_manager.h b/src/libcharon/sa/authenticators/eap/sim_manager.h new file mode 100644 index 000000000..9aa661ac8 --- /dev/null +++ b/src/libcharon/sa/authenticators/eap/sim_manager.h @@ -0,0 +1,514 @@ +/* + * Copyright (C) 2008-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. + */ + +/** + * @defgroup sim_manager sim_manager + * @{ @ingroup eap + */ + +#ifndef SIM_MANAGER_H_ +#define SIM_MANAGER_H_ + +#include <crypto/hashers/hasher.h> +#include <utils/identification.h> +#include <utils/enumerator.h> +#include <sa/authenticators/eap/eap_method.h> + +typedef struct sim_manager_t sim_manager_t; +typedef struct sim_card_t sim_card_t; +typedef struct sim_provider_t sim_provider_t; +typedef struct sim_hooks_t sim_hooks_t; + +/** implemented in libsimaka, but we need it for the message hook */ +typedef struct simaka_message_t simaka_message_t; + +#define SIM_RAND_LEN 16 +#define SIM_SRES_LEN 4 +#define SIM_KC_LEN 8 + +#define AKA_RAND_LEN 16 +#define AKA_RES_MAX 16 +#define AKA_CK_LEN 16 +#define AKA_IK_LEN 16 +#define AKA_AUTN_LEN 16 +#define AKA_AUTS_LEN 14 + +/** + * Interface for a (U)SIM card (used as EAP client). + * + * The SIM card completes triplets/quintuplets requested in a challenge + * received from the server. + * An implementation supporting only one of SIM/AKA authentication may + * implement the other methods with return_false()/return NOT_SUPPORTED/NULL. + */ +struct sim_card_t { + + /** + * Calculate SRES/KC from a RAND for SIM authentication. + * + * @param id permanent identity to get a triplet for + * @param rand RAND input buffer, fixed size 16 bytes + * @param sres SRES output buffer, fixed size 4 byte + * @param kc KC output buffer, fixed size 8 bytes + * @return TRUE if SRES/KC calculated, FALSE on error/wrong identity + */ + bool (*get_triplet)(sim_card_t *this, identification_t *id, + char rand[SIM_RAND_LEN], char sres[SIM_SRES_LEN], + char kc[SIM_KC_LEN]); + + /** + * Calculate CK/IK/RES from RAND/AUTN for AKA authentication. + * + * If the received sequence number (in autn) is out of sync, INVALID_STATE + * is returned. + * The RES value is the only one with variable length. Pass a buffer + * of at least AKA_RES_MAX, the actual number of bytes is written to the + * res_len value. While the standard would allow any bit length between + * 32 and 128 bits, we support only full bytes for now. + * + * @param id permanent identity to request quintuplet for + * @param rand random value rand + * @param autn authentication token autn + * @param ck buffer receiving encryption key ck + * @param ik buffer receiving integrity key ik + * @param res buffer receiving authentication result res + * @param res_len nubmer of bytes written to res buffer + * @return SUCCESS, FAILED, or INVALID_STATE if out of sync + */ + status_t (*get_quintuplet)(sim_card_t *this, identification_t *id, + char rand[AKA_RAND_LEN], char autn[AKA_AUTN_LEN], + char ck[AKA_CK_LEN], char ik[AKA_IK_LEN], + char res[AKA_RES_MAX], int *res_len); + + /** + * Calculate AUTS from RAND for AKA resynchronization. + * + * @param id permanent identity to request quintuplet for + * @param rand random value rand + * @param auts resynchronization parameter auts + * @return TRUE if parameter generated successfully + */ + bool (*resync)(sim_card_t *this, identification_t *id, + char rand[AKA_RAND_LEN], char auts[AKA_AUTS_LEN]); + + /** + * Set the pseudonym to use for next authentication. + * + * @param id permanent identity of the peer + * @param pseudonym pseudonym identity received from the server + */ + void (*set_pseudonym)(sim_card_t *this, identification_t *id, + identification_t *pseudonym); + + /** + * Get the pseudonym previously stored via set_pseudonym(). + * + * @param id permanent identity of the peer + * @return associated pseudonym identity, NULL if none stored + */ + identification_t* (*get_pseudonym)(sim_card_t *this, identification_t *id); + + /** + * Store parameters to use for the next fast reauthentication. + * + * @param id permanent identity of the peer + * @param next next fast reauthentication identity to use + * @param mk master key MK to store for reauthentication + * @param counter counter value to store, host order + */ + void (*set_reauth)(sim_card_t *this, identification_t *id, + identification_t *next, char mk[HASH_SIZE_SHA1], + u_int16_t counter); + + /** + * Retrieve parameters for fast reauthentication stored via set_reauth(). + * + * @param id permanent identity of the peer + * @param mk buffer receiving master key MK + * @param counter pointer receiving counter value, in host order + * @return fast reauthentication identity, NULL if not found + */ + identification_t* (*get_reauth)(sim_card_t *this, identification_t *id, + char mk[HASH_SIZE_SHA1], u_int16_t *counter); +}; + +/** + * Interface for a triplet/quintuplet provider (used as EAP server). + * + * A SIM provider hands out triplets for SIM authentication and quintuplets + * for AKA authentication. Multiple SIM provider instances can serve as + * authentication backend to authenticate clients using SIM/AKA. + * An implementation supporting only one of SIM/AKA authentication may + * implement the other methods with return_false(). + */ +struct sim_provider_t { + + /** + * Create a challenge for SIM authentication. + * + * @param id permanent identity of peer to gen triplet for + * @param rand RAND output buffer, fixed size 16 bytes + * @param sres SRES output buffer, fixed size 4 byte + * @param kc KC output buffer, fixed size 8 bytes + * @return TRUE if triplet received, FALSE otherwise + */ + bool (*get_triplet)(sim_provider_t *this, identification_t *id, + char rand[SIM_RAND_LEN], char sres[SIM_SRES_LEN], + char kc[SIM_KC_LEN]); + + /** + * Create a challenge for AKA authentication. + * + * The XRES value is the only one with variable length. Pass a buffer + * of at least AKA_RES_MAX, the actual number of bytes is written to the + * xres_len value. While the standard would allow any bit length between + * 32 and 128 bits, we support only full bytes for now. + * + * @param id permanent identity of peer to create challenge for + * @param rand buffer receiving random value rand + * @param xres buffer receiving expected authentication result xres + * @param xres_len nubmer of bytes written to xres buffer + * @param ck buffer receiving encryption key ck + * @param ik buffer receiving integrity key ik + * @param autn authentication token autn + * @return TRUE if quintuplet generated successfully + */ + bool (*get_quintuplet)(sim_provider_t *this, identification_t *id, + char rand[AKA_RAND_LEN], + char xres[AKA_RES_MAX], int *xres_len, + char ck[AKA_CK_LEN], char ik[AKA_IK_LEN], + char autn[AKA_AUTN_LEN]); + + /** + * Process AKA resynchroniusation request of a peer. + * + * @param id permanent identity of peer requesting resynchronisation + * @param rand random value rand + * @param auts synchronization parameter auts + * @return TRUE if resynchronized successfully + */ + bool (*resync)(sim_provider_t *this, identification_t *id, + char rand[AKA_RAND_LEN], char auts[AKA_AUTS_LEN]); + + /** + * Check if peer uses a pseudonym, get permanent identity. + * + * @param id pseudonym identity candidate + * @return permanent identity, NULL if id not a pseudonym + */ + identification_t* (*is_pseudonym)(sim_provider_t *this, + identification_t *id); + + /** + * Generate a pseudonym identitiy for a given peer identity. + * + * @param id permanent identity to generate a pseudonym for + * @return generated pseudonym, NULL to not use a pseudonym identity + */ + identification_t* (*gen_pseudonym)(sim_provider_t *this, + identification_t *id); + + /** + * Check if peer uses reauthentication, retrieve reauth parameters. + * + * @param id reauthentication identity (candidate) + * @param mk buffer receiving master key MK + * @param counter pointer receiving current counter value, host order + * @return permanent identity, NULL if id not a reauth identity + */ + identification_t* (*is_reauth)(sim_provider_t *this, identification_t *id, + char mk[HASH_SIZE_SHA1], u_int16_t *counter); + + /** + * Generate a fast reauthentication identity, associated to a master key. + * + * @param id permanent peer identity + * @param mk master key to store along with generated identity + * @return fast reauthentication identity, NULL to not use reauth + */ + identification_t* (*gen_reauth)(sim_provider_t *this, identification_t *id, + char mk[HASH_SIZE_SHA1]); +}; + +/** + * Additional hooks invoked during EAP-SIM/AKA message processing. + */ +struct sim_hooks_t { + + /** + * SIM/AKA message parsing. + * + * As a SIM/AKA optionally contains encrypted attributes, the hook + * might get invoked twice, once before and once after decryption. + * + * @param message SIM/AKA message + * @param inbound TRUE for incoming messages, FALSE for outgoing + * @param decrypted TRUE if AT_ENCR_DATA has been decrypted + */ + void (*message)(sim_hooks_t *this, simaka_message_t *message, + bool inbound, bool decrypted); + + /** + * SIM/AKA encryption/authentication key hooks. + * + * @param k_encr derived SIM/AKA encryption key k_encr + * @param k_auth derived SIM/AKA authentication key k_auth + */ + void (*keys)(sim_hooks_t *this, chunk_t k_encr, chunk_t k_auth); +}; + +/** + * The SIM manager handles multiple (U)SIM cards/providers and hooks. + */ +struct sim_manager_t { + + /** + * Register a SIM card (client) at the manager. + * + * @param card sim card to register + */ + void (*add_card)(sim_manager_t *this, sim_card_t *card); + + /** + * Unregister a previously registered card from the manager. + * + * @param card sim card to unregister + */ + void (*remove_card)(sim_manager_t *this, sim_card_t *card); + + /** + * Calculate SIM triplets on one of the registered SIM cards. + * + * @param id permanent identity to get a triplet for + * @param rand RAND input buffer, fixed size 16 bytes + * @param sres SRES output buffer, fixed size 4 byte + * @param kc KC output buffer, fixed size 8 bytes + * @return TRUE if calculated, FALSE if no matching card found + */ + bool (*card_get_triplet)(sim_manager_t *this, identification_t *id, + char rand[SIM_RAND_LEN], char sres[SIM_SRES_LEN], + char kc[SIM_KC_LEN]); + + /** + * Calculate AKA quitpulets on one of the registered SIM cards. + * + * @param id permanent identity to request quintuplet for + * @param rand random value rand + * @param autn authentication token autn + * @param ck buffer receiving encryption key ck + * @param ik buffer receiving integrity key ik + * @param res buffer receiving authentication result res + * @param res_len nubmer of bytes written to res buffer + * @return SUCCESS, FAILED, or INVALID_STATE if out of sync + */ + status_t (*card_get_quintuplet)(sim_manager_t *this, identification_t *id, + char rand[AKA_RAND_LEN], char autn[AKA_AUTN_LEN], + char ck[AKA_CK_LEN], char ik[AKA_IK_LEN], + char res[AKA_RES_MAX], int *res_len); + + /** + * Calculate resynchronization data on one of the registered SIM cards. + * + * @param id permanent identity to request quintuplet for + * @param rand random value rand + * @param auts resynchronization parameter auts + * @return TRUE if calculated, FALSE if no matcing card found + */ + bool (*card_resync)(sim_manager_t *this, identification_t *id, + char rand[AKA_RAND_LEN], char auts[AKA_AUTS_LEN]); + + /** + * Store a received pseudonym on one of the registered SIM cards. + * + * @param id permanent identity of the peer + * @param pseudonym pseudonym identity received from the server + */ + void (*card_set_pseudonym)(sim_manager_t *this, identification_t *id, + identification_t *pseudonym); + + /** + * Get a stored pseudonym from one of the registerd SIM cards. + * + * @param id permanent identity of the peer + * @return associated pseudonym identity, NULL if none found + */ + identification_t* (*card_get_pseudonym)(sim_manager_t *this, + identification_t *id); + + /** + * Store fast reauthentication parameters on one of the registered cards. + * + * @param id permanent identity of the peer + * @param next next fast reauthentication identity to use + * @param mk master key MK to store for reauthentication + * @param counter counter value to store, host order + */ + void (*card_set_reauth)(sim_manager_t *this, identification_t *id, + identification_t *next, char mk[HASH_SIZE_SHA1], + u_int16_t counter); + + /** + * Retrieve fast reauthentication parameters from one of the registerd cards. + * + * @param id permanent identity of the peer + * @param mk buffer receiving master key MK + * @param counter pointer receiving counter value, in host order + * @return fast reauthentication identity, NULL if none found + */ + identification_t* (*card_get_reauth)(sim_manager_t *this, + identification_t *id, char mk[HASH_SIZE_SHA1], + u_int16_t *counter); + + /** + * Register a triplet provider (server) at the manager. + * + * @param card sim card to register + */ + void (*add_provider)(sim_manager_t *this, sim_provider_t *provider); + + /** + * Unregister a previously registered provider from the manager. + * + * @param card sim card to unregister + */ + void (*remove_provider)(sim_manager_t *this, sim_provider_t *provider); + + /** + * Get a SIM triplet from one of the registered providers. + * + * @param id permanent identity of peer to gen triplet for + * @param rand RAND output buffer, fixed size 16 bytes + * @param sres SRES output buffer, fixed size 4 byte + * @param kc KC output buffer, fixed size 8 bytes + * @return TRUE if triplet received, FALSE if no match found + */ + bool (*provider_get_triplet)(sim_manager_t *this, identification_t *id, + char rand[SIM_RAND_LEN], char sres[SIM_SRES_LEN], + char kc[SIM_KC_LEN]); + + /** + * Get a AKA quintuplet from one of the registered providers. + * + * @param id permanent identity of peer to create challenge for + * @param rand buffer receiving random value rand + * @param xres buffer receiving expected authentication result xres + * @param ck buffer receiving encryption key ck + * @param ik buffer receiving integrity key ik + * @param autn authentication token autn + * @return TRUE if quintuplet received, FALSE if no match found + */ + bool (*provider_get_quintuplet)(sim_manager_t *this, identification_t *id, + char rand[AKA_RAND_LEN], + char xres[AKA_RES_MAX], int *xres_len, + char ck[AKA_CK_LEN], char ik[AKA_IK_LEN], + char autn[AKA_AUTN_LEN]); + + /** + * Pass AKA resynchronization data to one of the registered providers. + * + * @param id permanent identity of peer requesting resynchronisation + * @param rand random value rand + * @param auts synchronization parameter auts + * @return TRUE if resynchronized, FALSE if not handled + */ + bool (*provider_resync)(sim_manager_t *this, identification_t *id, + char rand[AKA_RAND_LEN], char auts[AKA_AUTS_LEN]); + + /** + * Check if a peer uses a pseudonym using one of the registered providers. + * + * @param id pseudonym identity candidate + * @return permanent identity, NULL if id not a pseudonym + */ + identification_t* (*provider_is_pseudonym)(sim_manager_t *this, + identification_t *id); + + /** + * Generate a new pseudonym using one of the registered providers. + * + * @param id permanent identity to generate a pseudonym for + * @return generated pseudonym, NULL to not use a pseudonym identity + */ + identification_t* (*provider_gen_pseudonym)(sim_manager_t *this, + identification_t *id); + + /** + * Check if a peer uses a reauth id using one of the registered providers. + * + * @param id reauthentication identity (candidate) + * @param mk buffer receiving master key MK + * @param counter pointer receiving current counter value, host order + * @return permanent identity, NULL if not a known reauth identity + */ + identification_t* (*provider_is_reauth)(sim_manager_t *this, + identification_t *id, char mk[HASH_SIZE_SHA1], + u_int16_t *counter); + + /** + * Generate a fast reauth id using one of the registered providers. + * + * @param id permanent peer identity + * @param mk master key to store along with generated identity + * @return fast reauthentication identity, NULL to not use reauth + */ + identification_t* (*provider_gen_reauth)(sim_manager_t *this, + identification_t *id, char mk[HASH_SIZE_SHA1]); + + /** + * Register a set of hooks to the manager. + * + * @param hooks hook interface implementation to register + */ + void (*add_hooks)(sim_manager_t *this, sim_hooks_t *hooks); + + /** + * Unregister a set of hooks from the manager. + * + * @param hooks hook interface implementation to unregister + */ + void (*remove_hooks)(sim_manager_t *this, sim_hooks_t *hooks); + + /** + * Invoke SIM/AKA message hook. + * + * @param message SIM message + * @param inbound TRUE for incoming messages, FALSE for outgoing + * @param decrypted TRUE if AT_ENCR_DATA has been decrypted + */ + void (*message_hook)(sim_manager_t *this, simaka_message_t *message, + bool inbound, bool decrypted); + + /** + * Invoke SIM/AKA key hook. + * + * @param k_encr SIM/AKA encryption key k_encr + * @param k_auth SIM/AKA authentication key k_auth + */ + void (*key_hook)(sim_manager_t *this, chunk_t k_encr, chunk_t k_auth); + + /** + * Destroy a manager instance. + */ + void (*destroy)(sim_manager_t *this); +}; + +/** + * Create an SIM manager to handle multiple (U)SIM cards/providers. + * + * @return sim_t object + */ +sim_manager_t *sim_manager_create(); + +#endif /** SIM_MANAGER_H_ @}*/ diff --git a/src/libcharon/sa/authenticators/eap_authenticator.c b/src/libcharon/sa/authenticators/eap_authenticator.c new file mode 100644 index 000000000..4617c4d8d --- /dev/null +++ b/src/libcharon/sa/authenticators/eap_authenticator.c @@ -0,0 +1,705 @@ +/* + * Copyright (C) 2006-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 "eap_authenticator.h" + +#include <daemon.h> +#include <sa/authenticators/eap/eap_method.h> +#include <encoding/payloads/auth_payload.h> +#include <encoding/payloads/eap_payload.h> + +typedef struct private_eap_authenticator_t private_eap_authenticator_t; + +/** + * Private data of an eap_authenticator_t object. + */ +struct private_eap_authenticator_t { + + /** + * Public authenticator_t interface. + */ + eap_authenticator_t public; + + /** + * Assigned IKE_SA + */ + ike_sa_t *ike_sa; + + /** + * others nonce to include in AUTH calculation + */ + chunk_t received_nonce; + + /** + * our nonce to include in AUTH calculation + */ + chunk_t sent_nonce; + + /** + * others IKE_SA_INIT message data to include in AUTH calculation + */ + chunk_t received_init; + + /** + * our IKE_SA_INIT message data to include in AUTH calculation + */ + chunk_t sent_init; + + /** + * Current EAP method processing + */ + eap_method_t *method; + + /** + * MSK used to build and verify auth payload + */ + chunk_t msk; + + /** + * EAP authentication method completed successfully + */ + bool eap_complete; + + /** + * Set if we require mutual EAP due EAP-only authentication + */ + bool require_mutual; + + /** + * authentication payload verified successfully + */ + bool auth_complete; + + /** + * generated EAP payload + */ + eap_payload_t *eap_payload; + + /** + * EAP identity of peer + */ + identification_t *eap_identity; +}; + +/** + * load an EAP method + */ +static eap_method_t *load_method(private_eap_authenticator_t *this, + eap_type_t type, u_int32_t vendor, eap_role_t role) +{ + identification_t *server, *peer; + + if (role == EAP_SERVER) + { + server = this->ike_sa->get_my_id(this->ike_sa); + peer = this->ike_sa->get_other_id(this->ike_sa); + } + else + { + server = this->ike_sa->get_other_id(this->ike_sa); + peer = this->ike_sa->get_my_id(this->ike_sa); + } + if (this->eap_identity) + { + peer = this->eap_identity; + } + return charon->eap->create_instance(charon->eap, type, vendor, + role, server, peer); +} + +/** + * Initiate EAP conversation as server + */ +static eap_payload_t* server_initiate_eap(private_eap_authenticator_t *this, + bool do_identity) +{ + auth_cfg_t *auth; + eap_type_t type; + identification_t *id; + u_int32_t vendor; + eap_payload_t *out; + char *action; + + auth = this->ike_sa->get_auth_cfg(this->ike_sa, FALSE); + + /* initiate EAP-Identity exchange if required */ + if (!this->eap_identity && do_identity) + { + id = auth->get(auth, AUTH_RULE_EAP_IDENTITY); + if (id) + { + this->method = load_method(this, EAP_IDENTITY, 0, EAP_SERVER); + if (this->method) + { + if (this->method->initiate(this->method, &out) == NEED_MORE) + { + DBG1(DBG_IKE, "initiating EAP-Identity request"); + return out; + } + this->method->destroy(this->method); + } + DBG1(DBG_IKE, "EAP-Identity request configured, but not supported"); + } + } + /* invoke real EAP method */ + type = (uintptr_t)auth->get(auth, AUTH_RULE_EAP_TYPE); + vendor = (uintptr_t)auth->get(auth, AUTH_RULE_EAP_VENDOR); + action = "loading"; + this->method = load_method(this, type, vendor, EAP_SERVER); + if (this->method) + { + action = "initiating"; + if (this->method->initiate(this->method, &out) == NEED_MORE) + { + if (vendor) + { + DBG1(DBG_IKE, "initiating EAP vendor type %d-%d method", + type, vendor); + } + else + { + DBG1(DBG_IKE, "initiating %N method", eap_type_names, type); + } + return out; + } + } + if (vendor) + { + DBG1(DBG_IKE, "%s EAP vendor type %d-%d method failed", + action, type, vendor); + } + else + { + DBG1(DBG_IKE, "%s %N method failed", action, eap_type_names, type); + } + return eap_payload_create_code(EAP_FAILURE, 0); +} + +/** + * Replace the existing EAP-Identity in other auth config + */ +static void replace_eap_identity(private_eap_authenticator_t *this) +{ + enumerator_t *enumerator; + auth_rule_t rule; + auth_cfg_t *cfg; + void *ptr; + + cfg = this->ike_sa->get_auth_cfg(this->ike_sa, FALSE); + enumerator = cfg->create_enumerator(cfg); + while (enumerator->enumerate(enumerator, &rule, &ptr)) + { + if (rule == AUTH_RULE_EAP_IDENTITY) + { + cfg->replace(cfg, enumerator, AUTH_RULE_EAP_IDENTITY, + this->eap_identity->clone(this->eap_identity)); + break; + } + } + enumerator->destroy(enumerator); +} + +/** + * Handle EAP exchange as server + */ +static eap_payload_t* server_process_eap(private_eap_authenticator_t *this, + eap_payload_t *in) +{ + eap_type_t type, received_type; + u_int32_t vendor, received_vendor; + eap_payload_t *out; + auth_cfg_t *cfg; + + if (in->get_code(in) != EAP_RESPONSE) + { + DBG1(DBG_IKE, "received %N, sending %N", + eap_code_names, in->get_code(in), eap_code_names, EAP_FAILURE); + return eap_payload_create_code(EAP_FAILURE, in->get_identifier(in)); + } + + type = this->method->get_type(this->method, &vendor); + received_type = in->get_type(in, &received_vendor); + if (type != received_type || vendor != received_vendor) + { + if (received_vendor == 0 && received_type == EAP_NAK) + { + DBG1(DBG_IKE, "received %N, sending %N", + eap_type_names, EAP_NAK, eap_code_names, EAP_FAILURE); + } + else + { + DBG1(DBG_IKE, "received invalid EAP response, sending %N", + eap_code_names, EAP_FAILURE); + } + return eap_payload_create_code(EAP_FAILURE, in->get_identifier(in)); + } + + switch (this->method->process(this->method, in, &out)) + { + case NEED_MORE: + return out; + case SUCCESS: + if (!vendor && type == EAP_IDENTITY) + { + chunk_t data; + + if (this->method->get_msk(this->method, &data) == SUCCESS) + { + this->eap_identity = identification_create_from_data(data); + DBG1(DBG_IKE, "received EAP identity '%Y'", + this->eap_identity); + replace_eap_identity(this); + } + /* restart EAP exchange, but with real method */ + this->method->destroy(this->method); + return server_initiate_eap(this, FALSE); + } + if (this->method->get_msk(this->method, &this->msk) == SUCCESS) + { + this->msk = chunk_clone(this->msk); + } + if (vendor) + { + DBG1(DBG_IKE, "EAP vendor specific method %d-%d succeeded, " + "%sMSK established", type, vendor, + this->msk.ptr ? "" : "no "); + } + else + { + DBG1(DBG_IKE, "EAP method %N succeeded, %sMSK established", + eap_type_names, type, this->msk.ptr ? "" : "no "); + } + this->ike_sa->set_condition(this->ike_sa, COND_EAP_AUTHENTICATED, + TRUE); + cfg = this->ike_sa->get_auth_cfg(this->ike_sa, FALSE); + cfg->add(cfg, AUTH_RULE_EAP_TYPE, type); + if (vendor) + { + cfg->add(cfg, AUTH_RULE_EAP_VENDOR, vendor); + } + this->eap_complete = TRUE; + return eap_payload_create_code(EAP_SUCCESS, in->get_identifier(in)); + case FAILED: + default: + if (vendor) + { + DBG1(DBG_IKE, "EAP vendor specific method %d-%d failed for " + "peer %Y", type, vendor, + this->ike_sa->get_other_id(this->ike_sa)); + } + else + { + DBG1(DBG_IKE, "EAP method %N failed for peer %Y", + eap_type_names, type, + this->ike_sa->get_other_id(this->ike_sa)); + } + return eap_payload_create_code(EAP_FAILURE, in->get_identifier(in)); + } +} + +/** + * Processing method for a peer + */ +static eap_payload_t* client_process_eap(private_eap_authenticator_t *this, + eap_payload_t *in) +{ + eap_type_t type; + u_int32_t vendor; + auth_cfg_t *auth; + eap_payload_t *out; + identification_t *id; + + type = in->get_type(in, &vendor); + + if (!vendor && type == EAP_IDENTITY) + { + DESTROY_IF(this->eap_identity); + auth = this->ike_sa->get_auth_cfg(this->ike_sa, TRUE); + id = auth->get(auth, AUTH_RULE_EAP_IDENTITY); + if (!id || id->get_type(id) == ID_ANY) + { + id = this->ike_sa->get_my_id(this->ike_sa); + } + DBG1(DBG_IKE, "server requested %N, sending '%Y'", + eap_type_names, type, id); + this->eap_identity = id->clone(id); + + this->method = load_method(this, type, vendor, EAP_PEER); + if (this->method) + { + if (this->method->process(this->method, in, &out) == SUCCESS) + { + this->method->destroy(this->method); + this->method = NULL; + return out; + } + this->method->destroy(this->method); + this->method = NULL; + } + DBG1(DBG_IKE, "%N not supported, sending EAP_NAK", + eap_type_names, type); + return eap_payload_create_nak(in->get_identifier(in)); + } + if (this->method == NULL) + { + if (vendor) + { + DBG1(DBG_IKE, "server requested vendor specific EAP method %d-%d", + type, vendor); + } + else + { + DBG1(DBG_IKE, "server requested %N authentication", + eap_type_names, type); + } + this->method = load_method(this, type, vendor, EAP_PEER); + if (!this->method) + { + DBG1(DBG_IKE, "EAP method not supported, sending EAP_NAK"); + return eap_payload_create_nak(in->get_identifier(in)); + } + } + + type = this->method->get_type(this->method, &vendor); + + if (this->method->process(this->method, in, &out) == NEED_MORE) + { /* client methods should never return SUCCESS */ + return out; + } + + if (vendor) + { + DBG1(DBG_IKE, "vendor specific EAP method %d-%d failed", type, vendor); + } + else + { + DBG1(DBG_IKE, "%N method failed", eap_type_names, type); + } + return NULL; +} + +/** + * Verify AUTH payload + */ +static bool verify_auth(private_eap_authenticator_t *this, message_t *message, + chunk_t nonce, chunk_t init) +{ + auth_payload_t *auth_payload; + chunk_t auth_data, recv_auth_data; + identification_t *other_id; + auth_cfg_t *auth; + keymat_t *keymat; + + auth_payload = (auth_payload_t*)message->get_payload(message, + AUTHENTICATION); + if (!auth_payload) + { + DBG1(DBG_IKE, "AUTH payload missing"); + return FALSE; + } + other_id = this->ike_sa->get_other_id(this->ike_sa); + keymat = this->ike_sa->get_keymat(this->ike_sa); + auth_data = keymat->get_psk_sig(keymat, TRUE, init, nonce, + this->msk, other_id); + recv_auth_data = auth_payload->get_data(auth_payload); + if (!auth_data.len || !chunk_equals(auth_data, recv_auth_data)) + { + DBG1(DBG_IKE, "verification of AUTH payload with%s EAP MSK failed", + this->msk.ptr ? "" : "out"); + chunk_free(&auth_data); + return FALSE; + } + chunk_free(&auth_data); + + DBG1(DBG_IKE, "authentication of '%Y' with %N successful", + other_id, auth_class_names, AUTH_CLASS_EAP); + this->auth_complete = TRUE; + auth = this->ike_sa->get_auth_cfg(this->ike_sa, FALSE); + auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_EAP); + return TRUE; +} + +/** + * Build AUTH payload + */ +static void build_auth(private_eap_authenticator_t *this, message_t *message, + chunk_t nonce, chunk_t init) +{ + auth_payload_t *auth_payload; + identification_t *my_id; + chunk_t auth_data; + keymat_t *keymat; + + my_id = this->ike_sa->get_my_id(this->ike_sa); + keymat = this->ike_sa->get_keymat(this->ike_sa); + + DBG1(DBG_IKE, "authentication of '%Y' (myself) with %N", + my_id, auth_class_names, AUTH_CLASS_EAP); + + auth_data = keymat->get_psk_sig(keymat, FALSE, init, nonce, this->msk, my_id); + auth_payload = auth_payload_create(); + auth_payload->set_auth_method(auth_payload, AUTH_PSK); + auth_payload->set_data(auth_payload, auth_data); + message->add_payload(message, (payload_t*)auth_payload); + chunk_free(&auth_data); +} + +/** + * Implementation of authenticator_t.process for a server + */ +static status_t process_server(private_eap_authenticator_t *this, + message_t *message) +{ + eap_payload_t *eap_payload; + + if (this->eap_complete) + { + if (!verify_auth(this, message, this->sent_nonce, this->received_init)) + { + return FAILED; + } + return NEED_MORE; + } + + if (!this->method) + { + this->eap_payload = server_initiate_eap(this, TRUE); + } + else + { + eap_payload = (eap_payload_t*)message->get_payload(message, + EXTENSIBLE_AUTHENTICATION); + if (!eap_payload) + { + return FAILED; + } + this->eap_payload = server_process_eap(this, eap_payload); + } + return NEED_MORE; +} + +/** + * Implementation of authenticator_t.build for a server + */ +static status_t build_server(private_eap_authenticator_t *this, + message_t *message) +{ + if (this->eap_payload) + { + eap_code_t code; + + code = this->eap_payload->get_code(this->eap_payload); + message->add_payload(message, (payload_t*)this->eap_payload); + this->eap_payload = NULL; + if (code == EAP_FAILURE) + { + return FAILED; + } + return NEED_MORE; + } + if (this->eap_complete && this->auth_complete) + { + build_auth(this, message, this->received_nonce, this->sent_init); + return SUCCESS; + } + return FAILED; +} + +/** + * Implementation of authenticator_t.process for a client + */ +static status_t process_client(private_eap_authenticator_t *this, + message_t *message) +{ + eap_payload_t *eap_payload; + + if (this->eap_complete) + { + if (!verify_auth(this, message, this->sent_nonce, this->received_init)) + { + return FAILED; + } + if (this->require_mutual && !this->method->is_mutual(this->method)) + { /* we require mutual authentication due to EAP-only */ + u_int32_t vendor; + + DBG1(DBG_IKE, "EAP-only authentication requires a mutual and " + "MSK deriving EAP method, but %N is not", + eap_type_names, this->method->get_type(this->method, &vendor)); + return FAILED; + } + return SUCCESS; + } + + eap_payload = (eap_payload_t*)message->get_payload(message, + EXTENSIBLE_AUTHENTICATION); + if (eap_payload) + { + switch (eap_payload->get_code(eap_payload)) + { + case EAP_REQUEST: + { + this->eap_payload = client_process_eap(this, eap_payload); + if (this->eap_payload) + { + return NEED_MORE; + } + return FAILED; + } + case EAP_SUCCESS: + { + eap_type_t type; + u_int32_t vendor; + auth_cfg_t *cfg; + + if (this->method->get_msk(this->method, &this->msk) == SUCCESS) + { + this->msk = chunk_clone(this->msk); + } + type = this->method->get_type(this->method, &vendor); + if (vendor) + { + DBG1(DBG_IKE, "EAP vendor specific method %d-%d succeeded, " + "%sMSK established", type, vendor, + this->msk.ptr ? "" : "no "); + } + else + { + DBG1(DBG_IKE, "EAP method %N succeeded, %sMSK established", + eap_type_names, type, this->msk.ptr ? "" : "no "); + } + cfg = this->ike_sa->get_auth_cfg(this->ike_sa, TRUE); + cfg->add(cfg, AUTH_RULE_EAP_TYPE, type); + if (vendor) + { + cfg->add(cfg, AUTH_RULE_EAP_VENDOR, vendor); + } + this->eap_complete = TRUE; + return NEED_MORE; + } + case EAP_FAILURE: + default: + { + DBG1(DBG_IKE, "received %N, EAP authentication failed", + eap_code_names, eap_payload->get_code(eap_payload)); + return FAILED; + } + } + } + return FAILED; +} + +/** + * Implementation of authenticator_t.build for a client + */ +static status_t build_client(private_eap_authenticator_t *this, + message_t *message) +{ + if (this->eap_payload) + { + message->add_payload(message, (payload_t*)this->eap_payload); + this->eap_payload = NULL; + return NEED_MORE; + } + if (this->eap_complete) + { + build_auth(this, message, this->received_nonce, this->sent_init); + return NEED_MORE; + } + return NEED_MORE; +} + +/** + * Implementation of authenticator_t.is_mutual. + */ +static bool is_mutual(private_eap_authenticator_t *this) +{ + /* we don't know yet, but insist on it after EAP is complete */ + this->require_mutual = TRUE; + return TRUE; +} + +/** + * Implementation of authenticator_t.destroy. + */ +static void destroy(private_eap_authenticator_t *this) +{ + DESTROY_IF(this->method); + DESTROY_IF(this->eap_payload); + DESTROY_IF(this->eap_identity); + chunk_free(&this->msk); + free(this); +} + +/* + * Described in header. + */ +eap_authenticator_t *eap_authenticator_create_builder(ike_sa_t *ike_sa, + chunk_t received_nonce, chunk_t sent_nonce, + chunk_t received_init, chunk_t sent_init) +{ + private_eap_authenticator_t *this = malloc_thing(private_eap_authenticator_t); + + this->public.authenticator.build = (status_t(*)(authenticator_t*, message_t *message))build_client; + this->public.authenticator.process = (status_t(*)(authenticator_t*, message_t *message))process_client; + this->public.authenticator.is_mutual = (bool(*)(authenticator_t*))is_mutual; + this->public.authenticator.destroy = (void(*)(authenticator_t*))destroy; + + this->ike_sa = ike_sa; + this->received_init = received_init; + this->received_nonce = received_nonce; + this->sent_init = sent_init; + this->sent_nonce = sent_nonce; + this->msk = chunk_empty; + this->method = NULL; + this->eap_payload = NULL; + this->eap_complete = FALSE; + this->auth_complete = FALSE; + this->eap_identity = NULL; + this->require_mutual = FALSE; + + return &this->public; +} + +/* + * Described in header. + */ +eap_authenticator_t *eap_authenticator_create_verifier(ike_sa_t *ike_sa, + chunk_t received_nonce, chunk_t sent_nonce, + chunk_t received_init, chunk_t sent_init) +{ + private_eap_authenticator_t *this = malloc_thing(private_eap_authenticator_t); + + this->public.authenticator.build = (status_t(*)(authenticator_t*, message_t *messageh))build_server; + this->public.authenticator.process = (status_t(*)(authenticator_t*, message_t *message))process_server; + this->public.authenticator.is_mutual = (bool(*)(authenticator_t*))is_mutual; + this->public.authenticator.destroy = (void(*)(authenticator_t*))destroy; + + this->ike_sa = ike_sa; + this->received_init = received_init; + this->received_nonce = received_nonce; + this->sent_init = sent_init; + this->sent_nonce = sent_nonce; + this->msk = chunk_empty; + this->method = NULL; + this->eap_payload = NULL; + this->eap_complete = FALSE; + this->auth_complete = FALSE; + this->eap_identity = NULL; + this->require_mutual = FALSE; + + return &this->public; +} + diff --git a/src/libcharon/sa/authenticators/eap_authenticator.h b/src/libcharon/sa/authenticators/eap_authenticator.h new file mode 100644 index 000000000..41eb6a8c9 --- /dev/null +++ b/src/libcharon/sa/authenticators/eap_authenticator.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2006-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. + */ + +/** + * @defgroup eap_authenticator eap_authenticator + * @{ @ingroup authenticators + */ + +#ifndef EAP_AUTHENTICATOR_H_ +#define EAP_AUTHENTICATOR_H_ + +typedef struct eap_authenticator_t eap_authenticator_t; + +#include <sa/authenticators/authenticator.h> + +/** + * Implementation of authenticator_t using EAP authentication. + * + * Authentication using EAP involves the most complex authenticator. It stays + * alive over multiple ike_auth transactions and handles multiple EAP + * messages. + * + * @verbatim + ike_sa_init + -------------------------> + <------------------------- + followed by multiple ike_auth: + + +--------+ +--------+ + | EAP | IDi, [IDr,] SA, TS | EAP | + | client | ---------------------------> | server | + | | ID, AUTH, EAP | | + | | <--------------------------- | | + | | EAP | | + | | ---------------------------> | | + | | EAP | | + | | <--------------------------- | | + | | EAP | | + | | ---------------------------> | | + | | EAP(SUCCESS) | | + | | <--------------------------- | | + | | AUTH | | If EAP establishes + | | ---------------------------> | | a session key, AUTH + | | AUTH, SA, TS | | payloads use this + | | <--------------------------- | | key, not SK_pi/pr + +--------+ +--------+ + + @endverbatim + */ +struct eap_authenticator_t { + + /** + * Implemented authenticator_t interface. + */ + authenticator_t authenticator; +}; + +/** + * Create an authenticator to authenticate against an EAP server. + * + * @param ike_sa associated ike_sa + * @param received_nonce nonce received in IKE_SA_INIT + * @param sent_nonce nonce sent in IKE_SA_INIT + * @param received_init received IKE_SA_INIT message data + * @param sent_init sent IKE_SA_INIT message data + * @return EAP authenticator + */ +eap_authenticator_t *eap_authenticator_create_builder(ike_sa_t *ike_sa, + chunk_t received_nonce, chunk_t sent_nonce, + chunk_t received_init, chunk_t sent_init); + +/** + * Create an authenticator to authenticate EAP clients. + * + * @param ike_sa associated ike_sa + * @param received_nonce nonce received in IKE_SA_INIT + * @param sent_nonce nonce sent in IKE_SA_INIT + * @param received_init received IKE_SA_INIT message data + * @param sent_init sent IKE_SA_INIT message data + * @return EAP authenticator + */ +eap_authenticator_t *eap_authenticator_create_verifier(ike_sa_t *ike_sa, + chunk_t received_nonce, chunk_t sent_nonce, + chunk_t received_init, chunk_t sent_init); + +#endif /** EAP_AUTHENTICATOR_H_ @}*/ diff --git a/src/libcharon/sa/authenticators/psk_authenticator.c b/src/libcharon/sa/authenticators/psk_authenticator.c new file mode 100644 index 000000000..67197d690 --- /dev/null +++ b/src/libcharon/sa/authenticators/psk_authenticator.c @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2005-2009 Martin Willi + * Copyright (C) 2005 Jan Hutter + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "psk_authenticator.h" + +#include <daemon.h> +#include <encoding/payloads/auth_payload.h> + +typedef struct private_psk_authenticator_t private_psk_authenticator_t; + +/** + * Private data of an psk_authenticator_t object. + */ +struct private_psk_authenticator_t { + + /** + * Public authenticator_t interface. + */ + psk_authenticator_t public; + + /** + * Assigned IKE_SA + */ + ike_sa_t *ike_sa; + + /** + * nonce to include in AUTH calculation + */ + chunk_t nonce; + + /** + * IKE_SA_INIT message data to include in AUTH calculation + */ + chunk_t ike_sa_init; +}; + +/* + * Implementation of authenticator_t.build for builder + */ +static status_t build(private_psk_authenticator_t *this, message_t *message) +{ + identification_t *my_id, *other_id; + auth_payload_t *auth_payload; + shared_key_t *key; + chunk_t auth_data; + keymat_t *keymat; + + keymat = this->ike_sa->get_keymat(this->ike_sa); + my_id = this->ike_sa->get_my_id(this->ike_sa); + other_id = this->ike_sa->get_other_id(this->ike_sa); + DBG1(DBG_IKE, "authentication of '%Y' (myself) with %N", + my_id, auth_method_names, AUTH_PSK); + key = charon->credentials->get_shared(charon->credentials, SHARED_IKE, + my_id, other_id); + if (key == NULL) + { + DBG1(DBG_IKE, "no shared key found for '%Y' - '%Y'", my_id, other_id); + return NOT_FOUND; + } + auth_data = keymat->get_psk_sig(keymat, FALSE, this->ike_sa_init, + this->nonce, key->get_key(key), my_id); + key->destroy(key); + DBG2(DBG_IKE, "successfully created shared key MAC"); + auth_payload = auth_payload_create(); + auth_payload->set_auth_method(auth_payload, AUTH_PSK); + auth_payload->set_data(auth_payload, auth_data); + chunk_free(&auth_data); + message->add_payload(message, (payload_t*)auth_payload); + + return SUCCESS; +} + +/** + * Implementation of authenticator_t.process for verifier + */ +static status_t process(private_psk_authenticator_t *this, message_t *message) +{ + chunk_t auth_data, recv_auth_data; + identification_t *my_id, *other_id; + auth_payload_t *auth_payload; + auth_cfg_t *auth; + shared_key_t *key; + enumerator_t *enumerator; + bool authenticated = FALSE; + int keys_found = 0; + keymat_t *keymat; + + auth_payload = (auth_payload_t*)message->get_payload(message, AUTHENTICATION); + if (!auth_payload) + { + return FAILED; + } + keymat = this->ike_sa->get_keymat(this->ike_sa); + recv_auth_data = auth_payload->get_data(auth_payload); + my_id = this->ike_sa->get_my_id(this->ike_sa); + other_id = this->ike_sa->get_other_id(this->ike_sa); + enumerator = charon->credentials->create_shared_enumerator( + charon->credentials, SHARED_IKE, my_id, other_id); + while (!authenticated && enumerator->enumerate(enumerator, &key, NULL, NULL)) + { + keys_found++; + + auth_data = keymat->get_psk_sig(keymat, TRUE, this->ike_sa_init, + this->nonce, key->get_key(key), other_id); + if (auth_data.len && chunk_equals(auth_data, recv_auth_data)) + { + DBG1(DBG_IKE, "authentication of '%Y' with %N successful", + other_id, auth_method_names, AUTH_PSK); + authenticated = TRUE; + } + chunk_free(&auth_data); + } + enumerator->destroy(enumerator); + + if (!authenticated) + { + if (keys_found == 0) + { + DBG1(DBG_IKE, "no shared key found for '%Y' - '%Y'", my_id, other_id); + return NOT_FOUND; + } + DBG1(DBG_IKE, "tried %d shared key%s for '%Y' - '%Y', but MAC mismatched", + keys_found, keys_found == 1 ? "" : "s", my_id, other_id); + return FAILED; + } + + auth = this->ike_sa->get_auth_cfg(this->ike_sa, FALSE); + auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PSK); + return SUCCESS; +} + +/** + * Implementation of authenticator_t.process for builder + * Implementation of authenticator_t.build for verifier + */ +static status_t return_failed() +{ + return FAILED; +} + +/** + * Implementation of authenticator_t.destroy. + */ +static void destroy(private_psk_authenticator_t *this) +{ + free(this); +} + +/* + * Described in header. + */ +psk_authenticator_t *psk_authenticator_create_builder(ike_sa_t *ike_sa, + chunk_t received_nonce, chunk_t sent_init) +{ + private_psk_authenticator_t *this = malloc_thing(private_psk_authenticator_t); + + this->public.authenticator.build = (status_t(*)(authenticator_t*, message_t *message))build; + this->public.authenticator.process = (status_t(*)(authenticator_t*, message_t *message))return_failed; + this->public.authenticator.is_mutual = (bool(*)(authenticator_t*))return_false; + this->public.authenticator.destroy = (void(*)(authenticator_t*))destroy; + + this->ike_sa = ike_sa; + this->ike_sa_init = sent_init; + this->nonce = received_nonce; + + return &this->public; +} + +/* + * Described in header. + */ +psk_authenticator_t *psk_authenticator_create_verifier(ike_sa_t *ike_sa, + chunk_t sent_nonce, chunk_t received_init) +{ + private_psk_authenticator_t *this = malloc_thing(private_psk_authenticator_t); + + this->public.authenticator.build = (status_t(*)(authenticator_t*, message_t *messageh))return_failed; + this->public.authenticator.process = (status_t(*)(authenticator_t*, message_t *message))process; + this->public.authenticator.is_mutual = (bool(*)(authenticator_t*))return_false; + this->public.authenticator.destroy = (void(*)(authenticator_t*))destroy; + + this->ike_sa = ike_sa; + this->ike_sa_init = received_init; + this->nonce = sent_nonce; + + return &this->public; +} + diff --git a/src/libcharon/sa/authenticators/psk_authenticator.h b/src/libcharon/sa/authenticators/psk_authenticator.h new file mode 100644 index 000000000..0fab11095 --- /dev/null +++ b/src/libcharon/sa/authenticators/psk_authenticator.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2006-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. + */ + +/** + * @defgroup psk_authenticator psk_authenticator + * @{ @ingroup authenticators + */ + +#ifndef PSK_AUTHENTICATOR_H_ +#define PSK_AUTHENTICATOR_H_ + +typedef struct psk_authenticator_t psk_authenticator_t; + +#include <sa/authenticators/authenticator.h> + +/** + * Implementation of authenticator_t using pre-shared keys. + */ +struct psk_authenticator_t { + + /** + * Implemented authenticator_t interface. + */ + authenticator_t authenticator; +}; + +/** + * Create an authenticator to build PSK signatures. + * + * @param ike_sa associated ike_sa + * @param received_nonce nonce received in IKE_SA_INIT + * @param sent_init sent IKE_SA_INIT message data + * @return PSK authenticator + */ +psk_authenticator_t *psk_authenticator_create_builder(ike_sa_t *ike_sa, + chunk_t received_nonce, chunk_t sent_init); + +/** + * Create an authenticator to verify PSK signatures. + * + * @param ike_sa associated ike_sa + * @param sent_nonce nonce sent in IKE_SA_INIT + * @param received_init received IKE_SA_INIT message data + * @return PSK authenticator + */ +psk_authenticator_t *psk_authenticator_create_verifier(ike_sa_t *ike_sa, + chunk_t sent_nonce, chunk_t received_init); + +#endif /** PSK_AUTHENTICATOR_H_ @}*/ diff --git a/src/libcharon/sa/authenticators/pubkey_authenticator.c b/src/libcharon/sa/authenticators/pubkey_authenticator.c new file mode 100644 index 000000000..f1dca2702 --- /dev/null +++ b/src/libcharon/sa/authenticators/pubkey_authenticator.c @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2008 Tobias Brunner + * Copyright (C) 2005-2009 Martin Willi + * Copyright (C) 2005 Jan Hutter + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "pubkey_authenticator.h" + +#include <daemon.h> +#include <encoding/payloads/auth_payload.h> + +typedef struct private_pubkey_authenticator_t private_pubkey_authenticator_t; + +/** + * Private data of an pubkey_authenticator_t object. + */ +struct private_pubkey_authenticator_t { + + /** + * Public authenticator_t interface. + */ + pubkey_authenticator_t public; + + /** + * Assigned IKE_SA + */ + ike_sa_t *ike_sa; + + /** + * nonce to include in AUTH calculation + */ + chunk_t nonce; + + /** + * IKE_SA_INIT message data to include in AUTH calculation + */ + chunk_t ike_sa_init; +}; + +/** + * Implementation of authenticator_t.build for builder + */ +static status_t build(private_pubkey_authenticator_t *this, message_t *message) +{ + chunk_t octets, auth_data; + status_t status = FAILED; + private_key_t *private; + identification_t *id; + auth_cfg_t *auth; + auth_payload_t *auth_payload; + auth_method_t auth_method; + signature_scheme_t scheme; + keymat_t *keymat; + + id = this->ike_sa->get_my_id(this->ike_sa); + auth = this->ike_sa->get_auth_cfg(this->ike_sa, TRUE); + private = charon->credentials->get_private(charon->credentials, KEY_ANY, + id, auth); + if (private == NULL) + { + DBG1(DBG_IKE, "no private key found for '%Y'", id); + return NOT_FOUND; + } + + switch (private->get_type(private)) + { + case KEY_RSA: + /* we currently use always SHA1 for signatures, + * TODO: support other hashes depending on configuration/auth */ + scheme = SIGN_RSA_EMSA_PKCS1_SHA1; + auth_method = AUTH_RSA; + break; + case KEY_ECDSA: + /* we try to deduct the signature scheme from the keysize */ + switch (private->get_keysize(private)) + { + case 32: + scheme = SIGN_ECDSA_256; + auth_method = AUTH_ECDSA_256; + break; + case 48: + scheme = SIGN_ECDSA_384; + auth_method = AUTH_ECDSA_384; + break; + case 66: + scheme = SIGN_ECDSA_521; + auth_method = AUTH_ECDSA_521; + break; + default: + DBG1(DBG_IKE, "%d bit ECDSA private key size not supported", + private->get_keysize(private)); + return status; + } + break; + default: + DBG1(DBG_IKE, "private key of type %N not supported", + key_type_names, private->get_type(private)); + return status; + } + keymat = this->ike_sa->get_keymat(this->ike_sa); + octets = keymat->get_auth_octets(keymat, FALSE, this->ike_sa_init, + this->nonce, id); + if (private->sign(private, scheme, octets, &auth_data)) + { + auth_payload = auth_payload_create(); + auth_payload->set_auth_method(auth_payload, auth_method); + auth_payload->set_data(auth_payload, auth_data); + chunk_free(&auth_data); + message->add_payload(message, (payload_t*)auth_payload); + status = SUCCESS; + } + DBG1(DBG_IKE, "authentication of '%Y' (myself) with %N %s", id, + auth_method_names, auth_method, + (status == SUCCESS)? "successful":"failed"); + chunk_free(&octets); + private->destroy(private); + + return status; +} + +/** + * Implementation of authenticator_t.process for verifier + */ +static status_t process(private_pubkey_authenticator_t *this, message_t *message) +{ + public_key_t *public; + auth_method_t auth_method; + auth_payload_t *auth_payload; + chunk_t auth_data, octets; + identification_t *id; + auth_cfg_t *auth, *current_auth; + enumerator_t *enumerator; + key_type_t key_type = KEY_ECDSA; + signature_scheme_t scheme; + status_t status = NOT_FOUND; + keymat_t *keymat; + + auth_payload = (auth_payload_t*)message->get_payload(message, AUTHENTICATION); + if (!auth_payload) + { + return FAILED; + } + auth_method = auth_payload->get_auth_method(auth_payload); + switch (auth_method) + { + case AUTH_RSA: + /* We currently accept SHA1 signatures only + * TODO: allow other hash algorithms and note it in "auth" */ + key_type = KEY_RSA; + scheme = SIGN_RSA_EMSA_PKCS1_SHA1; + break; + case AUTH_ECDSA_256: + scheme = SIGN_ECDSA_256; + break; + case AUTH_ECDSA_384: + scheme = SIGN_ECDSA_384; + break; + case AUTH_ECDSA_521: + scheme = SIGN_ECDSA_521; + break; + default: + return INVALID_ARG; + } + auth_data = auth_payload->get_data(auth_payload); + id = this->ike_sa->get_other_id(this->ike_sa); + keymat = this->ike_sa->get_keymat(this->ike_sa); + octets = keymat->get_auth_octets(keymat, TRUE, this->ike_sa_init, + this->nonce, id); + auth = this->ike_sa->get_auth_cfg(this->ike_sa, FALSE); + enumerator = charon->credentials->create_public_enumerator( + charon->credentials, key_type, id, auth); + while (enumerator->enumerate(enumerator, &public, ¤t_auth)) + { + if (public->verify(public, scheme, octets, auth_data)) + { + DBG1(DBG_IKE, "authentication of '%Y' with %N successful", + id, auth_method_names, auth_method); + status = SUCCESS; + auth->merge(auth, current_auth, FALSE); + auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PUBKEY); + break; + } + else + { + status = FAILED; + DBG1(DBG_IKE, "signature validation failed, looking for another key"); + } + } + enumerator->destroy(enumerator); + chunk_free(&octets); + if (status == NOT_FOUND) + { + DBG1(DBG_IKE, "no trusted %N public key found for '%Y'", + key_type_names, key_type, id); + } + return status; +} + +/** + * Implementation of authenticator_t.process for builder + * Implementation of authenticator_t.build for verifier + */ +static status_t return_failed() +{ + return FAILED; +} + +/** + * Implementation of authenticator_t.destroy. + */ +static void destroy(private_pubkey_authenticator_t *this) +{ + free(this); +} + +/* + * Described in header. + */ +pubkey_authenticator_t *pubkey_authenticator_create_builder(ike_sa_t *ike_sa, + chunk_t received_nonce, chunk_t sent_init) +{ + private_pubkey_authenticator_t *this = malloc_thing(private_pubkey_authenticator_t); + + this->public.authenticator.build = (status_t(*)(authenticator_t*, message_t *message))build; + this->public.authenticator.process = (status_t(*)(authenticator_t*, message_t *message))return_failed; + this->public.authenticator.is_mutual = (bool(*)(authenticator_t*))return_false; + this->public.authenticator.destroy = (void(*)(authenticator_t*))destroy; + + this->ike_sa = ike_sa; + this->ike_sa_init = sent_init; + this->nonce = received_nonce; + + return &this->public; +} + +/* + * Described in header. + */ +pubkey_authenticator_t *pubkey_authenticator_create_verifier(ike_sa_t *ike_sa, + chunk_t sent_nonce, chunk_t received_init) +{ + private_pubkey_authenticator_t *this = malloc_thing(private_pubkey_authenticator_t); + + this->public.authenticator.build = (status_t(*)(authenticator_t*, message_t *message))return_failed; + this->public.authenticator.process = (status_t(*)(authenticator_t*, message_t *message))process; + this->public.authenticator.is_mutual = (bool(*)(authenticator_t*))return_false; + this->public.authenticator.destroy = (void(*)(authenticator_t*))destroy; + + this->ike_sa = ike_sa; + this->ike_sa_init = received_init; + this->nonce = sent_nonce; + + return &this->public; +} diff --git a/src/libcharon/sa/authenticators/pubkey_authenticator.h b/src/libcharon/sa/authenticators/pubkey_authenticator.h new file mode 100644 index 000000000..be369cb89 --- /dev/null +++ b/src/libcharon/sa/authenticators/pubkey_authenticator.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2008 Tobias Brunner + * Copyright (C) 2006-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. + */ + +/** + * @defgroup pubkey_authenticator pubkey_authenticator + * @{ @ingroup authenticators + */ + +#ifndef PUBKEY_AUTHENTICATOR_H_ +#define PUBKEY_AUTHENTICATOR_H_ + +typedef struct pubkey_authenticator_t pubkey_authenticator_t; + +#include <sa/authenticators/authenticator.h> + +/** + * Implementation of authenticator_t using public key authenitcation. + */ +struct pubkey_authenticator_t { + + /** + * Implemented authenticator_t interface. + */ + authenticator_t authenticator; +}; + +/** + * Create an authenticator to build public key signatures. + * + * @param ike_sa associated ike_sa + * @param received_nonce nonce received in IKE_SA_INIT + * @param sent_init sent IKE_SA_INIT message data + * @return public key authenticator + */ +pubkey_authenticator_t *pubkey_authenticator_create_builder(ike_sa_t *ike_sa, + chunk_t received_nonce, chunk_t sent_init); + +/** + * Create an authenticator to verify public key signatures. + * + * @param ike_sa associated ike_sa + * @param sent_nonce nonce sent in IKE_SA_INIT + * @param received_init received IKE_SA_INIT message data + * @return public key authenticator + */ +pubkey_authenticator_t *pubkey_authenticator_create_verifier(ike_sa_t *ike_sa, + chunk_t sent_nonce, chunk_t received_init); + +#endif /** PUBKEY_AUTHENTICATOR_H_ @}*/ diff --git a/src/libcharon/sa/child_sa.c b/src/libcharon/sa/child_sa.c new file mode 100644 index 000000000..3fdfb51ad --- /dev/null +++ b/src/libcharon/sa/child_sa.c @@ -0,0 +1,1015 @@ +/* + * Copyright (C) 2006-2009 Tobias Brunner + * Copyright (C) 2005-2008 Martin Willi + * Copyright (C) 2006 Daniel Roethlisberger + * Copyright (C) 2005 Jan Hutter + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#define _GNU_SOURCE +#include "child_sa.h" + +#include <stdio.h> +#include <string.h> +#include <time.h> + +#include <daemon.h> + +ENUM(child_sa_state_names, CHILD_CREATED, CHILD_DESTROYING, + "CREATED", + "ROUTED", + "INSTALLING", + "INSTALLED", + "UPDATING", + "REKEYING", + "DELETING", + "DESTROYING", +); + +typedef struct private_child_sa_t private_child_sa_t; + +/** + * Private data of a child_sa_t object. + */ +struct private_child_sa_t { + /** + * Public interface of child_sa_t. + */ + child_sa_t public; + + /** + * address of us + */ + host_t *my_addr; + + /** + * address of remote + */ + host_t *other_addr; + + /** + * our actually used SPI, 0 if unused + */ + u_int32_t my_spi; + + /** + * others used SPI, 0 if unused + */ + u_int32_t other_spi; + + /** + * our Compression Parameter Index (CPI) used, 0 if unused + */ + u_int16_t my_cpi; + + /** + * others Compression Parameter Index (CPI) used, 0 if unused + */ + u_int16_t other_cpi; + + /** + * List for local traffic selectors + */ + linked_list_t *my_ts; + + /** + * List for remote traffic selectors + */ + linked_list_t *other_ts; + + /** + * Protocol used to protect this SA, ESP|AH + */ + protocol_id_t protocol; + + /** + * reqid used for this child_sa + */ + u_int32_t reqid; + + /** + * absolute time when rekeying is scheduled + */ + time_t rekey_time; + + /** + * absolute time when the SA expires + */ + time_t expire_time; + + /** + * state of the CHILD_SA + */ + child_sa_state_t state; + + /** + * Specifies if UDP encapsulation is enabled (NAT traversal) + */ + bool encap; + + /** + * Specifies the IPComp transform used (IPCOMP_NONE if disabled) + */ + ipcomp_transform_t ipcomp; + + /** + * mode this SA uses, tunnel/transport + */ + ipsec_mode_t mode; + + /** + * selected proposal + */ + proposal_t *proposal; + + /** + * config used to create this child + */ + child_cfg_t *config; + + /** + * time of last use in seconds (inbound) + */ + u_int32_t my_usetime; + + /** + * time of last use in seconds (outbound) + */ + u_int32_t other_usetime; + + /** + * last number of inbound bytes + */ + u_int64_t my_usebytes; + + /** + * last number of outbound bytes + */ + u_int64_t other_usebytes; +}; + +/** + * Implementation of child_sa_t.get_name + */ +static char *get_name(private_child_sa_t *this) +{ + return this->config->get_name(this->config); +} + +/** + * Implements child_sa_t.get_reqid + */ +static u_int32_t get_reqid(private_child_sa_t *this) +{ + return this->reqid; +} + +/** + * Implements child_sa_t.get_config + */ +static child_cfg_t* get_config(private_child_sa_t *this) +{ + return this->config; +} + +/** + * Implements child_sa_t.set_state + */ +static void set_state(private_child_sa_t *this, child_sa_state_t state) +{ + charon->bus->child_state_change(charon->bus, &this->public, state); + this->state = state; +} + +/** + * Implements child_sa_t.get_state + */ +static child_sa_state_t get_state(private_child_sa_t *this) +{ + return this->state; +} + +/** + * Implements child_sa_t.get_spi + */ +u_int32_t get_spi(private_child_sa_t *this, bool inbound) +{ + return inbound ? this->my_spi : this->other_spi; +} + +/** + * Implements child_sa_t.get_cpi + */ +u_int16_t get_cpi(private_child_sa_t *this, bool inbound) +{ + return inbound ? this->my_cpi : this->other_cpi; +} + +/** + * Implements child_sa_t.get_protocol + */ +protocol_id_t get_protocol(private_child_sa_t *this) +{ + return this->protocol; +} + +/** + * Implementation of child_sa_t.set_protocol + */ +static void set_protocol(private_child_sa_t *this, protocol_id_t protocol) +{ + this->protocol = protocol; +} + +/** + * Implementation of child_sa_t.get_mode + */ +static ipsec_mode_t get_mode(private_child_sa_t *this) +{ + return this->mode; +} + +/** + * Implementation of child_sa_t.set_mode + */ +static void set_mode(private_child_sa_t *this, ipsec_mode_t mode) +{ + this->mode = mode; +} + +/** + * Implementation of child_sa_t.has_encap + */ +static bool has_encap(private_child_sa_t *this) +{ + return this->encap; +} + +/** + * Implementation of child_sa_t.get_ipcomp + */ +static ipcomp_transform_t get_ipcomp(private_child_sa_t *this) +{ + return this->ipcomp; +} + +/** + * Implementation of child_sa_t.set_ipcomp. + */ +static void set_ipcomp(private_child_sa_t *this, ipcomp_transform_t ipcomp) +{ + this->ipcomp = ipcomp; +} + +/** + * Implementation of child_sa_t.get_proposal + */ +static proposal_t* get_proposal(private_child_sa_t *this) +{ + return this->proposal; +} + +/** + * Implementation of child_sa_t.set_proposal + */ +static void set_proposal(private_child_sa_t *this, proposal_t *proposal) +{ + this->proposal = proposal->clone(proposal); +} + +/** + * Implementation of child_sa_t.get_traffic_selectors. + */ +static linked_list_t *get_traffic_selectors(private_child_sa_t *this, bool local) +{ + return local ? this->my_ts : this->other_ts; +} + +typedef struct policy_enumerator_t policy_enumerator_t; + +/** + * Private policy enumerator + */ +struct policy_enumerator_t { + /** implements enumerator_t */ + enumerator_t public; + /** enumerator over own TS */ + enumerator_t *mine; + /** enumerator over others TS */ + enumerator_t *other; + /** list of others TS, to recreate enumerator */ + linked_list_t *list; + /** currently enumerating TS for "me" side */ + traffic_selector_t *ts; +}; + +/** + * enumerator function of create_policy_enumerator() + */ +static bool policy_enumerate(policy_enumerator_t *this, + traffic_selector_t **my_out, traffic_selector_t **other_out) +{ + traffic_selector_t *other_ts; + + while (this->ts || this->mine->enumerate(this->mine, &this->ts)) + { + if (!this->other->enumerate(this->other, &other_ts)) + { /* end of others list, restart with new of mine */ + this->other->destroy(this->other); + this->other = this->list->create_enumerator(this->list); + this->ts = NULL; + continue; + } + if (this->ts->get_type(this->ts) != other_ts->get_type(other_ts)) + { /* family mismatch */ + continue; + } + if (this->ts->get_protocol(this->ts) && + other_ts->get_protocol(other_ts) && + this->ts->get_protocol(this->ts) != other_ts->get_protocol(other_ts)) + { /* protocol mismatch */ + continue; + } + *my_out = this->ts; + *other_out = other_ts; + return TRUE; + } + return FALSE; +} + +/** + * destroy function of create_policy_enumerator() + */ +static void policy_destroy(policy_enumerator_t *this) +{ + this->mine->destroy(this->mine); + this->other->destroy(this->other); + free(this); +} + +/** + * Implementation of child_sa_t.create_policy_enumerator + */ +static enumerator_t* create_policy_enumerator(private_child_sa_t *this) +{ + policy_enumerator_t *e = malloc_thing(policy_enumerator_t); + + e->public.enumerate = (void*)policy_enumerate; + e->public.destroy = (void*)policy_destroy; + e->mine = this->my_ts->create_enumerator(this->my_ts); + e->other = this->other_ts->create_enumerator(this->other_ts); + e->list = this->other_ts; + e->ts = NULL; + + return &e->public; +} + +/** + * update the cached usebytes + * returns SUCCESS if the usebytes have changed, FAILED if not or no SPIs + * are available, and NOT_SUPPORTED if the kernel interface does not support + * querying the usebytes. + */ +static status_t update_usebytes(private_child_sa_t *this, bool inbound) +{ + status_t status = FAILED; + u_int64_t bytes; + + if (inbound) + { + if (this->my_spi) + { + status = charon->kernel_interface->query_sa( + charon->kernel_interface, + this->other_addr, this->my_addr, + this->my_spi, this->protocol, &bytes); + if (status == SUCCESS) + { + if (bytes > this->my_usebytes) + { + this->my_usebytes = bytes; + return SUCCESS; + } + return FAILED; + } + } + } + else + { + if (this->other_spi) + { + status = charon->kernel_interface->query_sa( + charon->kernel_interface, + this->my_addr, this->other_addr, + this->other_spi, this->protocol, &bytes); + if (status == SUCCESS) + { + if (bytes > this->other_usebytes) + { + this->other_usebytes = bytes; + return SUCCESS; + } + return FAILED; + } + } + } + return status; +} + +/** + * updates the cached usetime + */ +static void update_usetime(private_child_sa_t *this, bool inbound) +{ + enumerator_t *enumerator; + traffic_selector_t *my_ts, *other_ts; + u_int32_t last_use = 0; + + enumerator = create_policy_enumerator(this); + while (enumerator->enumerate(enumerator, &my_ts, &other_ts)) + { + u_int32_t in, out, fwd; + + if (inbound) + { + if (charon->kernel_interface->query_policy(charon->kernel_interface, + other_ts, my_ts, POLICY_IN, &in) == SUCCESS) + { + last_use = max(last_use, in); + } + if (this->mode != MODE_TRANSPORT) + { + if (charon->kernel_interface->query_policy(charon->kernel_interface, + other_ts, my_ts, POLICY_FWD, &fwd) == SUCCESS) + { + last_use = max(last_use, fwd); + } + } + } + else + { + if (charon->kernel_interface->query_policy(charon->kernel_interface, + my_ts, other_ts, POLICY_OUT, &out) == SUCCESS) + { + last_use = max(last_use, out); + } + } + } + enumerator->destroy(enumerator); + + if (last_use == 0) + { + return; + } + if (inbound) + { + this->my_usetime = last_use; + } + else + { + this->other_usetime = last_use; + } +} + +/** + * Implementation of child_sa_t.get_usestats + */ +static void get_usestats(private_child_sa_t *this, bool inbound, + time_t *time, u_int64_t *bytes) +{ + if (update_usebytes(this, inbound) != FAILED) + { + /* there was traffic since last update or the kernel interface + * does not support querying the number of usebytes. + */ + update_usetime(this, inbound); + } + if (time) + { + *time = inbound ? this->my_usetime : this->other_usetime; + } + if (bytes) + { + *bytes = inbound ? this->my_usebytes : this->other_usebytes; + } +} + +/** + * Implementation of child_sa_t.get_lifetime + */ +static time_t get_lifetime(private_child_sa_t *this, bool hard) +{ + return hard ? this->expire_time : this->rekey_time; +} + +/** + * Implementation of child_sa_t.alloc_spi + */ +static u_int32_t alloc_spi(private_child_sa_t *this, protocol_id_t protocol) +{ + if (charon->kernel_interface->get_spi(charon->kernel_interface, + this->other_addr, this->my_addr, protocol, + this->reqid, &this->my_spi) == SUCCESS) + { + return this->my_spi; + } + return 0; +} + +/** + * Implementation of child_sa_t.alloc_cpi + */ +static u_int16_t alloc_cpi(private_child_sa_t *this) +{ + if (charon->kernel_interface->get_cpi(charon->kernel_interface, + this->other_addr, this->my_addr, this->reqid, + &this->my_cpi) == SUCCESS) + { + return this->my_cpi; + } + return 0; +} + +/** + * Implementation of child_sa_t.install + */ +static status_t install(private_child_sa_t *this, chunk_t encr, chunk_t integ, + u_int32_t spi, u_int16_t cpi, bool inbound, + linked_list_t *my_ts, linked_list_t *other_ts) +{ + u_int16_t enc_alg = ENCR_UNDEFINED, int_alg = AUTH_UNDEFINED, size; + traffic_selector_t *src_ts = NULL, *dst_ts = NULL; + time_t now; + lifetime_cfg_t *lifetime; + host_t *src, *dst; + status_t status; + bool update = FALSE; + + /* now we have to decide which spi to use. Use self allocated, if "in", + * or the one in the proposal, if not "in" (others). Additionally, + * source and dest host switch depending on the role */ + if (inbound) + { + dst = this->my_addr; + src = this->other_addr; + if (this->my_spi == spi) + { /* alloc_spi has been called, do an SA update */ + update = TRUE; + } + this->my_spi = spi; + this->my_cpi = cpi; + } + else + { + src = this->my_addr; + dst = this->other_addr; + this->other_spi = spi; + this->other_cpi = cpi; + } + + DBG2(DBG_CHD, "adding %s %N SA", inbound ? "inbound" : "outbound", + protocol_id_names, this->protocol); + + /* send SA down to the kernel */ + DBG2(DBG_CHD, " SPI 0x%.8x, src %H dst %H", ntohl(spi), src, dst); + + this->proposal->get_algorithm(this->proposal, ENCRYPTION_ALGORITHM, + &enc_alg, &size); + this->proposal->get_algorithm(this->proposal, INTEGRITY_ALGORITHM, + &int_alg, &size); + + lifetime = this->config->get_lifetime(this->config); + + now = time_monotonic(NULL); + if (lifetime->time.rekey) + { + this->rekey_time = now + lifetime->time.rekey; + } + if (lifetime->time.life) + { + this->expire_time = now + lifetime->time.life; + } + + if (!lifetime->time.jitter && !inbound) + { /* avoid triggering multiple rekey events */ + lifetime->time.rekey = 0; + } + + if (this->mode == MODE_BEET) + { + /* BEET requires the bound address from the traffic selectors. + * TODO: We add just the first traffic selector for now, as the + * kernel accepts a single TS per SA only */ + if (inbound) + { + my_ts->get_first(my_ts, (void**)&dst_ts); + other_ts->get_first(other_ts, (void**)&src_ts); + } + else + { + my_ts->get_first(my_ts, (void**)&src_ts); + other_ts->get_first(other_ts, (void**)&dst_ts); + } + } + + status = charon->kernel_interface->add_sa(charon->kernel_interface, + src, dst, spi, this->protocol, this->reqid, lifetime, + enc_alg, encr, int_alg, integ, this->mode, this->ipcomp, cpi, + this->encap, update, src_ts, dst_ts); + + free(lifetime); + + return status; +} + +/** + * Implementation of child_sa_t.add_policies + */ +static status_t add_policies(private_child_sa_t *this, + linked_list_t *my_ts_list, linked_list_t *other_ts_list) +{ + enumerator_t *enumerator; + traffic_selector_t *my_ts, *other_ts; + status_t status = SUCCESS; + bool routed = (this->state == CHILD_CREATED); + + /* apply traffic selectors */ + enumerator = my_ts_list->create_enumerator(my_ts_list); + while (enumerator->enumerate(enumerator, &my_ts)) + { + this->my_ts->insert_last(this->my_ts, my_ts->clone(my_ts)); + } + enumerator->destroy(enumerator); + enumerator = other_ts_list->create_enumerator(other_ts_list); + while (enumerator->enumerate(enumerator, &other_ts)) + { + this->other_ts->insert_last(this->other_ts, other_ts->clone(other_ts)); + } + enumerator->destroy(enumerator); + + if (this->config->install_policy(this->config)) + { + /* enumerate pairs of traffic selectors */ + enumerator = create_policy_enumerator(this); + while (enumerator->enumerate(enumerator, &my_ts, &other_ts)) + { + /* install 3 policies: out, in and forward */ + status |= charon->kernel_interface->add_policy(charon->kernel_interface, + this->my_addr, this->other_addr, my_ts, other_ts, POLICY_OUT, + this->other_spi, this->protocol, this->reqid, this->mode, + this->ipcomp, this->other_cpi, routed); + + status |= charon->kernel_interface->add_policy(charon->kernel_interface, + this->other_addr, this->my_addr, other_ts, my_ts, POLICY_IN, + this->my_spi, this->protocol, this->reqid, this->mode, + this->ipcomp, this->my_cpi, routed); + if (this->mode != MODE_TRANSPORT) + { + status |= charon->kernel_interface->add_policy(charon->kernel_interface, + this->other_addr, this->my_addr, other_ts, my_ts, POLICY_FWD, + this->my_spi, this->protocol, this->reqid, this->mode, + this->ipcomp, this->my_cpi, routed); + } + + if (status != SUCCESS) + { + break; + } + } + enumerator->destroy(enumerator); + } + + if (status == SUCCESS && this->state == CHILD_CREATED) + { /* switch to routed state if no SAD entry set up */ + set_state(this, CHILD_ROUTED); + } + return status; +} + +/** + * Implementation of child_sa_t.update. + */ +static status_t update(private_child_sa_t *this, host_t *me, host_t *other, + host_t *vip, bool encap) +{ + child_sa_state_t old; + bool transport_proxy_mode; + + /* anything changed at all? */ + if (me->equals(me, this->my_addr) && + other->equals(other, this->other_addr) && this->encap == encap) + { + return SUCCESS; + } + + old = this->state; + set_state(this, CHILD_UPDATING); + transport_proxy_mode = this->config->use_proxy_mode(this->config) && + this->mode == MODE_TRANSPORT; + + if (!transport_proxy_mode) + { + /* update our (initator) SA */ + if (this->my_spi) + { + if (charon->kernel_interface->update_sa(charon->kernel_interface, + this->my_spi, this->protocol, + this->ipcomp != IPCOMP_NONE ? this->my_cpi : 0, + this->other_addr, this->my_addr, other, me, + this->encap, encap) == NOT_SUPPORTED) + { + return NOT_SUPPORTED; + } + } + + /* update his (responder) SA */ + if (this->other_spi) + { + if (charon->kernel_interface->update_sa(charon->kernel_interface, + this->other_spi, this->protocol, + this->ipcomp != IPCOMP_NONE ? this->other_cpi : 0, + this->my_addr, this->other_addr, me, other, + this->encap, encap) == NOT_SUPPORTED) + { + return NOT_SUPPORTED; + } + } + } + + if (this->config->install_policy(this->config)) + { + /* update policies */ + if (!me->ip_equals(me, this->my_addr) || + !other->ip_equals(other, this->other_addr)) + { + enumerator_t *enumerator; + traffic_selector_t *my_ts, *other_ts; + + /* always use high priorities, as hosts getting updated are INSTALLED */ + enumerator = create_policy_enumerator(this); + while (enumerator->enumerate(enumerator, &my_ts, &other_ts)) + { + /* remove old policies first */ + charon->kernel_interface->del_policy(charon->kernel_interface, + my_ts, other_ts, POLICY_OUT, FALSE); + charon->kernel_interface->del_policy(charon->kernel_interface, + other_ts, my_ts, POLICY_IN, FALSE); + if (this->mode != MODE_TRANSPORT) + { + charon->kernel_interface->del_policy(charon->kernel_interface, + other_ts, my_ts, POLICY_FWD, FALSE); + } + + /* check whether we have to update a "dynamic" traffic selector */ + if (!me->ip_equals(me, this->my_addr) && + my_ts->is_host(my_ts, this->my_addr)) + { + my_ts->set_address(my_ts, me); + } + if (!other->ip_equals(other, this->other_addr) && + other_ts->is_host(other_ts, this->other_addr)) + { + other_ts->set_address(other_ts, other); + } + + /* we reinstall the virtual IP to handle interface roaming + * correctly */ + if (vip) + { + charon->kernel_interface->del_ip(charon->kernel_interface, vip); + charon->kernel_interface->add_ip(charon->kernel_interface, vip, me); + } + + /* reinstall updated policies */ + charon->kernel_interface->add_policy(charon->kernel_interface, + me, other, my_ts, other_ts, POLICY_OUT, this->other_spi, + this->protocol, this->reqid, this->mode, this->ipcomp, + this->other_cpi, FALSE); + charon->kernel_interface->add_policy(charon->kernel_interface, + other, me, other_ts, my_ts, POLICY_IN, this->my_spi, + this->protocol, this->reqid, this->mode, this->ipcomp, + this->my_cpi, FALSE); + if (this->mode != MODE_TRANSPORT) + { + charon->kernel_interface->add_policy(charon->kernel_interface, + other, me, other_ts, my_ts, POLICY_FWD, this->my_spi, + this->protocol, this->reqid, this->mode, this->ipcomp, + this->my_cpi, FALSE); + } + } + enumerator->destroy(enumerator); + } + } + + if (!transport_proxy_mode) + { + /* apply hosts */ + if (!me->equals(me, this->my_addr)) + { + this->my_addr->destroy(this->my_addr); + this->my_addr = me->clone(me); + } + if (!other->equals(other, this->other_addr)) + { + this->other_addr->destroy(this->other_addr); + this->other_addr = other->clone(other); + } + } + + this->encap = encap; + set_state(this, old); + + return SUCCESS; +} + +/** + * Implementation of child_sa_t.destroy. + */ +static void destroy(private_child_sa_t *this) +{ + enumerator_t *enumerator; + traffic_selector_t *my_ts, *other_ts; + bool unrouted = (this->state == CHILD_ROUTED); + + set_state(this, CHILD_DESTROYING); + + /* delete SAs in the kernel, if they are set up */ + if (this->my_spi) + { + /* if CHILD was not established, use PROTO_ESP used during alloc_spi(). + * TODO: For AH support, we have to store protocol specific SPI.s */ + if (this->protocol == PROTO_NONE) + { + this->protocol = PROTO_ESP; + } + charon->kernel_interface->del_sa(charon->kernel_interface, + this->other_addr, this->my_addr, this->my_spi, + this->protocol, this->my_cpi); + } + if (this->other_spi) + { + charon->kernel_interface->del_sa(charon->kernel_interface, + this->my_addr, this->other_addr, this->other_spi, + this->protocol, this->other_cpi); + } + + if (this->config->install_policy(this->config)) + { + /* delete all policies in the kernel */ + enumerator = create_policy_enumerator(this); + while (enumerator->enumerate(enumerator, &my_ts, &other_ts)) + { + charon->kernel_interface->del_policy(charon->kernel_interface, + my_ts, other_ts, POLICY_OUT, unrouted); + charon->kernel_interface->del_policy(charon->kernel_interface, + other_ts, my_ts, POLICY_IN, unrouted); + if (this->mode != MODE_TRANSPORT) + { + charon->kernel_interface->del_policy(charon->kernel_interface, + other_ts, my_ts, POLICY_FWD, unrouted); + } + } + enumerator->destroy(enumerator); + } + + this->my_ts->destroy_offset(this->my_ts, offsetof(traffic_selector_t, destroy)); + this->other_ts->destroy_offset(this->other_ts, offsetof(traffic_selector_t, destroy)); + this->my_addr->destroy(this->my_addr); + this->other_addr->destroy(this->other_addr); + DESTROY_IF(this->proposal); + this->config->destroy(this->config); + free(this); +} + +/* + * Described in header. + */ +child_sa_t * child_sa_create(host_t *me, host_t* other, + child_cfg_t *config, u_int32_t rekey, bool encap) +{ + static u_int32_t reqid = 0; + private_child_sa_t *this = malloc_thing(private_child_sa_t); + + /* public functions */ + this->public.get_name = (char*(*)(child_sa_t*))get_name; + this->public.get_reqid = (u_int32_t(*)(child_sa_t*))get_reqid; + this->public.get_config = (child_cfg_t*(*)(child_sa_t*))get_config; + this->public.get_state = (child_sa_state_t(*)(child_sa_t*))get_state; + this->public.set_state = (void(*)(child_sa_t*,child_sa_state_t))set_state; + this->public.get_spi = (u_int32_t(*)(child_sa_t*, bool))get_spi; + this->public.get_cpi = (u_int16_t(*)(child_sa_t*, bool))get_cpi; + this->public.get_protocol = (protocol_id_t(*)(child_sa_t*))get_protocol; + this->public.set_protocol = (void(*)(child_sa_t*, protocol_id_t protocol))set_protocol; + this->public.get_mode = (ipsec_mode_t(*)(child_sa_t*))get_mode; + this->public.set_mode = (void(*)(child_sa_t*, ipsec_mode_t mode))set_mode; + this->public.get_proposal = (proposal_t*(*)(child_sa_t*))get_proposal; + this->public.set_proposal = (void(*)(child_sa_t*, proposal_t *proposal))set_proposal; + this->public.get_lifetime = (time_t(*)(child_sa_t*, bool))get_lifetime; + this->public.get_usestats = (void(*)(child_sa_t*,bool,time_t*,u_int64_t*))get_usestats; + this->public.has_encap = (bool(*)(child_sa_t*))has_encap; + this->public.get_ipcomp = (ipcomp_transform_t(*)(child_sa_t*))get_ipcomp; + this->public.set_ipcomp = (void(*)(child_sa_t*,ipcomp_transform_t))set_ipcomp; + this->public.alloc_spi = (u_int32_t(*)(child_sa_t*, protocol_id_t protocol))alloc_spi; + this->public.alloc_cpi = (u_int16_t(*)(child_sa_t*))alloc_cpi; + this->public.install = (status_t(*)(child_sa_t*, chunk_t encr, chunk_t integ, u_int32_t spi, u_int16_t cpi, bool inbound, linked_list_t *my_ts_list, linked_list_t *other_ts_list))install; + this->public.update = (status_t (*)(child_sa_t*,host_t*,host_t*,host_t*,bool))update; + this->public.add_policies = (status_t (*)(child_sa_t*, linked_list_t*,linked_list_t*))add_policies; + this->public.get_traffic_selectors = (linked_list_t*(*)(child_sa_t*,bool))get_traffic_selectors; + this->public.create_policy_enumerator = (enumerator_t*(*)(child_sa_t*))create_policy_enumerator; + this->public.destroy = (void(*)(child_sa_t*))destroy; + + /* private data */ + this->my_addr = me->clone(me); + this->other_addr = other->clone(other); + this->my_spi = 0; + this->other_spi = 0; + this->my_cpi = 0; + this->other_cpi = 0; + this->encap = encap; + this->ipcomp = IPCOMP_NONE; + this->state = CHILD_CREATED; + this->my_usetime = 0; + this->other_usetime = 0; + this->my_usebytes = 0; + this->other_usebytes = 0; + /* reuse old reqid if we are rekeying an existing CHILD_SA */ + this->reqid = rekey ? rekey : ++reqid; + this->my_ts = linked_list_create(); + this->other_ts = linked_list_create(); + this->protocol = PROTO_NONE; + this->mode = MODE_TUNNEL; + this->proposal = NULL; + this->rekey_time = 0; + this->expire_time = 0; + this->config = config; + config->get_ref(config); + + /* MIPv6 proxy transport mode sets SA endpoints to TS hosts */ + if (config->get_mode(config) == MODE_TRANSPORT && + config->use_proxy_mode(config)) + { + ts_type_t type; + int family; + chunk_t addr; + host_t *host; + enumerator_t *enumerator; + linked_list_t *my_ts_list, *other_ts_list; + traffic_selector_t *my_ts, *other_ts; + + this->mode = MODE_TRANSPORT; + + my_ts_list = config->get_traffic_selectors(config, TRUE, NULL, me); + enumerator = my_ts_list->create_enumerator(my_ts_list); + if (enumerator->enumerate(enumerator, &my_ts)) + { + if (my_ts->is_host(my_ts, NULL) && + !my_ts->is_host(my_ts, this->my_addr)) + { + type = my_ts->get_type(my_ts); + family = (type == TS_IPV4_ADDR_RANGE) ? AF_INET : AF_INET6; + addr = my_ts->get_from_address(my_ts); + host = host_create_from_chunk(family, addr, 0); + free(addr.ptr); + DBG1(DBG_CHD, "my address: %H is a transport mode proxy for %H", + this->my_addr, host); + this->my_addr->destroy(this->my_addr); + this->my_addr = host; + } + } + enumerator->destroy(enumerator); + my_ts_list->destroy_offset(my_ts_list, offsetof(traffic_selector_t, destroy)); + + other_ts_list = config->get_traffic_selectors(config, FALSE, NULL, other); + enumerator = other_ts_list->create_enumerator(other_ts_list); + if (enumerator->enumerate(enumerator, &other_ts)) + { + if (other_ts->is_host(other_ts, NULL) && + !other_ts->is_host(other_ts, this->other_addr)) + { + type = other_ts->get_type(other_ts); + family = (type == TS_IPV4_ADDR_RANGE) ? AF_INET : AF_INET6; + addr = other_ts->get_from_address(other_ts); + host = host_create_from_chunk(family, addr, 0); + free(addr.ptr); + DBG1(DBG_CHD, "other address: %H is a transport mode proxy for %H", + this->other_addr, host); + this->other_addr->destroy(this->other_addr); + this->other_addr = host; + } + } + enumerator->destroy(enumerator); + other_ts_list->destroy_offset(other_ts_list, offsetof(traffic_selector_t, destroy)); + } + + return &this->public; +} diff --git a/src/libcharon/sa/child_sa.h b/src/libcharon/sa/child_sa.h new file mode 100644 index 000000000..e6c603504 --- /dev/null +++ b/src/libcharon/sa/child_sa.h @@ -0,0 +1,337 @@ +/* + * Copyright (C) 2006-2008 Tobias Brunner + * Copyright (C) 2006-2008 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. + */ + +/** + * @defgroup child_sa child_sa + * @{ @ingroup sa + */ + +#ifndef CHILD_SA_H_ +#define CHILD_SA_H_ + +typedef enum child_sa_state_t child_sa_state_t; +typedef struct child_sa_t child_sa_t; + +#include <library.h> +#include <crypto/prf_plus.h> +#include <encoding/payloads/proposal_substructure.h> +#include <config/proposal.h> +#include <config/child_cfg.h> + +/** + * States of a CHILD_SA + */ +enum child_sa_state_t { + + /** + * Just created, uninstalled CHILD_SA + */ + CHILD_CREATED, + + /** + * Installed SPD, but no SAD entries + */ + CHILD_ROUTED, + + /** + * Installing an in-use CHILD_SA + */ + CHILD_INSTALLING, + + /** + * Installed an in-use CHILD_SA + */ + CHILD_INSTALLED, + + /** + * While updating hosts, in update_hosts() + */ + CHILD_UPDATING, + + /** + * CHILD_SA which is rekeying + */ + CHILD_REKEYING, + + /** + * CHILD_SA in progress of delete + */ + CHILD_DELETING, + + /** + * CHILD_SA object gets destroyed + */ + CHILD_DESTROYING, +}; + +/** + * enum strings for child_sa_state_t. + */ +extern enum_name_t *child_sa_state_names; + +/** + * Represents an IPsec SAs between two hosts. + * + * A child_sa_t contains two SAs. SAs for both + * directions are managed in one child_sa_t object. Both + * SAs and the policies have the same reqid. + * + * The procedure for child sa setup is as follows: + * - A gets SPIs for a all protocols in its proposals via child_sa_t.alloc + * - A send the proposals with the allocated SPIs to B + * - B selects a suitable proposal + * - B allocates an SPI for the selected protocol + * - B calls child_sa_t.install for both, the allocated and received SPI + * - B sends the proposal with the allocated SPI to A + * - A calls child_sa_t.install for both, the allocated and recevied SPI + * + * Once SAs are set up, policies can be added using add_policies. + */ +struct child_sa_t { + + /** + * Get the name of the config this CHILD_SA uses. + * + * @return name + */ + char* (*get_name) (child_sa_t *this); + + /** + * Get the reqid of the CHILD SA. + * + * Every CHILD_SA has a reqid. The kernel uses this ID to + * identify it. + * + * @return reqid of the CHILD SA + */ + u_int32_t (*get_reqid)(child_sa_t *this); + + /** + * Get the config used to set up this child sa. + * + * @return child_cfg + */ + child_cfg_t* (*get_config) (child_sa_t *this); + + /** + * Get the state of the CHILD_SA. + * + * @return CHILD_SA state + */ + child_sa_state_t (*get_state) (child_sa_t *this); + + /** + * Set the state of the CHILD_SA. + * + * @param state state to set on CHILD_SA + */ + void (*set_state) (child_sa_t *this, child_sa_state_t state); + + /** + * Get the SPI of this CHILD_SA. + * + * Set the boolean parameter inbound to TRUE to + * get the SPI for which we receive packets, use + * FALSE to get those we use for sending packets. + * + * @param inbound TRUE to get inbound SPI, FALSE for outbound. + * @return SPI of the CHILD SA + */ + u_int32_t (*get_spi) (child_sa_t *this, bool inbound); + + /** + * Get the CPI of this CHILD_SA. + * + * Set the boolean parameter inbound to TRUE to + * get the CPI for which we receive packets, use + * FALSE to get those we use for sending packets. + * + * @param inbound TRUE to get inbound CPI, FALSE for outbound. + * @return CPI of the CHILD SA + */ + u_int16_t (*get_cpi) (child_sa_t *this, bool inbound); + + /** + * Get the protocol which this CHILD_SA uses to protect traffic. + * + * @return AH | ESP + */ + protocol_id_t (*get_protocol) (child_sa_t *this); + + /** + * Set the negotiated protocol to use for this CHILD_SA. + * + * @param protocol AH | ESP + */ + void (*set_protocol)(child_sa_t *this, protocol_id_t protocol); + + /** + * Get the IPsec mode of this CHILD_SA. + * + * @return TUNNEL | TRANSPORT | BEET + */ + ipsec_mode_t (*get_mode)(child_sa_t *this); + + /** + * Set the negotiated IPsec mode to use. + * + * @param mode TUNNEL | TRANPORT | BEET + */ + void (*set_mode)(child_sa_t *this, ipsec_mode_t mode); + + /** + * Get the used IPComp algorithm. + * + * @return IPComp compression algorithm. + */ + ipcomp_transform_t (*get_ipcomp)(child_sa_t *this); + + /** + * Set the IPComp algorithm to use. + * + * @param ipcomp the IPComp transform to use + */ + void (*set_ipcomp)(child_sa_t *this, ipcomp_transform_t ipcomp); + + /** + * Get the selected proposal. + * + * @return selected proposal + */ + proposal_t* (*get_proposal)(child_sa_t *this); + + /** + * Set the negotiated proposal. + * + * @param proposal selected proposal + */ + void (*set_proposal)(child_sa_t *this, proposal_t *proposal); + + /** + * Check if this CHILD_SA uses UDP encapsulation. + * + * @return TRUE if SA encapsulates ESP packets + */ + bool (*has_encap)(child_sa_t *this); + + /** + * Get the absolute time when the CHILD_SA expires or gets rekeyed. + * + * @param hard TRUE for hard lifetime, FALSE for soft (rekey) lifetime + * @return absolute time + */ + time_t (*get_lifetime)(child_sa_t *this, bool hard); + + /** + * Get last use time and the number of bytes processed. + * + * @param inbound TRUE for inbound traffic, FALSE for outbound + * @param[out] time time of last use in seconds (NULL to ignore) + * @param[out] bytes number of processed bytes (NULL to ignore) + */ + void (*get_usestats)(child_sa_t *this, bool inbound, time_t *time, + u_int64_t *bytes); + + /** + * Get the traffic selectors list added for one side. + * + * @param local TRUE for own traffic selectors, FALSE for remote + * @return list of traffic selectors + */ + linked_list_t* (*get_traffic_selectors) (child_sa_t *this, bool local); + + /** + * Create an enumerator over installed policies. + * + * @return enumerator over pairs of traffic selectors. + */ + enumerator_t* (*create_policy_enumerator)(child_sa_t *this); + + /** + * Allocate an SPI to include in a proposal. + * + * @param protocol protocol to allocate SPI for (ESP|AH) + * @param spi SPI output pointer + * @return SPI, 0 on failure + */ + u_int32_t (*alloc_spi)(child_sa_t *this, protocol_id_t protocol); + + /** + * Allocate a CPI to use for IPComp. + * + * @return CPI, 0 on failure + */ + u_int16_t (*alloc_cpi)(child_sa_t *this); + + /** + * Install an IPsec SA for one direction. + * + * @param encr encryption key, if any + * @param integ integrity key + * @param spi SPI to use, allocated for inbound + * @param cpi CPI to use, allocated for outbound + * @param inbound TRUE to install an inbound SA, FALSE for outbound + * @param my_ts negotiated local traffic selector list + * @param other_ts negotiated remote traffic selector list + * @return SUCCESS or FAILED + */ + status_t (*install)(child_sa_t *this, chunk_t encr, chunk_t integ, + u_int32_t spi, u_int16_t cpi, bool inbound, + linked_list_t *my_ts, linked_list_t *other_ts); + /** + * Install the policies using some traffic selectors. + * + * Supplied lists of traffic_selector_t's specify the policies + * to use for this child sa. + * + * @param my_ts traffic selectors for local site + * @param other_ts traffic selectors for remote site + * @return SUCCESS or FAILED + */ + status_t (*add_policies)(child_sa_t *this, linked_list_t *my_ts_list, + linked_list_t *other_ts_list); + /** + * Update hosts and ecapulation mode in the kernel SAs and policies. + * + * @param me the new local host + * @param other the new remote host + * @param vip virtual IP, if any + * @param TRUE to use UDP encapsulation for NAT traversal + * @return SUCCESS or FAILED + */ + status_t (*update)(child_sa_t *this, host_t *me, host_t *other, + host_t *vip, bool encap); + /** + * Destroys a child_sa. + */ + void (*destroy) (child_sa_t *this); +}; + +/** + * Constructor to create a child SA negotiated with IKE. + * + * @param me own address + * @param other remote address + * @param config config to use for this CHILD_SA + * @param reqid reqid of old CHILD_SA when rekeying, 0 otherwise + * @param encap TRUE to enable UDP encapsulation (NAT traversal) + * @return child_sa_t object + */ +child_sa_t * child_sa_create(host_t *me, host_t *other, child_cfg_t *config, + u_int32_t reqid, bool encap); + +#endif /** CHILD_SA_H_ @}*/ diff --git a/src/libcharon/sa/connect_manager.c b/src/libcharon/sa/connect_manager.c new file mode 100644 index 000000000..b78ba070d --- /dev/null +++ b/src/libcharon/sa/connect_manager.c @@ -0,0 +1,1623 @@ +/* + * Copyright (C) 2007-2008 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 "connect_manager.h" + +#include <math.h> + +#include <daemon.h> +#include <threading/mutex.h> +#include <utils/linked_list.h> +#include <crypto/hashers/hasher.h> + +#include <processing/jobs/callback_job.h> +#include <processing/jobs/initiate_mediation_job.h> +#include <encoding/payloads/endpoint_notify.h> + +/* base timeout + * the check interval is ME_INTERVAL */ +#define ME_INTERVAL 25 /* ms */ +/* retransmission timeout is first ME_INTERVAL for ME_BOOST retransmissions + * then gets reduced to ME_INTERVAL * ME_RETRANS_BASE ^ (sent retransmissions - ME_BOOST). */ +/* number of initial retransmissions sent in short interval */ +#define ME_BOOST 2 +/* base for retransmissions */ +#define ME_RETRANS_BASE 1.8 +/* max number of retransmissions */ +#define ME_MAX_RETRANS 13 + +/* time to wait before the initiator finishes the connectivity checks after + * the first check has succeeded */ +#define ME_WAIT_TO_FINISH 1000 /* ms */ + +typedef struct private_connect_manager_t private_connect_manager_t; + +/** + * Additional private members of connect_manager_t. + */ +struct private_connect_manager_t { + /** + * Public interface of connect_manager_t. + */ + connect_manager_t public; + + /** + * Lock for exclusivly accessing the manager. + */ + mutex_t *mutex; + + /** + * Hasher to generate signatures + */ + hasher_t *hasher; + + /** + * Linked list with initiated mediated connections + */ + linked_list_t *initiated; + + /** + * Linked list with checklists (hash table with connect ID as key would + * be better). + */ + linked_list_t *checklists; +}; + +typedef enum check_state_t check_state_t; + +enum check_state_t { + CHECK_NONE, + CHECK_WAITING, + CHECK_IN_PROGRESS, + CHECK_SUCCEEDED, + CHECK_FAILED +}; + +typedef struct endpoint_pair_t endpoint_pair_t; + +/** + * An entry in the check list. + */ +struct endpoint_pair_t { + /** pair id */ + u_int32_t id; + + /** priority */ + u_int64_t priority; + + /** local endpoint */ + host_t *local; + + /** remote endpoint */ + host_t *remote; + + /** state */ + check_state_t state; + + /** number of retransmissions */ + u_int32_t retransmitted; + + /** the generated packet */ + packet_t *packet; +}; + +/** + * Destroys an endpoint pair + */ +static void endpoint_pair_destroy(endpoint_pair_t *this) +{ + DESTROY_IF(this->local); + DESTROY_IF(this->remote); + DESTROY_IF(this->packet); + free(this); +} + +/** + * Creates a new entry for the list. + */ +static endpoint_pair_t *endpoint_pair_create(endpoint_notify_t *initiator, + endpoint_notify_t *responder, bool initiator_is_local) +{ + endpoint_pair_t *this = malloc_thing(endpoint_pair_t); + + this->id = 0; + + u_int32_t pi = initiator->get_priority(initiator); + u_int32_t pr = responder->get_priority(responder); + this->priority = pow(2, 32) * min(pi, pr) + 2 * max(pi, pr) + (pi > pr ? 1 : 0); + + this->local = initiator_is_local ? initiator->get_base(initiator) + : responder->get_base(responder); + this->local = this->local->clone(this->local); + this->remote = initiator_is_local ? responder->get_host(responder) + : initiator->get_host(initiator); + this->remote = this->remote->clone(this->remote); + + this->state = CHECK_WAITING; + this->retransmitted = 0; + this->packet = NULL; + + return this; +} + + +typedef struct check_list_t check_list_t; + +/** + * An entry in the linked list. + */ +struct check_list_t { + + struct { + /** initiator's id */ + identification_t *id; + + /** initiator's key */ + chunk_t key; + + /** initiator's endpoints */ + linked_list_t *endpoints; + } initiator; + + struct { + /** responder's id */ + identification_t *id; + + /** responder's key */ + chunk_t key; + + /** responder's endpoints */ + linked_list_t *endpoints; + } responder; + + /** connect id */ + chunk_t connect_id; + + /** list of endpoint pairs */ + linked_list_t *pairs; + + /** pairs queued for triggered checks */ + linked_list_t *triggered; + + /** state */ + check_state_t state; + + /** TRUE if this is the initiator */ + bool is_initiator; + + /** TRUE if the initiator is finishing the checks */ + bool is_finishing; + + /** the current sender job */ + job_t *sender; + +}; + +/** + * Destroys a checklist + */ +static void check_list_destroy(check_list_t *this) +{ + DESTROY_IF(this->initiator.id); + DESTROY_IF(this->responder.id); + + chunk_free(&this->connect_id); + chunk_free(&this->initiator.key); + chunk_free(&this->responder.key); + + DESTROY_OFFSET_IF(this->initiator.endpoints, + offsetof(endpoint_notify_t, destroy)); + DESTROY_OFFSET_IF(this->responder.endpoints, + offsetof(endpoint_notify_t, destroy)); + + DESTROY_FUNCTION_IF(this->pairs, (void*)endpoint_pair_destroy); + /* this list contains some of the elements contained in this->pairs */ + DESTROY_IF(this->triggered); + + free(this); +} + +/** + * Creates a new checklist + */ +static check_list_t *check_list_create(identification_t *initiator, + identification_t *responder, + chunk_t connect_id, + chunk_t initiator_key, + linked_list_t *initiator_endpoints, + bool is_initiator) +{ + check_list_t *this = malloc_thing(check_list_t); + + this->connect_id = chunk_clone(connect_id); + + this->initiator.id = initiator->clone(initiator); + this->initiator.key = chunk_clone(initiator_key); + this->initiator.endpoints = initiator_endpoints->clone_offset(initiator_endpoints, offsetof(endpoint_notify_t, clone)); + + this->responder.id = responder->clone(responder); + this->responder.key = chunk_empty; + this->responder.endpoints = NULL; + + this->pairs = linked_list_create(); + this->triggered = linked_list_create(); + this->state = CHECK_NONE; + this->is_initiator = is_initiator; + this->is_finishing = FALSE; + + return this; +} + +typedef struct initiated_t initiated_t; + +/** + * For an initiator, the data stored about initiated mediation connections + */ +struct initiated_t { + /** my id */ + identification_t *id; + + /** peer id */ + identification_t *peer_id; + + /** list of mediated sas */ + linked_list_t *mediated; +}; + +/** + * Destroys a queued initiation + */ +static void initiated_destroy(initiated_t *this) +{ + DESTROY_IF(this->id); + DESTROY_IF(this->peer_id); + this->mediated->destroy_offset(this->mediated, + offsetof(ike_sa_id_t, destroy)); + free(this); +} + +/** + * Creates a queued initiation + */ +static initiated_t *initiated_create(identification_t *id, + identification_t *peer_id) +{ + initiated_t *this = malloc_thing(initiated_t); + + this->id = id->clone(id); + this->peer_id = peer_id->clone(peer_id); + this->mediated = linked_list_create(); + + return this; +} + + +typedef struct check_t check_t; + +/** + * Data exchanged in a connectivity check + */ +struct check_t { + /** message id */ + u_int32_t mid; + + /** source of the connectivity check */ + host_t *src; + + /** destination of the connectivity check */ + host_t *dst; + + /** connect id */ + chunk_t connect_id; + + /** endpoint */ + endpoint_notify_t *endpoint; + + /** raw endpoint payload (to verify the signature) */ + chunk_t endpoint_raw; + + /** connect auth */ + chunk_t auth; +}; + +/** + * Destroys a connectivity check + */ +static void check_destroy(check_t *this) +{ + chunk_free(&this->connect_id); + chunk_free(&this->endpoint_raw); + chunk_free(&this->auth); + DESTROY_IF(this->src); + DESTROY_IF(this->dst); + DESTROY_IF(this->endpoint); + free(this); +} + +/** + * Creates a new connectivity check + */ +static check_t *check_create() +{ + check_t *this = malloc_thing(check_t); + + this->connect_id = chunk_empty; + this->auth = chunk_empty; + this->endpoint_raw = chunk_empty; + this->src = NULL; + this->dst = NULL; + this->endpoint = NULL; + + this->mid = 0; + + return this; +} + +typedef struct callback_data_t callback_data_t; + +/** + * Data required by several callback jobs used in this file + */ +struct callback_data_t { + /** connect manager */ + private_connect_manager_t *connect_manager; + + /** connect id */ + chunk_t connect_id; + + /** message (pair) id */ + u_int32_t mid; +}; + +/** + * Destroys a callback data object + */ +static void callback_data_destroy(callback_data_t *this) +{ + chunk_free(&this->connect_id); + free(this); +} + +/** + * Creates a new callback data object + */ +static callback_data_t *callback_data_create(private_connect_manager_t *connect_manager, + chunk_t connect_id) +{ + callback_data_t *this = malloc_thing(callback_data_t); + this->connect_manager = connect_manager; + this->connect_id = chunk_clone(connect_id); + this->mid = 0; + return this; +} + +/** + * Creates a new retransmission data object + */ +static callback_data_t *retransmit_data_create(private_connect_manager_t *connect_manager, + chunk_t connect_id, u_int32_t mid) +{ + callback_data_t *this = callback_data_create(connect_manager, connect_id); + this->mid = mid; + return this; +} + +typedef struct initiate_data_t initiate_data_t; + +/** + * Data required by the initiate mediated + */ +struct initiate_data_t { + /** checklist */ + check_list_t *checklist; + + /** waiting mediated connections */ + initiated_t *initiated; +}; + +/** + * Destroys a initiate data object + */ +static void initiate_data_destroy(initiate_data_t *this) +{ + check_list_destroy(this->checklist); + initiated_destroy(this->initiated); + free(this); +} + +/** + * Creates a new initiate data object + */ +static initiate_data_t *initiate_data_create(check_list_t *checklist, + initiated_t *initiated) +{ + initiate_data_t *this = malloc_thing(initiate_data_t); + + this->checklist = checklist; + this->initiated = initiated; + + return this; +} + +/** + * Find an initiated connection by the peers' ids + */ +static bool match_initiated_by_ids(initiated_t *current, identification_t *id, + identification_t *peer_id) +{ + return id->equals(id, current->id) && peer_id->equals(peer_id, current->peer_id); +} + +static status_t get_initiated_by_ids(private_connect_manager_t *this, + identification_t *id, + identification_t *peer_id, + initiated_t **initiated) +{ + return this->initiated->find_first(this->initiated, + (linked_list_match_t)match_initiated_by_ids, + (void**)initiated, id, peer_id); +} + +/** + * Removes data about initiated connections + */ +static void remove_initiated(private_connect_manager_t *this, + initiated_t *initiated) +{ + iterator_t *iterator; + initiated_t *current; + + iterator = this->initiated->create_iterator(this->initiated, TRUE); + while (iterator->iterate(iterator, (void**)¤t)) + { + if (current == initiated) + { + iterator->remove(iterator); + break; + } + } + iterator->destroy(iterator); +} + +/** + * Find the checklist with a specific connect ID + */ +static bool match_checklist_by_id(check_list_t *current, chunk_t *connect_id) +{ + return chunk_equals(*connect_id, current->connect_id); +} + +static status_t get_checklist_by_id(private_connect_manager_t *this, + chunk_t connect_id, + check_list_t **check_list) +{ + return this->checklists->find_first(this->checklists, + (linked_list_match_t)match_checklist_by_id, + (void**)check_list, &connect_id); +} + +/** + * Removes a checklist + */ +static void remove_checklist(private_connect_manager_t *this, + check_list_t *checklist) +{ + iterator_t *iterator; + check_list_t *current; + + iterator = this->checklists->create_iterator(this->checklists, TRUE); + while (iterator->iterate(iterator, (void**)¤t)) + { + if (current == checklist) + { + iterator->remove(iterator); + break; + } + } + iterator->destroy(iterator); +} + +/** + * Checks if a list of endpoint_notify_t contains a certain host_t + */ +static bool match_endpoint_by_host(endpoint_notify_t *current, host_t *host) +{ + return host->equals(host, current->get_host(current)); +} + +static status_t endpoints_contain(linked_list_t *endpoints, host_t *host, + endpoint_notify_t **endpoint) +{ + return endpoints->find_first(endpoints, + (linked_list_match_t)match_endpoint_by_host, + (void**)endpoint, host); +} + +/** + * Inserts an endpoint pair into a list of pairs ordered by priority (high to low) + */ +static void insert_pair_by_priority(linked_list_t *pairs, endpoint_pair_t *pair) +{ + iterator_t *iterator; + endpoint_pair_t *current; + bool inserted = FALSE; + + iterator = pairs->create_iterator(pairs, TRUE); + while (iterator->iterate(iterator, (void**)¤t)) + { + if (current->priority < pair->priority) + { + iterator->insert_before(iterator, pair); + inserted = TRUE; + break; + } + } + iterator->destroy(iterator); + + if (!inserted) + { + pairs->insert_last(pairs, pair); + } +} + +/** + * Searches a list of endpoint_pair_t for a pair with specific host_ts + */ +static bool match_pair_by_hosts(endpoint_pair_t *current, host_t *local, + host_t *remote) +{ + return local->equals(local, current->local) && remote->equals(remote, current->remote); +} + +static status_t get_pair_by_hosts(linked_list_t *pairs, host_t *local, + host_t *remote, endpoint_pair_t **pair) +{ + return pairs->find_first(pairs, (linked_list_match_t)match_pair_by_hosts, + (void**)pair, local, remote); +} + +static bool match_pair_by_id(endpoint_pair_t *current, u_int32_t *id) +{ + return current->id == *id; +} + +/** + * Searches for a pair with a specific id + */ +static status_t get_pair_by_id(check_list_t *checklist, u_int32_t id, + endpoint_pair_t **pair) +{ + return checklist->pairs->find_first(checklist->pairs, + (linked_list_match_t)match_pair_by_id, + (void**)pair, &id); +} + +static bool match_succeeded_pair(endpoint_pair_t *current) +{ + return current->state == CHECK_SUCCEEDED; +} + +/** + * Returns the best pair of state CHECK_SUCCEEDED from a checklist. + */ +static status_t get_best_valid_pair(check_list_t *checklist, + endpoint_pair_t **pair) +{ + return checklist->pairs->find_first(checklist->pairs, + (linked_list_match_t)match_succeeded_pair, + (void**)pair); +} + +static bool match_waiting_pair(endpoint_pair_t *current) +{ + return current->state == CHECK_WAITING; +} + +/** + * Returns and *removes* the first triggered pair in state CHECK_WAITING. + */ +static status_t get_triggered_pair(check_list_t *checklist, + endpoint_pair_t **pair) +{ + iterator_t *iterator; + endpoint_pair_t *current; + status_t status = NOT_FOUND; + + iterator = checklist->triggered->create_iterator(checklist->triggered, TRUE); + while (iterator->iterate(iterator, (void**)¤t)) + { + iterator->remove(iterator); + + if (current->state == CHECK_WAITING) + { + if (pair) + { + *pair = current; + } + status = SUCCESS; + break; + } + } + iterator->destroy(iterator); + + return status; +} + +/** + * Prints all the pairs on a checklist + */ +static void print_checklist(check_list_t *checklist) +{ + iterator_t *iterator; + endpoint_pair_t *current; + + DBG1(DBG_IKE, "pairs on checklist %#B:", &checklist->connect_id); + iterator = checklist->pairs->create_iterator(checklist->pairs, TRUE); + while (iterator->iterate(iterator, (void**)¤t)) + { + DBG1(DBG_IKE, " * %#H - %#H (%d)", current->local, current->remote, + current->priority); + } + iterator->destroy(iterator); +} + +/** + * Prunes identical pairs with lower priority from the list + * Note: this function also numbers the remaining pairs serially + */ +static void prune_pairs(linked_list_t *pairs) +{ + iterator_t *iterator, *search; + endpoint_pair_t *current, *other; + u_int32_t id = 0; + + iterator = pairs->create_iterator(pairs, TRUE); + search = pairs->create_iterator(pairs, TRUE); + while (iterator->iterate(iterator, (void**)¤t)) + { + current->id = ++id; + + while (search->iterate(search, (void**)&other)) + { + if (current == other) + { + continue; + } + + if (current->local->equals(current->local, other->local) && + current->remote->equals(current->remote, other->remote)) + { + /* since the list of pairs is sorted by priority in descending + * order, and we iterate the list from the beginning, we are + * sure that the priority of 'other' is lower than that of + * 'current', remove it */ + DBG1(DBG_IKE, "pruning endpoint pair %#H - %#H with priority %d", + other->local, other->remote, other->priority); + search->remove(search); + endpoint_pair_destroy(other); + } + } + search->reset(search); + } + search->destroy(search); + iterator->destroy(iterator); +} + +/** + * Builds a list of endpoint pairs + */ +static void build_pairs(check_list_t *checklist) +{ + /* FIXME: limit endpoints and pairs */ + iterator_t *iterator_i, *iterator_r; + endpoint_notify_t *initiator, *responder; + + iterator_i = checklist->initiator.endpoints->create_iterator( + checklist->initiator.endpoints, TRUE); + while (iterator_i->iterate(iterator_i, (void**)&initiator)) + { + iterator_r = checklist->responder.endpoints->create_iterator( + checklist->responder.endpoints, TRUE); + while (iterator_r->iterate(iterator_r, (void**)&responder)) + { + if (initiator->get_family(initiator) != responder->get_family(responder)) + { + continue; + } + + insert_pair_by_priority(checklist->pairs, endpoint_pair_create( + initiator, responder, checklist->is_initiator)); + } + iterator_r->destroy(iterator_r); + } + iterator_i->destroy(iterator_i); + + print_checklist(checklist); + + prune_pairs(checklist->pairs); +} + +/** + * Processes the payloads of a connectivity check and returns the extracted data + */ +static status_t process_payloads(message_t *message, check_t *check) +{ + enumerator_t *enumerator; + payload_t *payload; + + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + if (payload->get_type(payload) != NOTIFY) + { + DBG1(DBG_IKE, "ignoring payload of type '%N' while processing " + "connectivity check", payload_type_names, + payload->get_type(payload)); + continue; + } + + notify_payload_t *notify = (notify_payload_t*)payload; + + switch (notify->get_notify_type(notify)) + { + case ME_ENDPOINT: + { + if (check->endpoint) + { + DBG1(DBG_IKE, "connectivity check contains multiple " + "ME_ENDPOINT notifies"); + break; + } + + endpoint_notify_t *endpoint = endpoint_notify_create_from_payload(notify); + if (!endpoint) + { + DBG1(DBG_IKE, "received invalid ME_ENDPOINT notify"); + break; + } + check->endpoint = endpoint; + check->endpoint_raw = chunk_clone(notify->get_notification_data(notify)); + DBG2(DBG_IKE, "received ME_ENDPOINT notify"); + break; + } + case ME_CONNECTID: + { + if (check->connect_id.ptr) + { + DBG1(DBG_IKE, "connectivity check contains multiple " + "ME_CONNECTID notifies"); + break; + } + check->connect_id = chunk_clone(notify->get_notification_data(notify)); + DBG2(DBG_IKE, "received ME_CONNECTID %#B", &check->connect_id); + break; + } + case ME_CONNECTAUTH: + { + if (check->auth.ptr) + { + DBG1(DBG_IKE, "connectivity check contains multiple " + "ME_CONNECTAUTH notifies"); + break; + } + check->auth = chunk_clone(notify->get_notification_data(notify)); + DBG2(DBG_IKE, "received ME_CONNECTAUTH %#B", &check->auth); + break; + } + default: + break; + } + } + enumerator->destroy(enumerator); + + if (!check->connect_id.ptr || !check->endpoint || !check->auth.ptr) + { + DBG1(DBG_IKE, "at least one required payload was missing from the " + "connectivity check"); + return FAILED; + } + + return SUCCESS; +} + +/** + * Builds the signature for a connectivity check + */ +static chunk_t build_signature(private_connect_manager_t *this, + check_list_t *checklist, check_t *check, bool outbound) +{ + u_int32_t mid; + chunk_t mid_chunk, key_chunk, sig_chunk; + chunk_t sig_hash; + + mid = htonl(check->mid); + mid_chunk = chunk_from_thing(mid); + + key_chunk = (checklist->is_initiator && outbound) || (!checklist->is_initiator && !outbound) + ? checklist->initiator.key : checklist->responder.key; + + /* signature = SHA1( MID | ME_CONNECTID | ME_ENDPOINT | ME_CONNECTKEY ) */ + sig_chunk = chunk_cat("cccc", mid_chunk, check->connect_id, + check->endpoint_raw, key_chunk); + this->hasher->allocate_hash(this->hasher, sig_chunk, &sig_hash); + DBG3(DBG_IKE, "sig_chunk %#B", &sig_chunk); + DBG3(DBG_IKE, "sig_hash %#B", &sig_hash); + + chunk_free(&sig_chunk); + return sig_hash; +} + +static void queue_retransmission(private_connect_manager_t *this, check_list_t *checklist, endpoint_pair_t *pair); +static void schedule_checks(private_connect_manager_t *this, check_list_t *checklist, u_int32_t time); +static void finish_checks(private_connect_manager_t *this, check_list_t *checklist); + +/** + * After one of the initiator's pairs has succeeded we finish the checks without + * waiting for all the timeouts + */ +static job_requeue_t initiator_finish(callback_data_t *data) +{ + private_connect_manager_t *this = data->connect_manager; + + this->mutex->lock(this->mutex); + + check_list_t *checklist; + if (get_checklist_by_id(this, data->connect_id, &checklist) != SUCCESS) + { + DBG1(DBG_IKE, "checklist with id '%#B' not found, can't finish " + "connectivity checks", &data->connect_id); + this->mutex->unlock(this->mutex); + return JOB_REQUEUE_NONE; + } + + finish_checks(this, checklist); + + this->mutex->unlock(this->mutex); + + return JOB_REQUEUE_NONE; +} + +/** + * Updates the state of the whole checklist + */ +static void update_checklist_state(private_connect_manager_t *this, + check_list_t *checklist) +{ + iterator_t *iterator; + endpoint_pair_t *current; + bool in_progress = FALSE, succeeded = FALSE; + + iterator = checklist->pairs->create_iterator(checklist->pairs, TRUE); + while (iterator->iterate(iterator, (void**)¤t)) + { + switch(current->state) + { + case CHECK_WAITING: + /* at least one is still waiting -> checklist remains + * in waiting state */ + iterator->destroy(iterator); + return; + case CHECK_IN_PROGRESS: + in_progress = TRUE; + break; + case CHECK_SUCCEEDED: + succeeded = TRUE; + break; + default: + break; + } + } + iterator->destroy(iterator); + + if (checklist->is_initiator && succeeded && !checklist->is_finishing) + { + /* instead of waiting until all checks have finished (i.e. all + * retransmissions have failed) the initiator finishes the checks + * right after the first check has succeeded. to allow a probably + * better pair to succeed, we still wait a certain time */ + DBG2(DBG_IKE, "fast finishing checks for checklist '%#B'", + &checklist->connect_id); + + callback_data_t *data = callback_data_create(this, checklist->connect_id); + job_t *job = (job_t*)callback_job_create((callback_job_cb_t)initiator_finish, data, (callback_job_cleanup_t)callback_data_destroy, NULL); + charon->scheduler->schedule_job_ms(charon->scheduler, job, ME_WAIT_TO_FINISH); + checklist->is_finishing = TRUE; + } + + if (in_progress) + { + checklist->state = CHECK_IN_PROGRESS; + } + else if (succeeded) + { + checklist->state = CHECK_SUCCEEDED; + } + else + { + checklist->state = CHECK_FAILED; + } +} + +/** + * This function is triggered for each sent check after a specific timeout + */ +static job_requeue_t retransmit(callback_data_t *data) +{ + private_connect_manager_t *this = data->connect_manager; + + this->mutex->lock(this->mutex); + + check_list_t *checklist; + if (get_checklist_by_id(this, data->connect_id, &checklist) != SUCCESS) + { + DBG1(DBG_IKE, "checklist with id '%#B' not found, can't retransmit " + "connectivity check", &data->connect_id); + this->mutex->unlock(this->mutex); + return JOB_REQUEUE_NONE; + } + + endpoint_pair_t *pair; + if (get_pair_by_id(checklist, data->mid, &pair) != SUCCESS) + { + DBG1(DBG_IKE, "pair with id '%d' not found, can't retransmit " + "connectivity check", data->mid); + goto retransmit_end; + } + + if (pair->state != CHECK_IN_PROGRESS) + { + DBG2(DBG_IKE, "pair with id '%d' is in wrong state [%d], don't " + "retransmit the connectivity check", data->mid, pair->state); + goto retransmit_end; + } + + if (++pair->retransmitted > ME_MAX_RETRANS) + { + DBG2(DBG_IKE, "pair with id '%d' failed after %d retransmissions", + data->mid, ME_MAX_RETRANS); + pair->state = CHECK_FAILED; + goto retransmit_end; + } + + charon->sender->send(charon->sender, pair->packet->clone(pair->packet)); + + queue_retransmission(this, checklist, pair); + +retransmit_end: + update_checklist_state(this, checklist); + + switch(checklist->state) + { + case CHECK_SUCCEEDED: + case CHECK_FAILED: + finish_checks(this, checklist); + break; + default: + break; + } + + this->mutex->unlock(this->mutex); + + /* we reschedule it manually */ + return JOB_REQUEUE_NONE; +} + +/** + * Queues a retransmission job + */ +static void queue_retransmission(private_connect_manager_t *this, check_list_t *checklist, endpoint_pair_t *pair) +{ + callback_data_t *data = retransmit_data_create(this, checklist->connect_id, pair->id); + job_t *job = (job_t*)callback_job_create((callback_job_cb_t)retransmit, data, (callback_job_cleanup_t)callback_data_destroy, NULL); + + u_int32_t retransmission = pair->retransmitted + 1; + u_int32_t rto = ME_INTERVAL; + if (retransmission > ME_BOOST) + { + rto = (u_int32_t)(ME_INTERVAL * pow(ME_RETRANS_BASE, retransmission - ME_BOOST)); + } + DBG2(DBG_IKE, "scheduling retransmission %d of pair '%d' in %dms", + retransmission, pair->id, rto); + + charon->scheduler->schedule_job_ms(charon->scheduler, (job_t*)job, rto); +} + +/** + * Sends a check + */ +static void send_check(private_connect_manager_t *this, check_list_t *checklist, + check_t *check, endpoint_pair_t *pair, bool request) +{ + message_t *message = message_create(); + message->set_message_id(message, check->mid); + message->set_exchange_type(message, INFORMATIONAL); + message->set_request(message, request); + message->set_destination(message, check->dst->clone(check->dst)); + message->set_source(message, check->src->clone(check->src)); + + ike_sa_id_t *ike_sa_id = ike_sa_id_create(0, 0, request); + message->set_ike_sa_id(message, ike_sa_id); + ike_sa_id->destroy(ike_sa_id); + + message->add_notify(message, FALSE, ME_CONNECTID, check->connect_id); + DBG2(DBG_IKE, "send ME_CONNECTID %#B", &check->connect_id); + + notify_payload_t *endpoint = check->endpoint->build_notify(check->endpoint); + check->endpoint_raw = chunk_clone(endpoint->get_notification_data(endpoint)); + message->add_payload(message, (payload_t*)endpoint); + DBG2(DBG_IKE, "send ME_ENDPOINT notify"); + + check->auth = build_signature(this, checklist, check, TRUE); + message->add_notify(message, FALSE, ME_CONNECTAUTH, check->auth); + DBG2(DBG_IKE, "send ME_CONNECTAUTH %#B", &check->auth); + + packet_t *packet; + if (message->generate(message, NULL, NULL, &packet) == SUCCESS) + { + charon->sender->send(charon->sender, packet->clone(packet)); + + if (request) + { + DESTROY_IF(pair->packet); + pair->packet = packet; + pair->retransmitted = 0; + queue_retransmission(this, checklist, pair); + } + else + { + packet->destroy(packet); + } + } + message->destroy(message); +} + +/** + * Queues a triggered check + */ +static void queue_triggered_check(private_connect_manager_t *this, + check_list_t *checklist, endpoint_pair_t *pair) +{ + DBG2(DBG_IKE, "queueing triggered check for pair '%d'", pair->id); + pair->state = CHECK_WAITING; + checklist->triggered->insert_last(checklist->triggered, pair); + + if (!checklist->sender) + { + /* if the sender is not running we restart it */ + schedule_checks(this, checklist, ME_INTERVAL); + } +} + +/** + * This function is triggered for each checklist at a specific interval + */ +static job_requeue_t sender(callback_data_t *data) +{ + private_connect_manager_t *this = data->connect_manager; + + this->mutex->lock(this->mutex); + + check_list_t *checklist; + if (get_checklist_by_id(this, data->connect_id, &checklist) != SUCCESS) + { + DBG1(DBG_IKE, "checklist with id '%#B' not found, can't send " + "connectivity check", &data->connect_id); + this->mutex->unlock(this->mutex); + return JOB_REQUEUE_NONE; + } + + /* reset the sender */ + checklist->sender = NULL; + + endpoint_pair_t *pair; + if (get_triggered_pair(checklist, &pair) != SUCCESS) + { + DBG1(DBG_IKE, "no triggered check queued, sending an ordinary check"); + + if (checklist->pairs->find_first(checklist->pairs, + (linked_list_match_t)match_waiting_pair, + (void**)&pair) != SUCCESS) + { + this->mutex->unlock(this->mutex); + DBG1(DBG_IKE, "no pairs in waiting state, aborting"); + return JOB_REQUEUE_NONE; + } + } + else + { + DBG1(DBG_IKE, "triggered check found"); + } + + check_t *check = check_create(); + check->mid = pair->id; + check->src = pair->local->clone(pair->local); + check->dst = pair->remote->clone(pair->remote); + check->connect_id = chunk_clone(checklist->connect_id); + check->endpoint = endpoint_notify_create_from_host(PEER_REFLEXIVE, NULL, + NULL); + + pair->state = CHECK_IN_PROGRESS; + + send_check(this, checklist, check, pair, TRUE); + + check_destroy(check); + + /* schedule this job again */ + schedule_checks(this, checklist, ME_INTERVAL); + + this->mutex->unlock(this->mutex); + + /* we reschedule it manually */ + return JOB_REQUEUE_NONE; +} + +/** + * Schedules checks for a checklist (time in ms) + */ +static void schedule_checks(private_connect_manager_t *this, check_list_t *checklist, u_int32_t time) +{ + callback_data_t *data = callback_data_create(this, checklist->connect_id); + checklist->sender = (job_t*)callback_job_create((callback_job_cb_t)sender, data, (callback_job_cleanup_t)callback_data_destroy, NULL); + charon->scheduler->schedule_job_ms(charon->scheduler, checklist->sender, time); +} + +/** + * Initiates waiting mediated connections + */ +static job_requeue_t initiate_mediated(initiate_data_t *data) +{ + check_list_t *checklist = data->checklist; + initiated_t *initiated = data->initiated; + + endpoint_pair_t *pair; + if (get_best_valid_pair(checklist, &pair) == SUCCESS) + { + ike_sa_id_t *waiting_sa; + iterator_t *iterator = initiated->mediated->create_iterator(initiated->mediated, TRUE); + while (iterator->iterate(iterator, (void**)&waiting_sa)) + { + ike_sa_t *sa = charon->ike_sa_manager->checkout(charon->ike_sa_manager, waiting_sa); + if (sa->initiate_mediated(sa, pair->local, pair->remote, checklist->connect_id) != SUCCESS) + { + DBG1(DBG_IKE, "establishing mediated connection failed"); + charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, sa); + } + charon->ike_sa_manager->checkin(charon->ike_sa_manager, sa); + } + iterator->destroy(iterator); + } + else + { + /* this should (can?) not happen */ + } + + return JOB_REQUEUE_NONE; +} + +/** + * Finishes checks for a checklist + */ +static void finish_checks(private_connect_manager_t *this, check_list_t *checklist) +{ + if (checklist->is_initiator) + { + initiated_t *initiated; + if (get_initiated_by_ids(this, checklist->initiator.id, + checklist->responder.id, &initiated) == SUCCESS) + { + remove_checklist(this, checklist); + remove_initiated(this, initiated); + + initiate_data_t *data = initiate_data_create(checklist, initiated); + job_t *job = (job_t*)callback_job_create((callback_job_cb_t)initiate_mediated, data, (callback_job_cleanup_t)initiate_data_destroy, NULL); + charon->processor->queue_job(charon->processor, job); + return; + } + else + { + DBG1(DBG_IKE, "there is no mediated connection waiting between '%Y'" + " and '%Y'", checklist->initiator.id, checklist->responder.id); + } + } +} + +/** + * Process the response to one of our requests + */ +static void process_response(private_connect_manager_t *this, check_t *check, + check_list_t *checklist) +{ + endpoint_pair_t *pair; + if (get_pair_by_id(checklist, check->mid, &pair) == SUCCESS) + { + if (pair->local->equals(pair->local, check->dst) && + pair->remote->equals(pair->remote, check->src)) + { + DBG1(DBG_IKE, "endpoint pair '%d' is valid: '%#H' - '%#H'", + pair->id, pair->local, pair->remote); + pair->state = CHECK_SUCCEEDED; + } + + linked_list_t *local_endpoints = checklist->is_initiator ? + checklist->initiator.endpoints : checklist->responder.endpoints; + + endpoint_notify_t *local_endpoint; + if (endpoints_contain(local_endpoints, + check->endpoint->get_host(check->endpoint), + &local_endpoint) != SUCCESS) + { + local_endpoint = endpoint_notify_create_from_host(PEER_REFLEXIVE, + check->endpoint->get_host(check->endpoint), pair->local); + local_endpoint->set_priority(local_endpoint, + check->endpoint->get_priority(check->endpoint)); + local_endpoints->insert_last(local_endpoints, local_endpoint); + } + + update_checklist_state(this, checklist); + + switch(checklist->state) + { + case CHECK_SUCCEEDED: + case CHECK_FAILED: + finish_checks(this, checklist); + break; + default: + break; + } + } + else + { + DBG1(DBG_IKE, "pair with id '%d' not found", check->mid); + } +} + +static void process_request(private_connect_manager_t *this, check_t *check, + check_list_t *checklist) +{ + linked_list_t *remote_endpoints = checklist->is_initiator ? + checklist->responder.endpoints : checklist->initiator.endpoints; + + endpoint_notify_t *peer_reflexive, *remote_endpoint; + peer_reflexive = endpoint_notify_create_from_host(PEER_REFLEXIVE, + check->src, NULL); + peer_reflexive->set_priority(peer_reflexive, + check->endpoint->get_priority(check->endpoint)); + + if (endpoints_contain(remote_endpoints, check->src, &remote_endpoint) != SUCCESS) + { + remote_endpoint = peer_reflexive->clone(peer_reflexive); + remote_endpoints->insert_last(remote_endpoints, remote_endpoint); + } + + endpoint_pair_t *pair; + if (get_pair_by_hosts(checklist->pairs, check->dst, check->src, + &pair) == SUCCESS) + { + switch(pair->state) + { + case CHECK_IN_PROGRESS: + /* prevent retransmissions */ + pair->retransmitted = ME_MAX_RETRANS; + /* FIXME: we should wait to the next rto to send the triggered + * check */ + /* fall-through */ + case CHECK_WAITING: + case CHECK_FAILED: + queue_triggered_check(this, checklist, pair); + break; + case CHECK_SUCCEEDED: + default: + break; + } + } + else + { + endpoint_notify_t *local_endpoint = endpoint_notify_create_from_host(HOST, check->dst, NULL); + + endpoint_notify_t *initiator = checklist->is_initiator ? local_endpoint : remote_endpoint; + endpoint_notify_t *responder = checklist->is_initiator ? remote_endpoint : local_endpoint; + + pair = endpoint_pair_create(initiator, responder, checklist->is_initiator); + pair->id = checklist->pairs->get_count(checklist->pairs) + 1; + + insert_pair_by_priority(checklist->pairs, pair); + + queue_triggered_check(this, checklist, pair); + + local_endpoint->destroy(local_endpoint); + } + + check_t *response = check_create(); + + response->mid = check->mid; + response->src = check->dst->clone(check->dst); + response->dst = check->src->clone(check->src); + response->connect_id = chunk_clone(check->connect_id); + response->endpoint = peer_reflexive; + + send_check(this, checklist, response, pair, FALSE); + + check_destroy(response); +} + +/** + * Implementation of connect_manager_t.process_check. + */ +static void process_check(private_connect_manager_t *this, message_t *message) +{ + if (message->parse_body(message, NULL, NULL) != SUCCESS) + { + DBG1(DBG_IKE, "%N %s with message ID %d processing failed", + exchange_type_names, message->get_exchange_type(message), + message->get_request(message) ? "request" : "response", + message->get_message_id(message)); + return; + } + + check_t *check = check_create(); + check->mid = message->get_message_id(message); + check->src = message->get_source(message); + check->src = check->src->clone(check->src); + check->dst = message->get_destination(message); + check->dst = check->dst->clone(check->dst); + + if (process_payloads(message, check) != SUCCESS) + { + DBG1(DBG_IKE, "invalid connectivity check %s received", + message->get_request(message) ? "request" : "response"); + check_destroy(check); + return; + } + + this->mutex->lock(this->mutex); + + check_list_t *checklist; + if (get_checklist_by_id(this, check->connect_id, &checklist) != SUCCESS) + { + DBG1(DBG_IKE, "checklist with id '%#B' not found", + &check->connect_id); + check_destroy(check); + this->mutex->unlock(this->mutex); + return; + } + + chunk_t sig = build_signature(this, checklist, check, FALSE); + if (!chunk_equals(sig, check->auth)) + { + DBG1(DBG_IKE, "connectivity check verification failed"); + check_destroy(check); + chunk_free(&sig); + this->mutex->unlock(this->mutex); + return; + } + chunk_free(&sig); + + if (message->get_request(message)) + { + process_request(this, check, checklist); + } + else + { + process_response(this, check, checklist); + } + + this->mutex->unlock(this->mutex); + + check_destroy(check); +} + +/** + * Implementation of connect_manager_t.check_and_register. + */ +static bool check_and_register(private_connect_manager_t *this, + identification_t *id, identification_t *peer_id, + ike_sa_id_t *mediated_sa) +{ + initiated_t *initiated; + bool already_there = TRUE; + + this->mutex->lock(this->mutex); + + if (get_initiated_by_ids(this, id, peer_id, &initiated) != SUCCESS) + { + DBG2(DBG_IKE, "registered waiting mediated connection with '%Y'", + peer_id); + initiated = initiated_create(id, peer_id); + this->initiated->insert_last(this->initiated, initiated); + already_there = FALSE; + } + + if (initiated->mediated->find_first(initiated->mediated, + (linked_list_match_t)mediated_sa->equals, + NULL, mediated_sa) != SUCCESS) + { + initiated->mediated->insert_last(initiated->mediated, + mediated_sa->clone(mediated_sa)); + } + + this->mutex->unlock(this->mutex); + + return already_there; +} + +/** + * Implementation of connect_manager_t.check_and_initiate. + */ +static void check_and_initiate(private_connect_manager_t *this, + ike_sa_id_t *mediation_sa, identification_t *id, + identification_t *peer_id) +{ + initiated_t *initiated; + + this->mutex->lock(this->mutex); + + if (get_initiated_by_ids(this, id, peer_id, &initiated) != SUCCESS) + { + DBG2(DBG_IKE, "no waiting mediated connections with '%Y'", peer_id); + this->mutex->unlock(this->mutex); + return; + } + + ike_sa_id_t *waiting_sa; + iterator_t *iterator = initiated->mediated->create_iterator( + initiated->mediated, TRUE); + while (iterator->iterate(iterator, (void**)&waiting_sa)) + { + job_t *job = (job_t*)reinitiate_mediation_job_create(mediation_sa, + waiting_sa); + charon->processor->queue_job(charon->processor, job); + } + iterator->destroy(iterator); + + this->mutex->unlock(this->mutex); +} + +/** + * Implementation of connect_manager_t.set_initiator_data. + */ +static status_t set_initiator_data(private_connect_manager_t *this, + identification_t *initiator, + identification_t *responder, + chunk_t connect_id, chunk_t key, + linked_list_t *endpoints, bool is_initiator) +{ + check_list_t *checklist; + + this->mutex->lock(this->mutex); + + if (get_checklist_by_id(this, connect_id, NULL) == SUCCESS) + { + DBG1(DBG_IKE, "checklist with id '%#B' already exists, aborting", + &connect_id); + this->mutex->unlock(this->mutex); + return FAILED; + } + + checklist = check_list_create(initiator, responder, connect_id, key, + endpoints, is_initiator); + this->checklists->insert_last(this->checklists, checklist); + + this->mutex->unlock(this->mutex); + + return SUCCESS; +} + +/** + * Implementation of connect_manager_t.set_responder_data. + */ +static status_t set_responder_data(private_connect_manager_t *this, + chunk_t connect_id, chunk_t key, + linked_list_t *endpoints) +{ + check_list_t *checklist; + + this->mutex->lock(this->mutex); + + if (get_checklist_by_id(this, connect_id, &checklist) != SUCCESS) + { + DBG1(DBG_IKE, "checklist with id '%#B' not found", + &connect_id); + this->mutex->unlock(this->mutex); + return NOT_FOUND; + } + + checklist->responder.key = chunk_clone(key); + checklist->responder.endpoints = endpoints->clone_offset(endpoints, + offsetof(endpoint_notify_t, clone)); + checklist->state = CHECK_WAITING; + + build_pairs(checklist); + + /* send the first check immediately */ + schedule_checks(this, checklist, 0); + + this->mutex->unlock(this->mutex); + + return SUCCESS; +} + +/** + * Implementation of connect_manager_t.stop_checks. + */ +static status_t stop_checks(private_connect_manager_t *this, chunk_t connect_id) +{ + check_list_t *checklist; + + this->mutex->lock(this->mutex); + + if (get_checklist_by_id(this, connect_id, &checklist) != SUCCESS) + { + DBG1(DBG_IKE, "checklist with id '%#B' not found", + &connect_id); + this->mutex->unlock(this->mutex); + return NOT_FOUND; + } + + DBG1(DBG_IKE, "removing checklist with id '%#B'", &connect_id); + + remove_checklist(this, checklist); + check_list_destroy(checklist); + + this->mutex->unlock(this->mutex); + + return SUCCESS; +} + +/** + * Implementation of connect_manager_t.destroy. + */ +static void destroy(private_connect_manager_t *this) +{ + this->mutex->lock(this->mutex); + + this->hasher->destroy(this->hasher); + this->checklists->destroy_function(this->checklists, (void*)check_list_destroy); + this->initiated->destroy_function(this->initiated, (void*)initiated_destroy); + + this->mutex->unlock(this->mutex); + this->mutex->destroy(this->mutex); + free(this); +} + +/* + * Described in header. + */ +connect_manager_t *connect_manager_create() +{ + private_connect_manager_t *this = malloc_thing(private_connect_manager_t); + + this->public.destroy = (void(*)(connect_manager_t*))destroy; + this->public.check_and_register = (bool(*)(connect_manager_t*,identification_t*,identification_t*,ike_sa_id_t*))check_and_register; + this->public.check_and_initiate = (void(*)(connect_manager_t*,ike_sa_id_t*,identification_t*,identification_t*))check_and_initiate; + this->public.set_initiator_data = (status_t(*)(connect_manager_t*,identification_t*,identification_t*,chunk_t,chunk_t,linked_list_t*,bool))set_initiator_data; + this->public.set_responder_data = (status_t(*)(connect_manager_t*,chunk_t,chunk_t,linked_list_t*))set_responder_data; + this->public.process_check = (void(*)(connect_manager_t*,message_t*))process_check; + this->public.stop_checks = (status_t(*)(connect_manager_t*,chunk_t))stop_checks; + + this->hasher = lib->crypto->create_hasher(lib->crypto, HASH_SHA1); + if (this->hasher == NULL) + { + DBG1(DBG_IKE, "unable to create connect manager, SHA1 not supported"); + free(this); + return NULL; + } + + this->checklists = linked_list_create(); + this->initiated = linked_list_create(); + + this->mutex = mutex_create(MUTEX_TYPE_DEFAULT); + + return (connect_manager_t*)this; +} diff --git a/src/libcharon/sa/connect_manager.h b/src/libcharon/sa/connect_manager.h new file mode 100644 index 000000000..8fa8ff697 --- /dev/null +++ b/src/libcharon/sa/connect_manager.h @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2007-2008 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 connect_manager connect_manager + * @{ @ingroup sa + */ + +#ifndef CONNECT_MANAGER_H_ +#define CONNECT_MANAGER_H_ + +typedef struct connect_manager_t connect_manager_t; + +#include <encoding/message.h> +#include <sa/ike_sa_id.h> +#include <utils/identification.h> + +/** + * The connection manager is responsible for establishing a direct + * connection with another peer. + */ +struct connect_manager_t { + + /** + * Checks if a there is already a mediated connection registered + * between two peers. + * + * @param id my id + * @param peer_id the other peer's id + * @param mediated_sa the IKE_SA ID of the mediated connection + * @returns + * - TRUE, if a mediated connection is registered + * - FALSE, otherwise + */ + bool (*check_and_register) (connect_manager_t *this, identification_t *id, + identification_t *peer_id, + ike_sa_id_t *mediated_sa); + + /** + * Checks if there are waiting connections with a specific peer. + * If so, reinitiate them. + * + * @param id my id + * @param peer_id the other peer's id + */ + void (*check_and_initiate) (connect_manager_t *this, + ike_sa_id_t *mediation_sa, identification_t *id, + identification_t *peer_id); + + /** + * Creates a checklist and sets the initiator's data. + * + * @param initiator ID of the initiator + * @param responder ID of the responder + * @param connect_id the connect ID provided by the initiator + * @param key the initiator's key + * @param endpoints the initiator's endpoints + * @param is_initiator TRUE, if the caller of this method is the initiator + * @returns SUCCESS + */ + status_t (*set_initiator_data) (connect_manager_t *this, + identification_t *initiator, + identification_t *responder, + chunk_t connect_id, chunk_t key, + linked_list_t *endpoints, + bool is_initiator); + + /** + * Updates a checklist and sets the responder's data. The checklist's + * state is advanced to WAITING which means that checks will be sent. + * + * @param connect_id the connect ID + * @param chunk_t the responder's key + * @param endpoints the responder's endpoints + * @returns + * - NOT_FOUND, if the checklist has not been found + * - SUCCESS, otherwise + */ + status_t (*set_responder_data) (connect_manager_t *this, + chunk_t connect_id, chunk_t key, + linked_list_t *endpoints); + + /** + * Stops checks for a checklist. Called after the responder received an + * IKE_SA_INIT request which contains a ME_CONNECTID payload. + * + * @param connect_id the connect ID + * @returns + * - NOT_FOUND, if the checklist has not been found + * - SUCCESS, otherwise + */ + status_t (*stop_checks) (connect_manager_t *this, chunk_t connect_id); + + /** + * Processes a connectivity check + * + * @param message the received message + */ + void (*process_check) (connect_manager_t *this, message_t *message); + + /** + * Destroys the manager with all data. + */ + void (*destroy) (connect_manager_t *this); +}; + +/** + * Create a manager. + * + * @returns connect_manager_t object + */ +connect_manager_t *connect_manager_create(void); + +#endif /** CONNECT_MANAGER_H_ @}*/ diff --git a/src/libcharon/sa/ike_sa.c b/src/libcharon/sa/ike_sa.c new file mode 100644 index 000000000..023f0749f --- /dev/null +++ b/src/libcharon/sa/ike_sa.c @@ -0,0 +1,2227 @@ +/* + * Copyright (C) 2006-2008 Tobias Brunner + * Copyright (C) 2006 Daniel Roethlisberger + * Copyright (C) 2005-2009 Martin Willi + * Copyright (C) 2005 Jan Hutter + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include <string.h> +#include <sys/stat.h> +#include <errno.h> +#include <time.h> + +#include "ike_sa.h" + +#include <library.h> +#include <daemon.h> +#include <hydra.h> +#include <utils/linked_list.h> +#include <utils/lexparser.h> +#include <sa/task_manager.h> +#include <sa/tasks/ike_init.h> +#include <sa/tasks/ike_natd.h> +#include <sa/tasks/ike_mobike.h> +#include <sa/tasks/ike_auth.h> +#include <sa/tasks/ike_auth_lifetime.h> +#include <sa/tasks/ike_config.h> +#include <sa/tasks/ike_cert_pre.h> +#include <sa/tasks/ike_cert_post.h> +#include <sa/tasks/ike_rekey.h> +#include <sa/tasks/ike_reauth.h> +#include <sa/tasks/ike_delete.h> +#include <sa/tasks/ike_dpd.h> +#include <sa/tasks/ike_vendor.h> +#include <sa/tasks/child_create.h> +#include <sa/tasks/child_delete.h> +#include <sa/tasks/child_rekey.h> +#include <processing/jobs/retransmit_job.h> +#include <processing/jobs/delete_ike_sa_job.h> +#include <processing/jobs/send_dpd_job.h> +#include <processing/jobs/send_keepalive_job.h> +#include <processing/jobs/rekey_ike_sa_job.h> + +#ifdef ME +#include <sa/tasks/ike_me.h> +#include <processing/jobs/initiate_mediation_job.h> +#endif + +ENUM(ike_sa_state_names, IKE_CREATED, IKE_DESTROYING, + "CREATED", + "CONNECTING", + "ESTABLISHED", + "PASSIVE", + "REKEYING", + "DELETING", + "DESTROYING", +); + +typedef struct private_ike_sa_t private_ike_sa_t; +typedef struct attribute_entry_t attribute_entry_t; + +/** + * Private data of an ike_sa_t object. + */ +struct private_ike_sa_t { + + /** + * Public members + */ + ike_sa_t public; + + /** + * Identifier for the current IKE_SA. + */ + ike_sa_id_t *ike_sa_id; + + /** + * unique numerical ID for this IKE_SA. + */ + u_int32_t unique_id; + + /** + * Current state of the IKE_SA + */ + ike_sa_state_t state; + + /** + * IKE configuration used to set up this IKE_SA + */ + ike_cfg_t *ike_cfg; + + /** + * Peer and authentication information to establish IKE_SA. + */ + peer_cfg_t *peer_cfg; + + /** + * currently used authentication ruleset, local (as auth_cfg_t) + */ + auth_cfg_t *my_auth; + + /** + * list of completed local authentication rounds + */ + linked_list_t *my_auths; + + /** + * list of completed remote authentication rounds + */ + linked_list_t *other_auths; + + /** + * currently used authentication constraints, remote (as auth_cfg_t) + */ + auth_cfg_t *other_auth; + + /** + * Selected IKE proposal + */ + proposal_t *proposal; + + /** + * Juggles tasks to process messages + */ + task_manager_t *task_manager; + + /** + * Address of local host + */ + host_t *my_host; + + /** + * Address of remote host + */ + host_t *other_host; + +#ifdef ME + /** + * Are we mediation server + */ + bool is_mediation_server; + + /** + * Server reflexive host + */ + host_t *server_reflexive_host; + + /** + * Connect ID + */ + chunk_t connect_id; +#endif /* ME */ + + /** + * Identification used for us + */ + identification_t *my_id; + + /** + * Identification used for other + */ + identification_t *other_id; + + /** + * set of extensions the peer supports + */ + ike_extension_t extensions; + + /** + * set of condition flags currently enabled for this IKE_SA + */ + ike_condition_t conditions; + + /** + * Linked List containing the child sa's of the current IKE_SA. + */ + linked_list_t *child_sas; + + /** + * keymat of this IKE_SA + */ + keymat_t *keymat; + + /** + * Virtual IP on local host, if any + */ + host_t *my_virtual_ip; + + /** + * Virtual IP on remote host, if any + */ + host_t *other_virtual_ip; + + /** + * List of configuration attributes (attribute_entry_t) + */ + linked_list_t *attributes; + + /** + * list of peers additional addresses, transmitted via MOBIKE + */ + linked_list_t *additional_addresses; + + /** + * previously value of received DESTINATION_IP hash + */ + chunk_t nat_detection_dest; + + /** + * number pending UPDATE_SA_ADDRESS (MOBIKE) + */ + u_int32_t pending_updates; + + /** + * NAT keep alive interval + */ + u_int32_t keepalive_interval; + + /** + * Timestamps for this IKE_SA + */ + u_int32_t stats[STAT_MAX]; + + /** + * how many times we have retried so far (keyingtries) + */ + u_int32_t keyingtry; + + /** + * local host address to be used for IKE, set via MIGRATE kernel message + */ + host_t *local_host; + + /** + * remote host address to be used for IKE, set via MIGRATE kernel message + */ + host_t *remote_host; +}; + +/** + * Entry to maintain install configuration attributes during IKE_SA lifetime + */ +struct attribute_entry_t { + /** handler used to install this attribute */ + attribute_handler_t *handler; + /** attribute type */ + configuration_attribute_type_t type; + /** attribute data */ + chunk_t data; +}; + +/** + * get the time of the latest traffic processed by the kernel + */ +static time_t get_use_time(private_ike_sa_t* this, bool inbound) +{ + enumerator_t *enumerator; + child_sa_t *child_sa; + time_t use_time, current; + + if (inbound) + { + use_time = this->stats[STAT_INBOUND]; + } + else + { + use_time = this->stats[STAT_OUTBOUND]; + } + enumerator = this->child_sas->create_enumerator(this->child_sas); + while (enumerator->enumerate(enumerator, &child_sa)) + { + child_sa->get_usestats(child_sa, inbound, ¤t, NULL); + use_time = max(use_time, current); + } + enumerator->destroy(enumerator); + + return use_time; +} + +/** + * Implementation of ike_sa_t.get_unique_id + */ +static u_int32_t get_unique_id(private_ike_sa_t *this) +{ + return this->unique_id; +} + +/** + * Implementation of ike_sa_t.get_name. + */ +static char *get_name(private_ike_sa_t *this) +{ + if (this->peer_cfg) + { + return this->peer_cfg->get_name(this->peer_cfg); + } + return "(unnamed)"; +} + +/** + * Implementation of ike_sa_t.get_statistic. + */ +static u_int32_t get_statistic(private_ike_sa_t *this, statistic_t kind) +{ + if (kind < STAT_MAX) + { + return this->stats[kind]; + } + return 0; +} + +/** + * Implementation of ike_sa_t.get_my_host. + */ +static host_t *get_my_host(private_ike_sa_t *this) +{ + return this->my_host; +} + +/** + * Implementation of ike_sa_t.set_my_host. + */ +static void set_my_host(private_ike_sa_t *this, host_t *me) +{ + DESTROY_IF(this->my_host); + this->my_host = me; +} + +/** + * Implementation of ike_sa_t.get_other_host. + */ +static host_t *get_other_host(private_ike_sa_t *this) +{ + return this->other_host; +} + +/** + * Implementation of ike_sa_t.set_other_host. + */ +static void set_other_host(private_ike_sa_t *this, host_t *other) +{ + DESTROY_IF(this->other_host); + this->other_host = other; +} + +/** + * Implementation of ike_sa_t.get_peer_cfg + */ +static peer_cfg_t* get_peer_cfg(private_ike_sa_t *this) +{ + return this->peer_cfg; +} + +/** + * Implementation of ike_sa_t.set_peer_cfg + */ +static void set_peer_cfg(private_ike_sa_t *this, peer_cfg_t *peer_cfg) +{ + DESTROY_IF(this->peer_cfg); + peer_cfg->get_ref(peer_cfg); + this->peer_cfg = peer_cfg; + + if (this->ike_cfg == NULL) + { + this->ike_cfg = peer_cfg->get_ike_cfg(peer_cfg); + this->ike_cfg->get_ref(this->ike_cfg); + } +} + +/** + * Implementation of ike_sa_t.get_auth_cfg + */ +static auth_cfg_t* get_auth_cfg(private_ike_sa_t *this, bool local) +{ + if (local) + { + return this->my_auth; + } + return this->other_auth; +} + +/** + * Implementation of ike_sa_t.add_auth_cfg + */ +static void add_auth_cfg(private_ike_sa_t *this, bool local, auth_cfg_t *cfg) +{ + if (local) + { + this->my_auths->insert_last(this->my_auths, cfg); + } + else + { + this->other_auths->insert_last(this->other_auths, cfg); + } +} + +/** + * Implementation of ike_sa_t.create_auth_cfg_enumerator + */ +static enumerator_t* create_auth_cfg_enumerator(private_ike_sa_t *this, + bool local) +{ + if (local) + { + return this->my_auths->create_enumerator(this->my_auths); + } + return this->other_auths->create_enumerator(this->other_auths); +} + +/** + * Flush the stored authentication round information + */ +static void flush_auth_cfgs(private_ike_sa_t *this) +{ + auth_cfg_t *cfg; + + if (lib->settings->get_bool(lib->settings, "charon.flush_auth_cfg", FALSE)) + { + while (this->my_auths->remove_last(this->my_auths, + (void**)&cfg) == SUCCESS) + { + cfg->destroy(cfg); + } + while (this->other_auths->remove_last(this->other_auths, + (void**)&cfg) == SUCCESS) + { + cfg->destroy(cfg); + } + } +} + +/** + * Implementation of ike_sa_t.get_proposal + */ +static proposal_t* get_proposal(private_ike_sa_t *this) +{ + return this->proposal; +} + +/** + * Implementation of ike_sa_t.set_proposal + */ +static void set_proposal(private_ike_sa_t *this, proposal_t *proposal) +{ + DESTROY_IF(this->proposal); + this->proposal = proposal->clone(proposal); +} + +/** + * Implementation of ike_sa_t.set_message_id + */ +static void set_message_id(private_ike_sa_t *this, bool initiate, u_int32_t mid) +{ + if (initiate) + { + this->task_manager->reset(this->task_manager, mid, UINT_MAX); + } + else + { + this->task_manager->reset(this->task_manager, UINT_MAX, mid); + } +} + +/** + * Implementation of ike_sa_t.send_keepalive + */ +static void send_keepalive(private_ike_sa_t *this) +{ + send_keepalive_job_t *job; + time_t last_out, now, diff; + + if (!(this->conditions & COND_NAT_HERE) || this->keepalive_interval == 0) + { /* disable keep alives if we are not NATed anymore */ + return; + } + + last_out = get_use_time(this, FALSE); + now = time_monotonic(NULL); + + diff = now - last_out; + + if (diff >= this->keepalive_interval) + { + packet_t *packet; + chunk_t data; + + packet = packet_create(); + packet->set_source(packet, this->my_host->clone(this->my_host)); + packet->set_destination(packet, this->other_host->clone(this->other_host)); + data.ptr = malloc(1); + data.ptr[0] = 0xFF; + data.len = 1; + packet->set_data(packet, data); + DBG1(DBG_IKE, "sending keep alive"); + charon->sender->send(charon->sender, packet); + diff = 0; + } + job = send_keepalive_job_create(this->ike_sa_id); + charon->scheduler->schedule_job(charon->scheduler, (job_t*)job, + this->keepalive_interval - diff); +} + +/** + * Implementation of ike_sa_t.get_ike_cfg + */ +static ike_cfg_t *get_ike_cfg(private_ike_sa_t *this) +{ + return this->ike_cfg; +} + +/** + * Implementation of ike_sa_t.set_ike_cfg + */ +static void set_ike_cfg(private_ike_sa_t *this, ike_cfg_t *ike_cfg) +{ + ike_cfg->get_ref(ike_cfg); + this->ike_cfg = ike_cfg; +} + +/** + * Implementation of ike_sa_t.enable_extension. + */ +static void enable_extension(private_ike_sa_t *this, ike_extension_t extension) +{ + this->extensions |= extension; +} + +/** + * Implementation of ike_sa_t.has_extension. + */ +static bool supports_extension(private_ike_sa_t *this, ike_extension_t extension) +{ + return (this->extensions & extension) != FALSE; +} + +/** + * Implementation of ike_sa_t.has_condition. + */ +static bool has_condition(private_ike_sa_t *this, ike_condition_t condition) +{ + return (this->conditions & condition) != FALSE; +} + +/** + * Implementation of ike_sa_t.enable_condition. + */ +static void set_condition(private_ike_sa_t *this, ike_condition_t condition, + bool enable) +{ + if (has_condition(this, condition) != enable) + { + if (enable) + { + this->conditions |= condition; + switch (condition) + { + case COND_NAT_HERE: + DBG1(DBG_IKE, "local host is behind NAT, sending keep alives"); + this->conditions |= COND_NAT_ANY; + send_keepalive(this); + break; + case COND_NAT_THERE: + DBG1(DBG_IKE, "remote host is behind NAT"); + this->conditions |= COND_NAT_ANY; + break; + case COND_NAT_FAKE: + DBG1(DBG_IKE, "faking NAT situation to enforce UDP encapsulation"); + this->conditions |= COND_NAT_ANY; + break; + default: + break; + } + } + else + { + this->conditions &= ~condition; + switch (condition) + { + case COND_NAT_HERE: + case COND_NAT_FAKE: + case COND_NAT_THERE: + set_condition(this, COND_NAT_ANY, + has_condition(this, COND_NAT_HERE) || + has_condition(this, COND_NAT_THERE) || + has_condition(this, COND_NAT_FAKE)); + break; + default: + break; + } + } + } +} + +/** + * Implementation of ike_sa_t.send_dpd + */ +static status_t send_dpd(private_ike_sa_t *this) +{ + job_t *job; + time_t diff, delay; + + delay = this->peer_cfg->get_dpd(this->peer_cfg); + + if (delay == 0) + { + /* DPD disabled */ + return SUCCESS; + } + + if (this->task_manager->busy(this->task_manager)) + { + /* an exchange is in the air, no need to start a DPD check */ + diff = 0; + } + else + { + /* check if there was any inbound traffic */ + time_t last_in, now; + last_in = get_use_time(this, TRUE); + now = time_monotonic(NULL); + diff = now - last_in; + if (diff >= delay) + { + /* to long ago, initiate dead peer detection */ + task_t *task; + ike_mobike_t *mobike; + + if (supports_extension(this, EXT_MOBIKE) && + has_condition(this, COND_NAT_HERE)) + { + /* use mobike enabled DPD to detect NAT mapping changes */ + mobike = ike_mobike_create(&this->public, TRUE); + mobike->dpd(mobike); + task = &mobike->task; + } + else + { + task = (task_t*)ike_dpd_create(TRUE); + } + diff = 0; + DBG1(DBG_IKE, "sending DPD request"); + + this->task_manager->queue_task(this->task_manager, task); + this->task_manager->initiate(this->task_manager); + } + } + /* recheck in "interval" seconds */ + job = (job_t*)send_dpd_job_create(this->ike_sa_id); + charon->scheduler->schedule_job(charon->scheduler, job, delay - diff); + return SUCCESS; +} + +/** + * Implementation of ike_sa_t.get_state. + */ +static ike_sa_state_t get_state(private_ike_sa_t *this) +{ + return this->state; +} + +/** + * Implementation of ike_sa_t.set_state. + */ +static void set_state(private_ike_sa_t *this, ike_sa_state_t state) +{ + DBG2(DBG_IKE, "IKE_SA %s[%d] state change: %N => %N", + get_name(this), this->unique_id, + ike_sa_state_names, this->state, + ike_sa_state_names, state); + + switch (state) + { + case IKE_ESTABLISHED: + { + if (this->state == IKE_CONNECTING || + this->state == IKE_PASSIVE) + { + job_t *job; + u_int32_t t; + + /* calculate rekey, reauth and lifetime */ + this->stats[STAT_ESTABLISHED] = time_monotonic(NULL); + + /* schedule rekeying if we have a time which is smaller than + * an already scheduled rekeying */ + t = this->peer_cfg->get_rekey_time(this->peer_cfg); + if (t && (this->stats[STAT_REKEY] == 0 || + (this->stats[STAT_REKEY] > t + this->stats[STAT_ESTABLISHED]))) + { + this->stats[STAT_REKEY] = t + this->stats[STAT_ESTABLISHED]; + job = (job_t*)rekey_ike_sa_job_create(this->ike_sa_id, FALSE); + charon->scheduler->schedule_job(charon->scheduler, job, t); + DBG1(DBG_IKE, "scheduling rekeying in %ds", t); + } + t = this->peer_cfg->get_reauth_time(this->peer_cfg); + if (t && (this->stats[STAT_REAUTH] == 0 || + (this->stats[STAT_REAUTH] > t + this->stats[STAT_ESTABLISHED]))) + { + this->stats[STAT_REAUTH] = t + this->stats[STAT_ESTABLISHED]; + job = (job_t*)rekey_ike_sa_job_create(this->ike_sa_id, TRUE); + charon->scheduler->schedule_job(charon->scheduler, job, t); + DBG1(DBG_IKE, "scheduling reauthentication in %ds", t); + } + t = this->peer_cfg->get_over_time(this->peer_cfg); + if (this->stats[STAT_REKEY] || this->stats[STAT_REAUTH]) + { + if (this->stats[STAT_REAUTH] == 0) + { + this->stats[STAT_DELETE] = this->stats[STAT_REKEY]; + } + else if (this->stats[STAT_REKEY] == 0) + { + this->stats[STAT_DELETE] = this->stats[STAT_REAUTH]; + } + else + { + this->stats[STAT_DELETE] = min(this->stats[STAT_REKEY], + this->stats[STAT_REAUTH]); + } + this->stats[STAT_DELETE] += t; + t = this->stats[STAT_DELETE] - this->stats[STAT_ESTABLISHED]; + job = (job_t*)delete_ike_sa_job_create(this->ike_sa_id, TRUE); + charon->scheduler->schedule_job(charon->scheduler, job, t); + DBG1(DBG_IKE, "maximum IKE_SA lifetime %ds", t); + } + + /* start DPD checks */ + send_dpd(this); + } + break; + } + case IKE_DELETING: + { + /* delete may fail if a packet gets lost, so set a timeout */ + job_t *job = (job_t*)delete_ike_sa_job_create(this->ike_sa_id, TRUE); + charon->scheduler->schedule_job(charon->scheduler, job, + HALF_OPEN_IKE_SA_TIMEOUT); + break; + } + default: + break; + } + charon->bus->ike_state_change(charon->bus, &this->public, state); + this->state = state; +} + +/** + * Implementation of ike_sa_t.reset + */ +static void reset(private_ike_sa_t *this) +{ + /* the responder ID is reset, as peer may choose another one */ + if (this->ike_sa_id->is_initiator(this->ike_sa_id)) + { + this->ike_sa_id->set_responder_spi(this->ike_sa_id, 0); + } + + set_state(this, IKE_CREATED); + + this->task_manager->reset(this->task_manager, 0, 0); +} + +/** + * Implementation of ike_sa_t.get_keymat + */ +static keymat_t* get_keymat(private_ike_sa_t *this) +{ + return this->keymat; +} + +/** + * Implementation of ike_sa_t.set_virtual_ip + */ +static void set_virtual_ip(private_ike_sa_t *this, bool local, host_t *ip) +{ + if (local) + { + DBG1(DBG_IKE, "installing new virtual IP %H", ip); + if (charon->kernel_interface->add_ip(charon->kernel_interface, ip, + this->my_host) == SUCCESS) + { + if (this->my_virtual_ip) + { + DBG1(DBG_IKE, "removing old virtual IP %H", this->my_virtual_ip); + charon->kernel_interface->del_ip(charon->kernel_interface, + this->my_virtual_ip); + } + DESTROY_IF(this->my_virtual_ip); + this->my_virtual_ip = ip->clone(ip); + } + else + { + DBG1(DBG_IKE, "installing virtual IP %H failed", ip); + this->my_virtual_ip = NULL; + } + } + else + { + DESTROY_IF(this->other_virtual_ip); + this->other_virtual_ip = ip->clone(ip); + } +} + +/** + * Implementation of ike_sa_t.get_virtual_ip + */ +static host_t* get_virtual_ip(private_ike_sa_t *this, bool local) +{ + if (local) + { + return this->my_virtual_ip; + } + else + { + return this->other_virtual_ip; + } +} + +/** + * Implementation of ike_sa_t.add_additional_address. + */ +static void add_additional_address(private_ike_sa_t *this, host_t *host) +{ + this->additional_addresses->insert_last(this->additional_addresses, host); +} + +/** + * Implementation of ike_sa_t.create_additional_address_iterator. + */ +static iterator_t* create_additional_address_iterator(private_ike_sa_t *this) +{ + return this->additional_addresses->create_iterator( + this->additional_addresses, TRUE); +} + +/** + * Implementation of ike_sa_t.has_mapping_changed + */ +static bool has_mapping_changed(private_ike_sa_t *this, chunk_t hash) +{ + if (this->nat_detection_dest.ptr == NULL) + { + this->nat_detection_dest = chunk_clone(hash); + return FALSE; + } + if (chunk_equals(hash, this->nat_detection_dest)) + { + return FALSE; + } + free(this->nat_detection_dest.ptr); + this->nat_detection_dest = chunk_clone(hash); + return TRUE; +} + +/** + * Implementation of ike_sa_t.set_pending_updates. + */ +static void set_pending_updates(private_ike_sa_t *this, u_int32_t updates) +{ + this->pending_updates = updates; +} + +/** + * Implementation of ike_sa_t.get_pending_updates. + */ +static u_int32_t get_pending_updates(private_ike_sa_t *this) +{ + return this->pending_updates; +} + +/** + * Update hosts, as addresses may change (NAT) + */ +static void update_hosts(private_ike_sa_t *this, host_t *me, host_t *other) +{ + bool update = FALSE; + + if (me == NULL) + { + me = this->my_host; + } + if (other == NULL) + { + other = this->other_host; + } + + /* apply hosts on first received message */ + if (this->my_host->is_anyaddr(this->my_host) || + this->other_host->is_anyaddr(this->other_host)) + { + set_my_host(this, me->clone(me)); + set_other_host(this, other->clone(other)); + update = TRUE; + } + else + { + /* update our address in any case */ + if (!me->equals(me, this->my_host)) + { + set_my_host(this, me->clone(me)); + update = TRUE; + } + + if (!other->equals(other, this->other_host)) + { + /* update others adress if we are NOT NATed, + * and allow port changes if we are NATed */ + if (!has_condition(this, COND_NAT_HERE) || + other->ip_equals(other, this->other_host)) + { + set_other_host(this, other->clone(other)); + update = TRUE; + } + } + } + + /* update all associated CHILD_SAs, if required */ + if (update) + { + iterator_t *iterator; + child_sa_t *child_sa; + + iterator = this->child_sas->create_iterator(this->child_sas, TRUE); + while (iterator->iterate(iterator, (void**)&child_sa)) + { + if (child_sa->update(child_sa, this->my_host, + this->other_host, this->my_virtual_ip, + has_condition(this, COND_NAT_ANY)) == NOT_SUPPORTED) + { + this->public.rekey_child_sa(&this->public, + child_sa->get_protocol(child_sa), + child_sa->get_spi(child_sa, TRUE)); + } + } + iterator->destroy(iterator); + } +} + +/** + * Implementation of ike_sa_t.generate + */ +static status_t generate_message(private_ike_sa_t *this, message_t *message, + packet_t **packet) +{ + this->stats[STAT_OUTBOUND] = time_monotonic(NULL); + message->set_ike_sa_id(message, this->ike_sa_id); + return message->generate(message, + this->keymat->get_crypter(this->keymat, FALSE), + this->keymat->get_signer(this->keymat, FALSE), packet); +} + +/** + * send a notify back to the sender + */ +static void send_notify_response(private_ike_sa_t *this, message_t *request, + notify_type_t type) +{ + message_t *response; + packet_t *packet; + + response = message_create(); + response->set_exchange_type(response, request->get_exchange_type(request)); + response->set_request(response, FALSE); + response->set_message_id(response, request->get_message_id(request)); + response->add_notify(response, FALSE, type, chunk_empty); + if (this->my_host->is_anyaddr(this->my_host)) + { + this->my_host->destroy(this->my_host); + this->my_host = request->get_destination(request); + this->my_host = this->my_host->clone(this->my_host); + } + if (this->other_host->is_anyaddr(this->other_host)) + { + this->other_host->destroy(this->other_host); + this->other_host = request->get_source(request); + this->other_host = this->other_host->clone(this->other_host); + } + response->set_source(response, this->my_host->clone(this->my_host)); + response->set_destination(response, this->other_host->clone(this->other_host)); + if (generate_message(this, response, &packet) == SUCCESS) + { + charon->sender->send(charon->sender, packet); + } + response->destroy(response); +} + +/** + * Implementation of ike_sa_t.set_kmaddress. + */ +static void set_kmaddress(private_ike_sa_t *this, host_t *local, host_t *remote) +{ + DESTROY_IF(this->local_host); + DESTROY_IF(this->remote_host); + this->local_host = local->clone(local); + this->remote_host = remote->clone(remote); +} + +#ifdef ME +/** + * Implementation of ike_sa_t.act_as_mediation_server. + */ +static void act_as_mediation_server(private_ike_sa_t *this) +{ + charon->mediation_manager->update_sa_id(charon->mediation_manager, + this->other_id, this->ike_sa_id); + this->is_mediation_server = TRUE; +} + +/** + * Implementation of ike_sa_t.get_server_reflexive_host. + */ +static host_t *get_server_reflexive_host(private_ike_sa_t *this) +{ + return this->server_reflexive_host; +} + +/** + * Implementation of ike_sa_t.set_server_reflexive_host. + */ +static void set_server_reflexive_host(private_ike_sa_t *this, host_t *host) +{ + DESTROY_IF(this->server_reflexive_host); + this->server_reflexive_host = host; +} + +/** + * Implementation of ike_sa_t.get_connect_id. + */ +static chunk_t get_connect_id(private_ike_sa_t *this) +{ + return this->connect_id; +} + +/** + * Implementation of ike_sa_t.respond + */ +static status_t respond(private_ike_sa_t *this, identification_t *peer_id, + chunk_t connect_id) +{ + ike_me_t *task = ike_me_create(&this->public, TRUE); + task->respond(task, peer_id, connect_id); + this->task_manager->queue_task(this->task_manager, (task_t*)task); + return this->task_manager->initiate(this->task_manager); +} + +/** + * Implementation of ike_sa_t.callback + */ +static status_t callback(private_ike_sa_t *this, identification_t *peer_id) +{ + ike_me_t *task = ike_me_create(&this->public, TRUE); + task->callback(task, peer_id); + this->task_manager->queue_task(this->task_manager, (task_t*)task); + return this->task_manager->initiate(this->task_manager); +} + +/** + * Implementation of ike_sa_t.relay + */ +static status_t relay(private_ike_sa_t *this, identification_t *requester, + chunk_t connect_id, chunk_t connect_key, + linked_list_t *endpoints, bool response) +{ + ike_me_t *task = ike_me_create(&this->public, TRUE); + task->relay(task, requester, connect_id, connect_key, endpoints, response); + this->task_manager->queue_task(this->task_manager, (task_t*)task); + return this->task_manager->initiate(this->task_manager); +} + +/** + * Implementation of ike_sa_t.initiate_mediation + */ +static status_t initiate_mediation(private_ike_sa_t *this, + peer_cfg_t *mediated_cfg) +{ + ike_me_t *task = ike_me_create(&this->public, TRUE); + task->connect(task, mediated_cfg->get_peer_id(mediated_cfg)); + this->task_manager->queue_task(this->task_manager, (task_t*)task); + return this->task_manager->initiate(this->task_manager); +} + +/** + * Implementation of ike_sa_t.initiate_mediated + */ +static status_t initiate_mediated(private_ike_sa_t *this, host_t *me, + host_t *other, chunk_t connect_id) +{ + set_my_host(this, me->clone(me)); + set_other_host(this, other->clone(other)); + chunk_free(&this->connect_id); + this->connect_id = chunk_clone(connect_id); + return this->task_manager->initiate(this->task_manager); +} +#endif /* ME */ + +/** + * Resolve DNS host in configuration + */ +static void resolve_hosts(private_ike_sa_t *this) +{ + host_t *host; + + if (this->remote_host) + { + host = this->remote_host->clone(this->remote_host); + host->set_port(host, IKEV2_UDP_PORT); + } + else + { + host = host_create_from_dns(this->ike_cfg->get_other_addr(this->ike_cfg), + 0, this->ike_cfg->get_other_port(this->ike_cfg)); + } + if (host) + { + set_other_host(this, host); + } + + if (this->local_host) + { + host = this->local_host->clone(this->local_host); + host->set_port(host, IKEV2_UDP_PORT); + } + else + { + int family = 0; + + /* use same address family as for other */ + if (!this->other_host->is_anyaddr(this->other_host)) + { + family = this->other_host->get_family(this->other_host); + } + host = host_create_from_dns(this->ike_cfg->get_my_addr(this->ike_cfg), + family, this->ike_cfg->get_my_port(this->ike_cfg)); + + if (host && host->is_anyaddr(host) && + !this->other_host->is_anyaddr(this->other_host)) + { + host->destroy(host); + host = charon->kernel_interface->get_source_addr( + charon->kernel_interface, this->other_host, NULL); + if (host) + { + host->set_port(host, this->ike_cfg->get_my_port(this->ike_cfg)); + } + else + { /* fallback to address family specific %any(6), if configured */ + host = host_create_from_dns( + this->ike_cfg->get_my_addr(this->ike_cfg), + 0, this->ike_cfg->get_my_port(this->ike_cfg)); + } + } + } + if (host) + { + set_my_host(this, host); + } +} + +/** + * Implementation of ike_sa_t.initiate + */ +static status_t initiate(private_ike_sa_t *this, + child_cfg_t *child_cfg, u_int32_t reqid, + traffic_selector_t *tsi, traffic_selector_t *tsr) +{ + task_t *task; + + if (this->state == IKE_CREATED) + { + resolve_hosts(this); + + if (this->other_host->is_anyaddr(this->other_host) +#ifdef ME + && !this->peer_cfg->get_mediated_by(this->peer_cfg) +#endif /* ME */ + ) + { + child_cfg->destroy(child_cfg); + DBG1(DBG_IKE, "unable to initiate to %%any"); + return DESTROY_ME; + } + + set_condition(this, COND_ORIGINAL_INITIATOR, TRUE); + + task = (task_t*)ike_vendor_create(&this->public, TRUE); + this->task_manager->queue_task(this->task_manager, task); + task = (task_t*)ike_init_create(&this->public, TRUE, NULL); + this->task_manager->queue_task(this->task_manager, task); + task = (task_t*)ike_natd_create(&this->public, TRUE); + this->task_manager->queue_task(this->task_manager, task); + task = (task_t*)ike_cert_pre_create(&this->public, TRUE); + this->task_manager->queue_task(this->task_manager, task); + task = (task_t*)ike_auth_create(&this->public, TRUE); + this->task_manager->queue_task(this->task_manager, task); + task = (task_t*)ike_cert_post_create(&this->public, TRUE); + this->task_manager->queue_task(this->task_manager, task); + task = (task_t*)ike_config_create(&this->public, TRUE); + this->task_manager->queue_task(this->task_manager, task); + task = (task_t*)ike_auth_lifetime_create(&this->public, TRUE); + this->task_manager->queue_task(this->task_manager, task); + if (this->peer_cfg->use_mobike(this->peer_cfg)) + { + task = (task_t*)ike_mobike_create(&this->public, TRUE); + this->task_manager->queue_task(this->task_manager, task); + } +#ifdef ME + task = (task_t*)ike_me_create(&this->public, TRUE); + this->task_manager->queue_task(this->task_manager, task); +#endif /* ME */ + } + +#ifdef ME + if (this->peer_cfg->is_mediation(this->peer_cfg)) + { + if (this->state == IKE_ESTABLISHED) + { + /* mediation connection is already established, retrigger state + * change to notify bus listeners */ + DBG1(DBG_IKE, "mediation connection is already up"); + set_state(this, IKE_ESTABLISHED); + } + DESTROY_IF(child_cfg); + } + else +#endif /* ME */ + { + /* normal IKE_SA with CHILD_SA */ + task = (task_t*)child_create_create(&this->public, child_cfg, FALSE, + tsi, tsr); + child_cfg->destroy(child_cfg); + if (reqid) + { + child_create_t *child_create = (child_create_t*)task; + child_create->use_reqid(child_create, reqid); + } + this->task_manager->queue_task(this->task_manager, task); + +#ifdef ME + if (this->peer_cfg->get_mediated_by(this->peer_cfg)) + { + /* mediated connection, initiate mediation process */ + job_t *job = (job_t*)initiate_mediation_job_create(this->ike_sa_id); + charon->processor->queue_job(charon->processor, job); + return SUCCESS; + } +#endif /* ME */ + } + + return this->task_manager->initiate(this->task_manager); +} + +/** + * Implementation of ike_sa_t.process_message. + */ +static status_t process_message(private_ike_sa_t *this, message_t *message) +{ + status_t status; + bool is_request; + + if (this->state == IKE_PASSIVE) + { /* do not handle messages in passive state */ + return FAILED; + } + + is_request = message->get_request(message); + + status = message->parse_body(message, + this->keymat->get_crypter(this->keymat, TRUE), + this->keymat->get_signer(this->keymat, TRUE)); + if (status != SUCCESS) + { + + if (is_request) + { + switch (status) + { + case NOT_SUPPORTED: + DBG1(DBG_IKE, "critical unknown payloads found"); + if (is_request) + { + send_notify_response(this, message, UNSUPPORTED_CRITICAL_PAYLOAD); + } + break; + case PARSE_ERROR: + DBG1(DBG_IKE, "message parsing failed"); + if (is_request) + { + send_notify_response(this, message, INVALID_SYNTAX); + } + break; + case VERIFY_ERROR: + DBG1(DBG_IKE, "message verification failed"); + if (is_request) + { + send_notify_response(this, message, INVALID_SYNTAX); + } + break; + case FAILED: + DBG1(DBG_IKE, "integrity check failed"); + /* ignored */ + break; + case INVALID_STATE: + DBG1(DBG_IKE, "found encrypted message, but no keys available"); + if (is_request) + { + send_notify_response(this, message, INVALID_SYNTAX); + } + default: + break; + } + } + DBG1(DBG_IKE, "%N %s with message ID %d processing failed", + exchange_type_names, message->get_exchange_type(message), + message->get_request(message) ? "request" : "response", + message->get_message_id(message)); + + if (this->state == IKE_CREATED) + { /* invalid initiation attempt, close SA */ + return DESTROY_ME; + } + } + else + { + host_t *me, *other; + + me = message->get_destination(message); + other = message->get_source(message); + + /* if this IKE_SA is virgin, we check for a config */ + if (this->ike_cfg == NULL) + { + job_t *job; + this->ike_cfg = charon->backends->get_ike_cfg(charon->backends, + me, other); + if (this->ike_cfg == NULL) + { + /* no config found for these hosts, destroy */ + DBG1(DBG_IKE, "no IKE config found for %H...%H, sending %N", + me, other, notify_type_names, NO_PROPOSAL_CHOSEN); + send_notify_response(this, message, NO_PROPOSAL_CHOSEN); + return DESTROY_ME; + } + /* add a timeout if peer does not establish it completely */ + job = (job_t*)delete_ike_sa_job_create(this->ike_sa_id, FALSE); + charon->scheduler->schedule_job(charon->scheduler, job, + HALF_OPEN_IKE_SA_TIMEOUT); + } + this->stats[STAT_INBOUND] = time_monotonic(NULL); + /* check if message is trustworthy, and update host information */ + if (this->state == IKE_CREATED || this->state == IKE_CONNECTING || + message->get_exchange_type(message) != IKE_SA_INIT) + { + if (!supports_extension(this, EXT_MOBIKE)) + { /* with MOBIKE, we do no implicit updates */ + update_hosts(this, me, other); + } + } + status = this->task_manager->process_message(this->task_manager, message); + if (message->get_exchange_type(message) == IKE_AUTH && + this->state == IKE_ESTABLISHED) + { /* authentication completed */ + flush_auth_cfgs(this); + } + } + return status; +} + +/** + * Implementation of ike_sa_t.get_id. + */ +static ike_sa_id_t* get_id(private_ike_sa_t *this) +{ + return this->ike_sa_id; +} + +/** + * Implementation of ike_sa_t.get_my_id. + */ +static identification_t* get_my_id(private_ike_sa_t *this) +{ + return this->my_id; +} + +/** + * Implementation of ike_sa_t.set_my_id. + */ +static void set_my_id(private_ike_sa_t *this, identification_t *me) +{ + DESTROY_IF(this->my_id); + this->my_id = me; +} + +/** + * Implementation of ike_sa_t.get_other_id. + */ +static identification_t* get_other_id(private_ike_sa_t *this) +{ + return this->other_id; +} + +/** + * Implementation of ike_sa_t.get_other_eap_id. + */ +static identification_t* get_other_eap_id(private_ike_sa_t *this) +{ + identification_t *id = NULL, *current; + enumerator_t *enumerator; + auth_cfg_t *cfg; + + enumerator = this->other_auths->create_enumerator(this->other_auths); + while (enumerator->enumerate(enumerator, &cfg)) + { + /* prefer EAP-Identity of last round */ + current = cfg->get(cfg, AUTH_RULE_EAP_IDENTITY); + if (!current || current->get_type(current) == ID_ANY) + { + current = cfg->get(cfg, AUTH_RULE_IDENTITY); + } + if (current && current->get_type(current) != ID_ANY) + { + id = current; + continue; + } + } + enumerator->destroy(enumerator); + if (id) + { + return id; + } + return this->other_id; +} + +/** + * Implementation of ike_sa_t.set_other_id. + */ +static void set_other_id(private_ike_sa_t *this, identification_t *other) +{ + DESTROY_IF(this->other_id); + this->other_id = other; +} + +/** + * Implementation of ike_sa_t.add_child_sa. + */ +static void add_child_sa(private_ike_sa_t *this, child_sa_t *child_sa) +{ + this->child_sas->insert_last(this->child_sas, child_sa); +} + +/** + * Implementation of ike_sa_t.get_child_sa. + */ +static child_sa_t* get_child_sa(private_ike_sa_t *this, protocol_id_t protocol, + u_int32_t spi, bool inbound) +{ + iterator_t *iterator; + child_sa_t *current, *found = NULL; + + iterator = this->child_sas->create_iterator(this->child_sas, TRUE); + while (iterator->iterate(iterator, (void**)¤t)) + { + if (current->get_spi(current, inbound) == spi && + current->get_protocol(current) == protocol) + { + found = current; + } + } + iterator->destroy(iterator); + return found; +} + +/** + * Implementation of ike_sa_t.create_child_sa_iterator. + */ +static iterator_t* create_child_sa_iterator(private_ike_sa_t *this) +{ + return this->child_sas->create_iterator(this->child_sas, TRUE); +} + +/** + * Implementation of ike_sa_t.rekey_child_sa. + */ +static status_t rekey_child_sa(private_ike_sa_t *this, protocol_id_t protocol, + u_int32_t spi) +{ + child_rekey_t *child_rekey; + + child_rekey = child_rekey_create(&this->public, protocol, spi); + this->task_manager->queue_task(this->task_manager, &child_rekey->task); + return this->task_manager->initiate(this->task_manager); +} + +/** + * Implementation of ike_sa_t.delete_child_sa. + */ +static status_t delete_child_sa(private_ike_sa_t *this, protocol_id_t protocol, + u_int32_t spi) +{ + child_delete_t *child_delete; + + child_delete = child_delete_create(&this->public, protocol, spi); + this->task_manager->queue_task(this->task_manager, &child_delete->task); + return this->task_manager->initiate(this->task_manager); +} + +/** + * Implementation of ike_sa_t.destroy_child_sa. + */ +static status_t destroy_child_sa(private_ike_sa_t *this, protocol_id_t protocol, + u_int32_t spi) +{ + iterator_t *iterator; + child_sa_t *child_sa; + status_t status = NOT_FOUND; + + iterator = this->child_sas->create_iterator(this->child_sas, TRUE); + while (iterator->iterate(iterator, (void**)&child_sa)) + { + if (child_sa->get_protocol(child_sa) == protocol && + child_sa->get_spi(child_sa, TRUE) == spi) + { + child_sa->destroy(child_sa); + iterator->remove(iterator); + status = SUCCESS; + break; + } + } + iterator->destroy(iterator); + return status; +} + +/** + * Implementation of public_ike_sa_t.delete. + */ +static status_t delete_(private_ike_sa_t *this) +{ + ike_delete_t *ike_delete; + + switch (this->state) + { + case IKE_ESTABLISHED: + case IKE_REKEYING: + ike_delete = ike_delete_create(&this->public, TRUE); + this->task_manager->queue_task(this->task_manager, &ike_delete->task); + return this->task_manager->initiate(this->task_manager); + case IKE_CREATED: + DBG1(DBG_IKE, "deleting unestablished IKE_SA"); + break; + case IKE_PASSIVE: + break; + default: + DBG1(DBG_IKE, "destroying IKE_SA in state %N " + "without notification", ike_sa_state_names, this->state); + charon->bus->ike_updown(charon->bus, &this->public, FALSE); + break; + } + return DESTROY_ME; +} + +/** + * Implementation of ike_sa_t.rekey. + */ +static status_t rekey(private_ike_sa_t *this) +{ + ike_rekey_t *ike_rekey; + + ike_rekey = ike_rekey_create(&this->public, TRUE); + + this->task_manager->queue_task(this->task_manager, &ike_rekey->task); + return this->task_manager->initiate(this->task_manager); +} + +/** + * Implementation of ike_sa_t.reauth + */ +static status_t reauth(private_ike_sa_t *this) +{ + task_t *task; + + /* we can't reauthenticate as responder when we use EAP or virtual IPs. + * If the peer does not support RFC4478, there is no way to keep the + * IKE_SA up. */ + if (!has_condition(this, COND_ORIGINAL_INITIATOR)) + { + DBG1(DBG_IKE, "initiator did not reauthenticate as requested"); + if (this->other_virtual_ip != NULL || + has_condition(this, COND_EAP_AUTHENTICATED) +#ifdef ME + /* as mediation server we too cannot reauth the IKE_SA */ + || this->is_mediation_server +#endif /* ME */ + ) + { + time_t now = time_monotonic(NULL); + + DBG1(DBG_IKE, "IKE_SA will timeout in %V", + &now, &this->stats[STAT_DELETE]); + return FAILED; + } + else + { + DBG1(DBG_IKE, "reauthenticating actively"); + } + } + task = (task_t*)ike_reauth_create(&this->public); + this->task_manager->queue_task(this->task_manager, task); + + return this->task_manager->initiate(this->task_manager); +} + +/** + * Implementation of ike_sa_t.reestablish + */ +static status_t reestablish(private_ike_sa_t *this) +{ + ike_sa_t *new; + host_t *host; + action_t action; + iterator_t *iterator; + child_sa_t *child_sa; + child_cfg_t *child_cfg; + bool restart = FALSE; + status_t status = FAILED; + + /* check if we have children to keep up at all */ + iterator = create_child_sa_iterator(this); + while (iterator->iterate(iterator, (void**)&child_sa)) + { + child_cfg = child_sa->get_config(child_sa); + if (this->state == IKE_DELETING) + { + action = child_cfg->get_close_action(child_cfg); + } + else + { + action = child_cfg->get_dpd_action(child_cfg); + } + switch (action) + { + case ACTION_RESTART: + restart = TRUE; + break; + case ACTION_ROUTE: + charon->traps->install(charon->traps, this->peer_cfg, child_cfg); + break; + default: + break; + } + } + iterator->destroy(iterator); +#ifdef ME + /* mediation connections have no children, keep them up anyway */ + if (this->peer_cfg->is_mediation(this->peer_cfg)) + { + restart = TRUE; + } +#endif /* ME */ + if (!restart) + { + return FAILED; + } + + /* check if we are able to reestablish this IKE_SA */ + if (!has_condition(this, COND_ORIGINAL_INITIATOR) && + (this->other_virtual_ip != NULL || + has_condition(this, COND_EAP_AUTHENTICATED) +#ifdef ME + || this->is_mediation_server +#endif /* ME */ + )) + { + DBG1(DBG_IKE, "unable to reestablish IKE_SA due asymetric setup"); + return FAILED; + } + + new = charon->ike_sa_manager->checkout_new(charon->ike_sa_manager, TRUE); + new->set_peer_cfg(new, this->peer_cfg); + host = this->other_host; + new->set_other_host(new, host->clone(host)); + host = this->my_host; + new->set_my_host(new, host->clone(host)); + /* if we already have a virtual IP, we reuse it */ + host = this->my_virtual_ip; + if (host) + { + new->set_virtual_ip(new, TRUE, host); + } + +#ifdef ME + if (this->peer_cfg->is_mediation(this->peer_cfg)) + { + status = new->initiate(new, NULL, 0, NULL, NULL); + } + else +#endif /* ME */ + { + iterator = create_child_sa_iterator(this); + while (iterator->iterate(iterator, (void**)&child_sa)) + { + child_cfg = child_sa->get_config(child_sa); + if (this->state == IKE_DELETING) + { + action = child_cfg->get_close_action(child_cfg); + } + else + { + action = child_cfg->get_dpd_action(child_cfg); + } + switch (action) + { + case ACTION_RESTART: + DBG1(DBG_IKE, "restarting CHILD_SA %s", + child_cfg->get_name(child_cfg)); + child_cfg->get_ref(child_cfg); + status = new->initiate(new, child_cfg, 0, NULL, NULL); + break; + default: + continue; + } + if (status == DESTROY_ME) + { + break; + } + } + iterator->destroy(iterator); + } + + if (status == DESTROY_ME) + { + charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, new); + status = FAILED; + } + else + { + charon->ike_sa_manager->checkin(charon->ike_sa_manager, new); + status = SUCCESS; + } + charon->bus->set_sa(charon->bus, &this->public); + return status; +} + +/** + * Implementation of ike_sa_t.retransmit. + */ +static status_t retransmit(private_ike_sa_t *this, u_int32_t message_id) +{ + this->stats[STAT_OUTBOUND] = time_monotonic(NULL); + if (this->task_manager->retransmit(this->task_manager, message_id) != SUCCESS) + { + /* send a proper signal to brief interested bus listeners */ + switch (this->state) + { + case IKE_CONNECTING: + { + /* retry IKE_SA_INIT if we have multiple keyingtries */ + u_int32_t tries = this->peer_cfg->get_keyingtries(this->peer_cfg); + this->keyingtry++; + if (tries == 0 || tries > this->keyingtry) + { + DBG1(DBG_IKE, "peer not responding, trying again (%d/%d)", + this->keyingtry + 1, tries); + reset(this); + return this->task_manager->initiate(this->task_manager); + } + DBG1(DBG_IKE, "establishing IKE_SA failed, peer not responding"); + break; + } + case IKE_DELETING: + DBG1(DBG_IKE, "proper IKE_SA delete failed, peer not responding"); + break; + case IKE_REKEYING: + DBG1(DBG_IKE, "rekeying IKE_SA failed, peer not responding"); + /* FALL */ + default: + reestablish(this); + break; + } + return DESTROY_ME; + } + return SUCCESS; +} + +/** + * Implementation of ike_sa_t.set_auth_lifetime. + */ +static void set_auth_lifetime(private_ike_sa_t *this, u_int32_t lifetime) +{ + u_int32_t reduction = this->peer_cfg->get_over_time(this->peer_cfg); + u_int32_t reauth_time = time_monotonic(NULL) + lifetime - reduction; + + if (lifetime < reduction) + { + DBG1(DBG_IKE, "received AUTH_LIFETIME of %ds, starting reauthentication", + lifetime); + charon->processor->queue_job(charon->processor, + (job_t*)rekey_ike_sa_job_create(this->ike_sa_id, TRUE)); + } + else if (this->stats[STAT_REAUTH] == 0 || + this->stats[STAT_REAUTH] > reauth_time) + { + this->stats[STAT_REAUTH] = reauth_time; + DBG1(DBG_IKE, "received AUTH_LIFETIME of %ds, scheduling reauthentication" + " in %ds", lifetime, lifetime - reduction); + charon->scheduler->schedule_job(charon->scheduler, + (job_t*)rekey_ike_sa_job_create(this->ike_sa_id, TRUE), + lifetime - reduction); + } + else + { + DBG1(DBG_IKE, "received AUTH_LIFETIME of %ds, " + "reauthentication already scheduled in %ds", lifetime, + this->stats[STAT_REAUTH] - time_monotonic(NULL)); + } +} + +/** + * Implementation of ike_sa_t.roam. + */ +static status_t roam(private_ike_sa_t *this, bool address) +{ + host_t *src; + ike_mobike_t *mobike; + + switch (this->state) + { + case IKE_CREATED: + case IKE_DELETING: + case IKE_DESTROYING: + case IKE_PASSIVE: + return SUCCESS; + default: + break; + } + /* responder just updates the peer about changed address config */ + if (!this->ike_sa_id->is_initiator(this->ike_sa_id)) + { + if (supports_extension(this, EXT_MOBIKE) && address) + { + DBG1(DBG_IKE, "sending address list update using MOBIKE"); + mobike = ike_mobike_create(&this->public, TRUE); + this->task_manager->queue_task(this->task_manager, (task_t*)mobike); + return this->task_manager->initiate(this->task_manager); + } + return SUCCESS; + } + + /* keep existing path if possible */ + src = charon->kernel_interface->get_source_addr(charon->kernel_interface, + this->other_host, this->my_host); + if (src) + { + if (src->ip_equals(src, this->my_host)) + { + DBG2(DBG_IKE, "keeping connection path %H - %H", + src, this->other_host); + src->destroy(src); + set_condition(this, COND_STALE, FALSE); + return SUCCESS; + } + src->destroy(src); + + } + else + { + /* check if we find a route at all */ + enumerator_t *enumerator; + host_t *addr; + + src = charon->kernel_interface->get_source_addr(charon->kernel_interface, + this->other_host, NULL); + if (!src) + { + enumerator = this->additional_addresses->create_enumerator( + this->additional_addresses); + while (enumerator->enumerate(enumerator, &addr)) + { + DBG1(DBG_IKE, "looking for a route to %H ...", addr); + src = charon->kernel_interface->get_source_addr( + charon->kernel_interface, addr, NULL); + if (src) + { + break; + } + } + enumerator->destroy(enumerator); + } + if (!src) + { + DBG1(DBG_IKE, "no route found to reach %H, MOBIKE update deferred", + this->other_host); + set_condition(this, COND_STALE, TRUE); + return SUCCESS; + } + src->destroy(src); + } + set_condition(this, COND_STALE, FALSE); + + /* update addresses with mobike, if supported ... */ + if (supports_extension(this, EXT_MOBIKE)) + { + DBG1(DBG_IKE, "requesting address change using MOBIKE"); + mobike = ike_mobike_create(&this->public, TRUE); + mobike->roam(mobike, address); + this->task_manager->queue_task(this->task_manager, (task_t*)mobike); + return this->task_manager->initiate(this->task_manager); + } + DBG1(DBG_IKE, "reauthenticating IKE_SA due to address change"); + /* ... reauth if not */ + return reauth(this); +} + +/** + * Implementation of ike_sa_t.add_configuration_attribute + */ +static void add_configuration_attribute(private_ike_sa_t *this, + attribute_handler_t *handler, + configuration_attribute_type_t type, chunk_t data) +{ + attribute_entry_t *entry = malloc_thing(attribute_entry_t); + + entry->handler = handler; + entry->type = type; + entry->data = chunk_clone(data); + + this->attributes->insert_last(this->attributes, entry); +} + +/** + * Implementation of ike_sa_t.inherit. + */ +static status_t inherit(private_ike_sa_t *this, private_ike_sa_t *other) +{ + child_sa_t *child_sa; + attribute_entry_t *entry; + + /* apply hosts and ids */ + this->my_host->destroy(this->my_host); + this->other_host->destroy(this->other_host); + this->my_id->destroy(this->my_id); + this->other_id->destroy(this->other_id); + this->my_host = other->my_host->clone(other->my_host); + this->other_host = other->other_host->clone(other->other_host); + this->my_id = other->my_id->clone(other->my_id); + this->other_id = other->other_id->clone(other->other_id); + + /* apply virtual assigned IPs... */ + if (other->my_virtual_ip) + { + this->my_virtual_ip = other->my_virtual_ip; + other->my_virtual_ip = NULL; + } + if (other->other_virtual_ip) + { + this->other_virtual_ip = other->other_virtual_ip; + other->other_virtual_ip = NULL; + } + + /* ... and configuration attributes */ + while (other->attributes->remove_last(other->attributes, + (void**)&entry) == SUCCESS) + { + this->attributes->insert_first(this->attributes, entry); + } + + /* inherit all conditions */ + this->conditions = other->conditions; + if (this->conditions & COND_NAT_HERE) + { + send_keepalive(this); + } + +#ifdef ME + if (other->is_mediation_server) + { + act_as_mediation_server(this); + } + else if (other->server_reflexive_host) + { + this->server_reflexive_host = other->server_reflexive_host->clone( + other->server_reflexive_host); + } +#endif /* ME */ + + /* adopt all children */ + while (other->child_sas->remove_last(other->child_sas, + (void**)&child_sa) == SUCCESS) + { + this->child_sas->insert_first(this->child_sas, (void*)child_sa); + } + + /* move pending tasks to the new IKE_SA */ + this->task_manager->adopt_tasks(this->task_manager, other->task_manager); + + /* reauthentication timeout survives a rekeying */ + if (other->stats[STAT_REAUTH]) + { + time_t reauth, delete, now = time_monotonic(NULL); + + this->stats[STAT_REAUTH] = other->stats[STAT_REAUTH]; + reauth = this->stats[STAT_REAUTH] - now; + delete = reauth + this->peer_cfg->get_over_time(this->peer_cfg); + this->stats[STAT_DELETE] = this->stats[STAT_REAUTH] + delete; + DBG1(DBG_IKE, "rescheduling reauthentication in %ds after rekeying, " + "lifetime reduced to %ds", reauth, delete); + charon->scheduler->schedule_job(charon->scheduler, + (job_t*)rekey_ike_sa_job_create(this->ike_sa_id, TRUE), reauth); + charon->scheduler->schedule_job(charon->scheduler, + (job_t*)delete_ike_sa_job_create(this->ike_sa_id, TRUE), delete); + } + /* we have to initate here, there may be new tasks to handle */ + return this->task_manager->initiate(this->task_manager); +} + +/** + * Implementation of ike_sa_t.destroy. + */ +static void destroy(private_ike_sa_t *this) +{ + attribute_entry_t *entry; + + charon->bus->set_sa(charon->bus, &this->public); + + set_state(this, IKE_DESTROYING); + + /* remove attributes first, as we pass the IKE_SA to the handler */ + while (this->attributes->remove_last(this->attributes, + (void**)&entry) == SUCCESS) + { + hydra->attributes->release(hydra->attributes, entry->handler, + this->other_id, entry->type, entry->data); + free(entry->data.ptr); + free(entry); + } + this->attributes->destroy(this->attributes); + + this->child_sas->destroy_offset(this->child_sas, offsetof(child_sa_t, destroy)); + + /* unset SA after here to avoid usage by the listeners */ + charon->bus->set_sa(charon->bus, NULL); + + this->task_manager->destroy(this->task_manager); + this->keymat->destroy(this->keymat); + + if (this->my_virtual_ip) + { + charon->kernel_interface->del_ip(charon->kernel_interface, + this->my_virtual_ip); + this->my_virtual_ip->destroy(this->my_virtual_ip); + } + if (this->other_virtual_ip) + { + if (this->peer_cfg && this->peer_cfg->get_pool(this->peer_cfg)) + { + hydra->attributes->release_address(hydra->attributes, + this->peer_cfg->get_pool(this->peer_cfg), + this->other_virtual_ip, get_other_eap_id(this)); + } + this->other_virtual_ip->destroy(this->other_virtual_ip); + } + this->additional_addresses->destroy_offset(this->additional_addresses, + offsetof(host_t, destroy)); +#ifdef ME + if (this->is_mediation_server) + { + charon->mediation_manager->remove(charon->mediation_manager, + this->ike_sa_id); + } + DESTROY_IF(this->server_reflexive_host); + chunk_free(&this->connect_id); +#endif /* ME */ + free(this->nat_detection_dest.ptr); + + DESTROY_IF(this->my_host); + DESTROY_IF(this->other_host); + DESTROY_IF(this->my_id); + DESTROY_IF(this->other_id); + DESTROY_IF(this->local_host); + DESTROY_IF(this->remote_host); + + DESTROY_IF(this->ike_cfg); + DESTROY_IF(this->peer_cfg); + DESTROY_IF(this->proposal); + this->my_auth->destroy(this->my_auth); + this->other_auth->destroy(this->other_auth); + this->my_auths->destroy_offset(this->my_auths, + offsetof(auth_cfg_t, destroy)); + this->other_auths->destroy_offset(this->other_auths, + offsetof(auth_cfg_t, destroy)); + + this->ike_sa_id->destroy(this->ike_sa_id); + free(this); +} + +/* + * Described in header. + */ +ike_sa_t * ike_sa_create(ike_sa_id_t *ike_sa_id) +{ + private_ike_sa_t *this = malloc_thing(private_ike_sa_t); + static u_int32_t unique_id = 0; + + /* Public functions */ + this->public.get_state = (ike_sa_state_t (*)(ike_sa_t*)) get_state; + this->public.set_state = (void (*)(ike_sa_t*,ike_sa_state_t)) set_state; + this->public.get_name = (char* (*)(ike_sa_t*))get_name; + this->public.get_statistic = (u_int32_t(*)(ike_sa_t*, statistic_t kind))get_statistic; + this->public.process_message = (status_t (*)(ike_sa_t*, message_t*)) process_message; + this->public.initiate = (status_t (*)(ike_sa_t*,child_cfg_t*,u_int32_t,traffic_selector_t*,traffic_selector_t*)) initiate; + this->public.get_ike_cfg = (ike_cfg_t* (*)(ike_sa_t*))get_ike_cfg; + this->public.set_ike_cfg = (void (*)(ike_sa_t*,ike_cfg_t*))set_ike_cfg; + this->public.get_peer_cfg = (peer_cfg_t* (*)(ike_sa_t*))get_peer_cfg; + this->public.set_peer_cfg = (void (*)(ike_sa_t*,peer_cfg_t*))set_peer_cfg; + this->public.get_auth_cfg = (auth_cfg_t*(*)(ike_sa_t*, bool local))get_auth_cfg; + this->public.create_auth_cfg_enumerator = (enumerator_t*(*)(ike_sa_t*, bool local))create_auth_cfg_enumerator; + this->public.add_auth_cfg = (void(*)(ike_sa_t*, bool local, auth_cfg_t *cfg))add_auth_cfg; + this->public.get_proposal = (proposal_t*(*)(ike_sa_t*))get_proposal; + this->public.set_proposal = (void(*)(ike_sa_t*, proposal_t *proposal))set_proposal; + this->public.get_id = (ike_sa_id_t* (*)(ike_sa_t*)) get_id; + this->public.get_my_host = (host_t* (*)(ike_sa_t*)) get_my_host; + this->public.set_my_host = (void (*)(ike_sa_t*,host_t*)) set_my_host; + this->public.get_other_host = (host_t* (*)(ike_sa_t*)) get_other_host; + this->public.set_other_host = (void (*)(ike_sa_t*,host_t*)) set_other_host; + this->public.set_message_id = (void(*)(ike_sa_t*, bool inbound, u_int32_t mid))set_message_id; + this->public.update_hosts = (void(*)(ike_sa_t*, host_t *me, host_t *other))update_hosts; + this->public.get_my_id = (identification_t* (*)(ike_sa_t*)) get_my_id; + this->public.set_my_id = (void (*)(ike_sa_t*,identification_t*)) set_my_id; + this->public.get_other_id = (identification_t* (*)(ike_sa_t*)) get_other_id; + this->public.set_other_id = (void (*)(ike_sa_t*,identification_t*)) set_other_id; + this->public.get_other_eap_id = (identification_t* (*)(ike_sa_t*)) get_other_eap_id; + this->public.enable_extension = (void(*)(ike_sa_t*, ike_extension_t extension))enable_extension; + this->public.supports_extension = (bool(*)(ike_sa_t*, ike_extension_t extension))supports_extension; + this->public.set_condition = (void (*)(ike_sa_t*, ike_condition_t,bool)) set_condition; + this->public.has_condition = (bool (*)(ike_sa_t*,ike_condition_t)) has_condition; + this->public.set_pending_updates = (void(*)(ike_sa_t*, u_int32_t updates))set_pending_updates; + this->public.get_pending_updates = (u_int32_t(*)(ike_sa_t*))get_pending_updates; + this->public.create_additional_address_iterator = (iterator_t*(*)(ike_sa_t*))create_additional_address_iterator; + this->public.add_additional_address = (void(*)(ike_sa_t*, host_t *host))add_additional_address; + this->public.has_mapping_changed = (bool(*)(ike_sa_t*, chunk_t hash))has_mapping_changed; + this->public.retransmit = (status_t (*)(ike_sa_t *, u_int32_t)) retransmit; + this->public.delete = (status_t (*)(ike_sa_t*))delete_; + this->public.destroy = (void (*)(ike_sa_t*))destroy; + this->public.send_dpd = (status_t (*)(ike_sa_t*)) send_dpd; + this->public.send_keepalive = (void (*)(ike_sa_t*)) send_keepalive; + this->public.get_keymat = (keymat_t*(*)(ike_sa_t*))get_keymat; + this->public.add_child_sa = (void (*)(ike_sa_t*,child_sa_t*)) add_child_sa; + this->public.get_child_sa = (child_sa_t* (*)(ike_sa_t*,protocol_id_t,u_int32_t,bool)) get_child_sa; + this->public.create_child_sa_iterator = (iterator_t* (*)(ike_sa_t*)) create_child_sa_iterator; + this->public.rekey_child_sa = (status_t (*)(ike_sa_t*,protocol_id_t,u_int32_t)) rekey_child_sa; + this->public.delete_child_sa = (status_t (*)(ike_sa_t*,protocol_id_t,u_int32_t)) delete_child_sa; + this->public.destroy_child_sa = (status_t (*)(ike_sa_t*,protocol_id_t,u_int32_t))destroy_child_sa; + this->public.rekey = (status_t (*)(ike_sa_t*))rekey; + this->public.reauth = (status_t (*)(ike_sa_t*))reauth; + this->public.reestablish = (status_t (*)(ike_sa_t*))reestablish; + this->public.set_auth_lifetime = (void(*)(ike_sa_t*, u_int32_t lifetime))set_auth_lifetime; + this->public.roam = (status_t(*)(ike_sa_t*,bool))roam; + this->public.inherit = (status_t (*)(ike_sa_t*,ike_sa_t*))inherit; + this->public.generate_message = (status_t (*)(ike_sa_t*,message_t*,packet_t**))generate_message; + this->public.reset = (void (*)(ike_sa_t*))reset; + this->public.get_unique_id = (u_int32_t (*)(ike_sa_t*))get_unique_id; + this->public.set_virtual_ip = (void (*)(ike_sa_t*,bool,host_t*))set_virtual_ip; + this->public.get_virtual_ip = (host_t* (*)(ike_sa_t*,bool))get_virtual_ip; + this->public.add_configuration_attribute = (void(*)(ike_sa_t*, attribute_handler_t *handler,configuration_attribute_type_t type, chunk_t data))add_configuration_attribute; + this->public.set_kmaddress = (void (*)(ike_sa_t*,host_t*,host_t*))set_kmaddress; +#ifdef ME + this->public.act_as_mediation_server = (void (*)(ike_sa_t*)) act_as_mediation_server; + this->public.get_server_reflexive_host = (host_t* (*)(ike_sa_t*)) get_server_reflexive_host; + this->public.set_server_reflexive_host = (void (*)(ike_sa_t*,host_t*)) set_server_reflexive_host; + this->public.get_connect_id = (chunk_t (*)(ike_sa_t*)) get_connect_id; + this->public.initiate_mediation = (status_t (*)(ike_sa_t*,peer_cfg_t*)) initiate_mediation; + this->public.initiate_mediated = (status_t (*)(ike_sa_t*,host_t*,host_t*,chunk_t)) initiate_mediated; + this->public.relay = (status_t (*)(ike_sa_t*,identification_t*,chunk_t,chunk_t,linked_list_t*,bool)) relay; + this->public.callback = (status_t (*)(ike_sa_t*,identification_t*)) callback; + this->public.respond = (status_t (*)(ike_sa_t*,identification_t*,chunk_t)) respond; +#endif /* ME */ + + /* initialize private fields */ + this->ike_sa_id = ike_sa_id->clone(ike_sa_id); + this->child_sas = linked_list_create(); + this->my_host = host_create_any(AF_INET); + this->my_host->set_port(this->my_host, IKEV2_UDP_PORT); + this->other_host = host_create_any(AF_INET); + this->my_id = identification_create_from_encoding(ID_ANY, chunk_empty); + this->other_id = identification_create_from_encoding(ID_ANY, chunk_empty); + this->extensions = 0; + this->conditions = 0; + this->keymat = keymat_create(ike_sa_id->is_initiator(ike_sa_id)); + this->state = IKE_CREATED; + this->keepalive_interval = lib->settings->get_time(lib->settings, + "charon.keep_alive", KEEPALIVE_INTERVAL); + memset(this->stats, 0, sizeof(this->stats)); + this->stats[STAT_INBOUND] = this->stats[STAT_OUTBOUND] = time_monotonic(NULL); + this->ike_cfg = NULL; + this->peer_cfg = NULL; + this->my_auth = auth_cfg_create(); + this->other_auth = auth_cfg_create(); + this->my_auths = linked_list_create(); + this->other_auths = linked_list_create(); + this->proposal = NULL; + this->task_manager = task_manager_create(&this->public); + this->unique_id = ++unique_id; + this->my_virtual_ip = NULL; + this->other_virtual_ip = NULL; + this->additional_addresses = linked_list_create(); + this->attributes = linked_list_create(); + this->nat_detection_dest = chunk_empty; + this->pending_updates = 0; + this->keyingtry = 0; + this->local_host = NULL; + this->remote_host = NULL; +#ifdef ME + this->is_mediation_server = FALSE; + this->server_reflexive_host = NULL; + this->connect_id = chunk_empty; +#endif /* ME */ + + return &this->public; +} diff --git a/src/libcharon/sa/ike_sa.h b/src/libcharon/sa/ike_sa.h new file mode 100644 index 000000000..c61502edf --- /dev/null +++ b/src/libcharon/sa/ike_sa.h @@ -0,0 +1,921 @@ +/* + * Copyright (C) 2006-2008 Tobias Brunner + * Copyright (C) 2006 Daniel Roethlisberger + * Copyright (C) 2005-2009 Martin Willi + * Copyright (C) 2005 Jan Hutter + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup ike_sa ike_sa + * @{ @ingroup sa + */ + +#ifndef IKE_SA_H_ +#define IKE_SA_H_ + +typedef enum ike_extension_t ike_extension_t; +typedef enum ike_condition_t ike_condition_t; +typedef enum ike_sa_state_t ike_sa_state_t; +typedef enum statistic_t statistic_t; +typedef struct ike_sa_t ike_sa_t; + +#include <library.h> +#include <attributes/attribute_handler.h> +#include <encoding/message.h> +#include <encoding/payloads/proposal_substructure.h> +#include <encoding/payloads/configuration_attribute.h> +#include <sa/ike_sa_id.h> +#include <sa/child_sa.h> +#include <sa/tasks/task.h> +#include <sa/keymat.h> +#include <config/peer_cfg.h> +#include <config/ike_cfg.h> +#include <config/auth_cfg.h> + +/** + * Timeout in seconds after that a half open IKE_SA gets deleted. + */ +#define HALF_OPEN_IKE_SA_TIMEOUT 30 + +/** + * Interval to send keepalives when NATed, in seconds. + */ +#define KEEPALIVE_INTERVAL 20 + +/** + * After which time rekeying should be retried if it failed, in seconds. + */ +#define RETRY_INTERVAL 30 + +/** + * Jitter to subtract from RETRY_INTERVAL to randomize rekey retry. + */ +#define RETRY_JITTER 20 + +/** + * Extensions (or optional features) the peer supports + */ +enum ike_extension_t { + + /** + * peer supports NAT traversal as specified in RFC4306 + */ + EXT_NATT = (1<<0), + + /** + * peer supports MOBIKE (RFC4555) + */ + EXT_MOBIKE = (1<<1), + + /** + * peer supports HTTP cert lookups as specified in RFC4306 + */ + EXT_HASH_AND_URL = (1<<2), + + /** + * peer supports multiple authentication exchanges, RFC4739 + */ + EXT_MULTIPLE_AUTH = (1<<3), + + /** + * peer uses strongSwan, accept private use extensions + */ + EXT_STRONGSWAN = (1<<4), + + /** + * peer supports EAP-only authentication, draft-eronen-ipsec-ikev2-eap-auth + */ + EXT_EAP_ONLY_AUTHENTICATION = (1<<5), +}; + +/** + * Conditions of an IKE_SA, change during its lifetime + */ +enum ike_condition_t { + + /** + * Connection is natted (or faked) somewhere + */ + COND_NAT_ANY = (1<<0), + + /** + * we are behind NAT + */ + COND_NAT_HERE = (1<<1), + + /** + * other is behind NAT + */ + COND_NAT_THERE = (1<<2), + + /** + * Faking NAT to enforce UDP encapsulation + */ + COND_NAT_FAKE = (1<<3), + + /** + * peer has been authenticated using EAP at least once + */ + COND_EAP_AUTHENTICATED = (1<<4), + + /** + * received a certificate request from the peer + */ + COND_CERTREQ_SEEN = (1<<5), + + /** + * Local peer is the "original" IKE initiator. Unaffected from rekeying. + */ + COND_ORIGINAL_INITIATOR = (1<<6), + + /** + * IKE_SA is stale, the peer is currently unreachable (MOBIKE) + */ + COND_STALE = (1<<7), +}; + +/** + * Timing information and statistics to query from an SA + */ +enum statistic_t { + /** Timestamp of SA establishement */ + STAT_ESTABLISHED = 0, + /** Timestamp of scheudled rekeying */ + STAT_REKEY, + /** Timestamp of scheudled reauthentication */ + STAT_REAUTH, + /** Timestamp of scheudled delete */ + STAT_DELETE, + /** Timestamp of last inbound IKE packet */ + STAT_INBOUND, + /** Timestamp of last outbound IKE packet */ + STAT_OUTBOUND, + + STAT_MAX +}; + +/** + * State of an IKE_SA. + * + * An IKE_SA passes various states in its lifetime. A newly created + * SA is in the state CREATED. + * @verbatim + +----------------+ + ¦ SA_CREATED ¦ + +----------------+ + ¦ + on initiate()---> ¦ <----- on IKE_SA_INIT received + V + +----------------+ + ¦ SA_CONNECTING ¦ + +----------------+ + ¦ + ¦ <----- on IKE_AUTH successfully completed + V + +----------------+ + ¦ SA_ESTABLISHED ¦-------------------------+ <-- on rekeying + +----------------+ ¦ + ¦ V + on delete()---> ¦ <----- on IKE_SA +-------------+ + ¦ delete request ¦ SA_REKEYING ¦ + ¦ received +-------------+ + V ¦ + +----------------+ ¦ + ¦ SA_DELETING ¦<------------------------+ <-- after rekeying + +----------------+ + ¦ + ¦ <----- after delete() acknowledged + ¦ + \V/ + X + / \ + @endverbatim + */ +enum ike_sa_state_t { + + /** + * IKE_SA just got created, but is not initiating nor responding yet. + */ + IKE_CREATED, + + /** + * IKE_SA gets initiated actively or passively + */ + IKE_CONNECTING, + + /** + * IKE_SA is fully established + */ + IKE_ESTABLISHED, + + /** + * IKE_SA is managed externally and does not process messages + */ + IKE_PASSIVE, + + /** + * IKE_SA rekeying in progress + */ + IKE_REKEYING, + + /** + * IKE_SA is in progress of deletion + */ + IKE_DELETING, + + /** + * IKE_SA object gets destroyed + */ + IKE_DESTROYING, +}; + +/** + * enum names for ike_sa_state_t. + */ +extern enum_name_t *ike_sa_state_names; + +/** + * Class ike_sa_t representing an IKE_SA. + * + * An IKE_SA contains crypto information related to a connection + * with a peer. It contains multiple IPsec CHILD_SA, for which + * it is responsible. All traffic is handled by an IKE_SA, using + * the task manager and its tasks. + */ +struct ike_sa_t { + + /** + * Get the id of the SA. + * + * Returned ike_sa_id_t object is not getting cloned! + * + * @return ike_sa's ike_sa_id_t + */ + ike_sa_id_t* (*get_id) (ike_sa_t *this); + + /** + * Get the numerical ID uniquely defining this IKE_SA. + * + * @return unique ID + */ + u_int32_t (*get_unique_id) (ike_sa_t *this); + + /** + * Get the state of the IKE_SA. + * + * @return state of the IKE_SA + */ + ike_sa_state_t (*get_state) (ike_sa_t *this); + + /** + * Set the state of the IKE_SA. + * + * @param state state to set for the IKE_SA + */ + void (*set_state) (ike_sa_t *this, ike_sa_state_t ike_sa); + + /** + * Get the name of the connection this IKE_SA uses. + * + * @return name + */ + char* (*get_name) (ike_sa_t *this); + + /** + * Get statistic values from the IKE_SA. + * + * @param kind kind of requested value + * @return value as integer + */ + u_int32_t (*get_statistic)(ike_sa_t *this, statistic_t kind); + + /** + * Get the own host address. + * + * @return host address + */ + host_t* (*get_my_host) (ike_sa_t *this); + + /** + * Set the own host address. + * + * @param me host address + */ + void (*set_my_host) (ike_sa_t *this, host_t *me); + + /** + * Get the other peers host address. + * + * @return host address + */ + host_t* (*get_other_host) (ike_sa_t *this); + + /** + * Set the others host address. + * + * @param other host address + */ + void (*set_other_host) (ike_sa_t *this, host_t *other); + + /** + * Update the IKE_SAs host. + * + * Hosts may be NULL to use current host. + * + * @param me new local host address, or NULL + * @param other new remote host address, or NULL + */ + void (*update_hosts)(ike_sa_t *this, host_t *me, host_t *other); + + /** + * Get the own identification. + * + * @return identification + */ + identification_t* (*get_my_id) (ike_sa_t *this); + + /** + * Set the own identification. + * + * @param me identification + */ + void (*set_my_id) (ike_sa_t *this, identification_t *me); + + /** + * Get the other peer's identification. + * + * @return identification + */ + identification_t* (*get_other_id) (ike_sa_t *this); + + /** + * Get the others peer identity, but prefer an EAP-Identity. + * + * @return EAP or IKEv2 identity + */ + identification_t* (*get_other_eap_id)(ike_sa_t *this); + + /** + * Set the other peer's identification. + * + * @param other identification + */ + void (*set_other_id) (ike_sa_t *this, identification_t *other); + + /** + * Get the config used to setup this IKE_SA. + * + * @return ike_config + */ + ike_cfg_t* (*get_ike_cfg) (ike_sa_t *this); + + /** + * Set the config to setup this IKE_SA. + * + * @param config ike_config to use + */ + void (*set_ike_cfg) (ike_sa_t *this, ike_cfg_t* config); + + /** + * Get the peer config used by this IKE_SA. + * + * @return peer_config + */ + peer_cfg_t* (*get_peer_cfg) (ike_sa_t *this); + + /** + * Set the peer config to use with this IKE_SA. + * + * @param config peer_config to use + */ + void (*set_peer_cfg) (ike_sa_t *this, peer_cfg_t *config); + + /** + * Get the authentication config with rules of the current auth round. + * + * @param local TRUE for local rules, FALSE for remote constraints + * @return current cfg + */ + auth_cfg_t* (*get_auth_cfg)(ike_sa_t *this, bool local); + + /** + * Insert a completed authentication round. + * + * @param local TRUE for own rules, FALSE for others constraints + * @param cfg auth config to append + */ + void (*add_auth_cfg)(ike_sa_t *this, bool local, auth_cfg_t *cfg); + + /** + * Create an enumerator over added authentication rounds. + * + * @param local TRUE for own rules, FALSE for others constraints + * @return enumerator over auth_cfg_t + */ + enumerator_t* (*create_auth_cfg_enumerator)(ike_sa_t *this, bool local); + + /** + * Get the selected proposal of this IKE_SA. + * + * @return selected proposal + */ + proposal_t* (*get_proposal)(ike_sa_t *this); + + /** + * Set the proposal selected for this IKE_SA. + * + * @param selected proposal + */ + void (*set_proposal)(ike_sa_t *this, proposal_t *proposal); + + /** + * Set the message id of the IKE_SA. + * + * The IKE_SA stores two message IDs, one for initiating exchanges (send) + * and one to respond to exchanges (expect). + * + * @param initiate TRUE to set message ID for initiating + * @param mid message id to set + */ + void (*set_message_id)(ike_sa_t *this, bool initiate, u_int32_t mid); + + /** + * Add an additional address for the peer. + * + * In MOBIKE, a peer may transmit additional addresses where it is + * reachable. These are stored in the IKE_SA. + * The own list of addresses is not stored, they are queried from + * the kernel when required. + * + * @param host host to add to list + */ + void (*add_additional_address)(ike_sa_t *this, host_t *host); + + /** + * Create an iterator over all additional addresses of the peer. + * + * @return iterator over addresses + */ + iterator_t* (*create_additional_address_iterator)(ike_sa_t *this); + + /** + * Check if mappings have changed on a NAT for our source address. + * + * @param hash received DESTINATION_IP hash + * @return TRUE if mappings have changed + */ + bool (*has_mapping_changed)(ike_sa_t *this, chunk_t hash); + + /** + * Enable an extension the peer supports. + * + * If support for an IKE extension is detected, this method is called + * to enable that extension and behave accordingly. + * + * @param extension extension to enable + */ + void (*enable_extension)(ike_sa_t *this, ike_extension_t extension); + + /** + * Check if the peer supports an extension. + * + * @param extension extension to check for support + * @return TRUE if peer supports it, FALSE otherwise + */ + bool (*supports_extension)(ike_sa_t *this, ike_extension_t extension); + + /** + * Enable/disable a condition flag for this IKE_SA. + * + * @param condition condition to enable/disable + * @param enable TRUE to enable condition, FALSE to disable + */ + void (*set_condition) (ike_sa_t *this, ike_condition_t condition, bool enable); + + /** + * Check if a condition flag is set. + * + * @param condition condition to check + * @return TRUE if condition flag set, FALSE otherwise + */ + bool (*has_condition) (ike_sa_t *this, ike_condition_t condition); + + /** + * Get the number of queued MOBIKE address updates. + * + * @return number of pending updates + */ + u_int32_t (*get_pending_updates)(ike_sa_t *this); + + /** + * Set the number of queued MOBIKE address updates. + * + * @param updates number of pending updates + */ + void (*set_pending_updates)(ike_sa_t *this, u_int32_t updates); + +#ifdef ME + /** + * Activate mediation server functionality for this IKE_SA. + */ + void (*act_as_mediation_server) (ike_sa_t *this); + + /** + * Get the server reflexive host. + * + * @return server reflexive host + */ + host_t* (*get_server_reflexive_host) (ike_sa_t *this); + + /** + * Set the server reflexive host. + * + * @param host server reflexive host + */ + void (*set_server_reflexive_host) (ike_sa_t *this, host_t *host); + + /** + * Get the connect ID. + * + * @return connect ID + */ + chunk_t (*get_connect_id) (ike_sa_t *this); + + /** + * Initiate the mediation of a mediated connection (i.e. initiate a + * ME_CONNECT exchange to a mediation server). + * + * @param mediated_cfg peer_cfg of the mediated connection + * @return + * - SUCCESS if initialization started + * - DESTROY_ME if initialization failed + */ + status_t (*initiate_mediation) (ike_sa_t *this, peer_cfg_t *mediated_cfg); + + /** + * Initiate the mediated connection + * + * @param me local endpoint (gets cloned) + * @param other remote endpoint (gets cloned) + * @param connect_id connect ID (gets cloned) + * @return + * - SUCCESS if initialization started + * - DESTROY_ME if initialization failed + */ + status_t (*initiate_mediated) (ike_sa_t *this, host_t *me, host_t *other, + chunk_t connect_id); + + /** + * Relay data from one peer to another (i.e. initiate a ME_CONNECT exchange + * to a peer). + * + * Data is cloned. + * + * @param requester ID of the requesting peer + * @param connect_id data of the ME_CONNECTID payload + * @param connect_key data of the ME_CONNECTKEY payload + * @param endpoints endpoints + * @param response TRUE if this is a response + * @return + * - SUCCESS if relay started + * - DESTROY_ME if relay failed + */ + status_t (*relay) (ike_sa_t *this, identification_t *requester, + chunk_t connect_id, chunk_t connect_key, + linked_list_t *endpoints, bool response); + + /** + * Send a callback to a peer. + * + * Data is cloned. + * + * @param peer_id ID of the other peer + * @return + * - SUCCESS if response started + * - DESTROY_ME if response failed + */ + status_t (*callback) (ike_sa_t *this, identification_t *peer_id); + + /** + * Respond to a ME_CONNECT request. + * + * Data is cloned. + * + * @param peer_id ID of the other peer + * @param connect_id the connect ID supplied by the initiator + * @return + * - SUCCESS if response started + * - DESTROY_ME if response failed + */ + status_t (*respond) (ike_sa_t *this, identification_t *peer_id, + chunk_t connect_id); +#endif /* ME */ + + /** + * Initiate a new connection. + * + * The configs are owned by the IKE_SA after the call. If the initiate + * is triggered by a packet, traffic selectors of the packet can be added + * to the CHILD_SA. + * + * @param child_cfg child config to create CHILD from + * @param reqid reqid to use for CHILD_SA, 0 assigne uniquely + * @param tsi source of triggering packet + * @param tsr destination of triggering packet. + * @return + * - SUCCESS if initialization started + * - DESTROY_ME if initialization failed + */ + status_t (*initiate) (ike_sa_t *this, child_cfg_t *child_cfg, + u_int32_t reqid, traffic_selector_t *tsi, + traffic_selector_t *tsr); + + /** + * Initiates the deletion of an IKE_SA. + * + * Sends a delete message to the remote peer and waits for + * its response. If the response comes in, or a timeout occurs, + * the IKE SA gets deleted. + * + * @return + * - SUCCESS if deletion is initialized + * - DESTROY_ME, if the IKE_SA is not in + * an established state and can not be + * deleted (but destroyed). + */ + status_t (*delete) (ike_sa_t *this); + + /** + * Update IKE_SAs after network interfaces have changed. + * + * Whenever the network interface configuration changes, the kernel + * interface calls roam() on each IKE_SA. The IKE_SA then checks if + * the new network config requires changes, and handles appropriate. + * If MOBIKE is supported, addresses are updated; If not, the tunnel is + * restarted. + * + * @param address TRUE if address list changed, FALSE otherwise + * @return SUCCESS, FAILED, DESTROY_ME + */ + status_t (*roam)(ike_sa_t *this, bool address); + + /** + * Processes a incoming IKEv2-Message. + * + * Message processing may fail. If a critical failure occurs, + * process_message() return DESTROY_ME. Then the caller must + * destroy the IKE_SA immediatly, as it is unusable. + * + * @param message message to process + * @return + * - SUCCESS + * - FAILED + * - DESTROY_ME if this IKE_SA MUST be deleted + */ + status_t (*process_message) (ike_sa_t *this, message_t *message); + + /** + * Generate a IKE message to send it to the peer. + * + * This method generates all payloads in the message and encrypts/signs + * the packet. + * + * @param message message to generate + * @param packet generated output packet + * @return + * - SUCCESS + * - FAILED + * - DESTROY_ME if this IKE_SA MUST be deleted + */ + status_t (*generate_message) (ike_sa_t *this, message_t *message, + packet_t **packet); + + /** + * Retransmits a request. + * + * @param message_id ID of the request to retransmit + * @return + * - SUCCESS + * - NOT_FOUND if request doesn't have to be retransmited + */ + status_t (*retransmit) (ike_sa_t *this, u_int32_t message_id); + + /** + * Sends a DPD request to the peer. + * + * To check if a peer is still alive, periodic + * empty INFORMATIONAL messages are sent if no + * other traffic was received. + * + * @return + * - SUCCESS + * - DESTROY_ME, if peer did not respond + */ + status_t (*send_dpd) (ike_sa_t *this); + + /** + * Sends a keep alive packet. + * + * To refresh NAT tables in a NAT router + * between the peers, periodic empty + * UDP packets are sent if no other traffic + * was sent. + */ + void (*send_keepalive) (ike_sa_t *this); + + /** + * Get the keying material of this IKE_SA. + * + * @return per IKE_SA keymat instance + */ + keymat_t* (*get_keymat)(ike_sa_t *this); + + /** + * Associates a child SA to this IKE SA + * + * @param child_sa child_sa to add + */ + void (*add_child_sa) (ike_sa_t *this, child_sa_t *child_sa); + + /** + * Get a CHILD_SA identified by protocol and SPI. + * + * @param protocol protocol of the SA + * @param spi SPI of the CHILD_SA + * @param inbound TRUE if SPI is inbound, FALSE if outbound + * @return child_sa, or NULL if none found + */ + child_sa_t* (*get_child_sa) (ike_sa_t *this, protocol_id_t protocol, + u_int32_t spi, bool inbound); + + /** + * Create an iterator over all CHILD_SAs. + * + * @return iterator + */ + iterator_t* (*create_child_sa_iterator) (ike_sa_t *this); + + /** + * Rekey the CHILD SA with the specified reqid. + * + * Looks for a CHILD SA owned by this IKE_SA, and start the rekeing. + * + * @param protocol protocol of the SA + * @param spi inbound SPI of the CHILD_SA + * @return + * - NOT_FOUND, if IKE_SA has no such CHILD_SA + * - SUCCESS, if rekeying initiated + */ + status_t (*rekey_child_sa) (ike_sa_t *this, protocol_id_t protocol, u_int32_t spi); + + /** + * Close the CHILD SA with the specified protocol/SPI. + * + * Looks for a CHILD SA owned by this IKE_SA, deletes it and + * notify's the remote peer about the delete. The associated + * states and policies in the kernel get deleted, if they exist. + * + * @param protocol protocol of the SA + * @param spi inbound SPI of the CHILD_SA + * @return + * - NOT_FOUND, if IKE_SA has no such CHILD_SA + * - SUCCESS, if delete message sent + */ + status_t (*delete_child_sa) (ike_sa_t *this, protocol_id_t protocol, u_int32_t spi); + + /** + * Destroy a CHILD SA with the specified protocol/SPI. + * + * Looks for a CHILD SA owned by this IKE_SA and destroys it. + * + * @param protocol protocol of the SA + * @param spi inbound SPI of the CHILD_SA + * @return + * - NOT_FOUND, if IKE_SA has no such CHILD_SA + * - SUCCESS + */ + status_t (*destroy_child_sa) (ike_sa_t *this, protocol_id_t protocol, u_int32_t spi); + + /** + * Rekey the IKE_SA. + * + * Sets up a new IKE_SA, moves all CHILDs to it and deletes this IKE_SA. + * + * @return - SUCCESS, if IKE_SA rekeying initiated + */ + status_t (*rekey) (ike_sa_t *this); + + /** + * Reauthenticate the IKE_SA. + * + * Create a completely new IKE_SA with authentication, recreates all children + * within the IKE_SA, closes this IKE_SA. + * + * @return DESTROY_ME to destroy the IKE_SA + */ + status_t (*reauth) (ike_sa_t *this); + + /** + * Restablish the IKE_SA. + * + * Reestablish an IKE_SA after it has been closed. + * + * @return DESTROY_ME to destroy the IKE_SA + */ + status_t (*reestablish) (ike_sa_t *this); + + /** + * Set the lifetime limit received from a AUTH_LIFETIME notify. + * + * @param lifetime lifetime in seconds + */ + void (*set_auth_lifetime)(ike_sa_t *this, u_int32_t lifetime); + + /** + * Set the virtual IP to use for this IKE_SA and its children. + * + * The virtual IP is assigned per IKE_SA, not per CHILD_SA. It has the same + * lifetime as the IKE_SA. + * + * @param local TRUE to set local address, FALSE for remote + * @param ip IP to set as virtual IP + */ + void (*set_virtual_ip) (ike_sa_t *this, bool local, host_t *ip); + + /** + * Get the virtual IP configured. + * + * @param local TRUE to get local virtual IP, FALSE for remote + * @return host_t *virtual IP + */ + host_t* (*get_virtual_ip) (ike_sa_t *this, bool local); + + /** + * Register a configuration attribute to the IKE_SA. + * + * If an IRAS sends a configuration attribute it is installed and + * registered at the IKE_SA. Attributes are inherit()ed and get released + * when the IKE_SA is closed. + * + * @param handler handler installed the attribute, use for release() + * @param type configuration attribute type + * @param data associated attribute data + */ + void (*add_configuration_attribute)(ike_sa_t *this, + attribute_handler_t *handler, + configuration_attribute_type_t type, chunk_t data); + + /** + * Set local and remote host addresses to be used for IKE. + * + * These addresses are communicated via the KMADDRESS field of a MIGRATE + * message sent via the NETLINK or PF _KEY kernel socket interface. + * + * @param local local kmaddress + * @param remote remote kmaddress + */ + void (*set_kmaddress) (ike_sa_t *this, host_t *local, host_t *remote); + + /** + * Inherit all attributes of other to this after rekeying. + * + * When rekeying is completed, all CHILD_SAs, the virtual IP and all + * outstanding tasks are moved from other to this. + * As this call may initiate inherited tasks, a status is returned. + * + * @param other other task to inherit from + * @return DESTROY_ME if initiation of inherited task failed + */ + status_t (*inherit) (ike_sa_t *this, ike_sa_t *other); + + /** + * Reset the IKE_SA, useable when initiating fails + */ + void (*reset) (ike_sa_t *this); + + /** + * Destroys a ike_sa_t object. + */ + void (*destroy) (ike_sa_t *this); +}; + +/** + * Creates an ike_sa_t object with a specific ID. + * + * @param ike_sa_id ike_sa_id_t object to associate with new IKE_SA + * @return ike_sa_t object + */ +ike_sa_t *ike_sa_create(ike_sa_id_t *ike_sa_id); + +#endif /** IKE_SA_H_ @}*/ diff --git a/src/libcharon/sa/ike_sa_id.c b/src/libcharon/sa/ike_sa_id.c new file mode 100644 index 000000000..94c5405f2 --- /dev/null +++ b/src/libcharon/sa/ike_sa_id.c @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2005-2006 Martin Willi + * Copyright (C) 2005 Jan Hutter + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "ike_sa_id.h" + +#include <stdio.h> + + +typedef struct private_ike_sa_id_t private_ike_sa_id_t; + +/** + * Private data of an ike_sa_id_t object. + */ +struct private_ike_sa_id_t { + /** + * Public interface of ike_sa_id_t. + */ + ike_sa_id_t public; + + /** + * SPI of Initiator. + */ + u_int64_t initiator_spi; + + /** + * SPI of Responder. + */ + u_int64_t responder_spi; + + /** + * Role for specific IKE_SA. + */ + bool is_initiator_flag; +}; + +/** + * Implementation of ike_sa_id_t.set_responder_spi. + */ +static void set_responder_spi (private_ike_sa_id_t *this, u_int64_t responder_spi) +{ + this->responder_spi = responder_spi; +} + +/** + * Implementation of ike_sa_id_t.set_initiator_spi. + */ +static void set_initiator_spi(private_ike_sa_id_t *this, u_int64_t initiator_spi) +{ + this->initiator_spi = initiator_spi; +} + +/** + * Implementation of ike_sa_id_t.get_initiator_spi. + */ +static u_int64_t get_initiator_spi (private_ike_sa_id_t *this) +{ + return this->initiator_spi; +} + +/** + * Implementation of ike_sa_id_t.get_responder_spi. + */ +static u_int64_t get_responder_spi (private_ike_sa_id_t *this) +{ + return this->responder_spi; +} + +/** + * Implementation of ike_sa_id_t.equals. + */ +static bool equals (private_ike_sa_id_t *this, private_ike_sa_id_t *other) +{ + if (other == NULL) + { + return FALSE; + } + if ((this->is_initiator_flag == other->is_initiator_flag) && + (this->initiator_spi == other->initiator_spi) && + (this->responder_spi == other->responder_spi)) + { + /* private_ike_sa_id's are equal */ + return TRUE; + } + else + { + /* private_ike_sa_id's are not equal */ + return FALSE; + } +} + +/** + * Implementation of ike_sa_id_t.replace_values. + */ +static void replace_values(private_ike_sa_id_t *this, private_ike_sa_id_t *other) +{ + this->initiator_spi = other->initiator_spi; + this->responder_spi = other->responder_spi; + this->is_initiator_flag = other->is_initiator_flag; +} + +/** + * Implementation of ike_sa_id_t.is_initiator. + */ +static bool is_initiator(private_ike_sa_id_t *this) +{ + return this->is_initiator_flag; +} + +/** + * Implementation of ike_sa_id_t.switch_initiator. + */ +static bool switch_initiator(private_ike_sa_id_t *this) +{ + if (this->is_initiator_flag) + { + this->is_initiator_flag = FALSE; + } + else + { + this->is_initiator_flag = TRUE; + } + return this->is_initiator_flag; +} + +/** + * Implementation of ike_sa_id_t.clone. + */ +static ike_sa_id_t* clone_(private_ike_sa_id_t *this) +{ + return ike_sa_id_create(this->initiator_spi, this->responder_spi, this->is_initiator_flag); +} + +/** + * Implementation of ike_sa_id_t.destroy. + */ +static void destroy(private_ike_sa_id_t *this) +{ + free(this); +} + +/* + * Described in header. + */ +ike_sa_id_t * ike_sa_id_create(u_int64_t initiator_spi, u_int64_t responder_spi, bool is_initiator_flag) +{ + private_ike_sa_id_t *this = malloc_thing(private_ike_sa_id_t); + + /* public functions */ + this->public.set_responder_spi = (void(*)(ike_sa_id_t*,u_int64_t)) set_responder_spi; + this->public.set_initiator_spi = (void(*)(ike_sa_id_t*,u_int64_t)) set_initiator_spi; + this->public.get_responder_spi = (u_int64_t(*)(ike_sa_id_t*)) get_responder_spi; + this->public.get_initiator_spi = (u_int64_t(*)(ike_sa_id_t*)) get_initiator_spi; + this->public.equals = (bool(*)(ike_sa_id_t*,ike_sa_id_t*)) equals; + this->public.replace_values = (void(*)(ike_sa_id_t*,ike_sa_id_t*)) replace_values; + this->public.is_initiator = (bool(*)(ike_sa_id_t*)) is_initiator; + this->public.switch_initiator = (bool(*)(ike_sa_id_t*)) switch_initiator; + this->public.clone = (ike_sa_id_t*(*)(ike_sa_id_t*)) clone_; + this->public.destroy = (void(*)(ike_sa_id_t*))destroy; + + /* private data */ + this->initiator_spi = initiator_spi; + this->responder_spi = responder_spi; + this->is_initiator_flag = is_initiator_flag; + + return &this->public; +} diff --git a/src/libcharon/sa/ike_sa_id.h b/src/libcharon/sa/ike_sa_id.h new file mode 100644 index 000000000..a833aa9d6 --- /dev/null +++ b/src/libcharon/sa/ike_sa_id.h @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2005-2006 Martin Willi + * Copyright (C) 2005 Jan Hutter + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup ike_sa_id ike_sa_id + * @{ @ingroup sa + */ + +#ifndef IKE_SA_ID_H_ +#define IKE_SA_ID_H_ + +typedef struct ike_sa_id_t ike_sa_id_t; + +#include <library.h> + +/** + * An object of type ike_sa_id_t is used to identify an IKE_SA. + * + * An IKE_SA is identified by its initiator and responder spi's. + * Additionaly it contains the role of the actual running IKEv2-Daemon + * for the specific IKE_SA (original initiator or responder). + */ +struct ike_sa_id_t { + + /** + * Set the SPI of the responder. + * + * This function is called when a request or reply of a IKE_SA_INIT is received. + * + * @param responder_spi SPI of responder to set + */ + void (*set_responder_spi) (ike_sa_id_t *this, u_int64_t responder_spi); + + /** + * Set the SPI of the initiator. + * + * @param initiator_spi SPI to set + */ + void (*set_initiator_spi) (ike_sa_id_t *this, u_int64_t initiator_spi); + + /** + * Get the initiator SPI. + * + * @return SPI of the initiator + */ + u_int64_t (*get_initiator_spi) (ike_sa_id_t *this); + + /** + * Get the responder SPI. + * + * @return SPI of the responder + */ + u_int64_t (*get_responder_spi) (ike_sa_id_t *this); + + /** + * Check if two ike_sa_id_t objects are equal. + * + * Two ike_sa_id_t objects are equal if both SPI values and the role matches. + * + * @param other ike_sa_id_t object to check if equal + * @return TRUE if given ike_sa_id_t are equal, FALSE otherwise + */ + bool (*equals) (ike_sa_id_t *this, ike_sa_id_t *other); + + /** + * Replace all values of a given ike_sa_id_t object with values. + * from another ike_sa_id_t object. + * + * After calling this function, both objects are equal. + * + * @param other ike_sa_id_t object from which values will be taken + */ + void (*replace_values) (ike_sa_id_t *this, ike_sa_id_t *other); + + /** + * Get the initiator flag. + * + * @return TRUE if we are the original initator + */ + bool (*is_initiator) (ike_sa_id_t *this); + + /** + * Switche the original initiator flag. + * + * @return TRUE if we are the original initator after switch, FALSE otherwise + */ + bool (*switch_initiator) (ike_sa_id_t *this); + + /** + * Clones a given ike_sa_id_t object. + * + * @return cloned ike_sa_id_t object + */ + ike_sa_id_t *(*clone) (ike_sa_id_t *this); + + /** + * Destroys an ike_sa_id_t object. + */ + void (*destroy) (ike_sa_id_t *this); +}; + +/** + * Creates an ike_sa_id_t object with specific SPI's and defined role. + * + * @param initiator_spi initiators SPI + * @param responder_spi responders SPI + * @param is_initiaor TRUE if we are the original initiator + * @return ike_sa_id_t object + */ +ike_sa_id_t * ike_sa_id_create(u_int64_t initiator_spi, u_int64_t responder_spi, + bool is_initiaor); + +#endif /** IKE_SA_ID_H_ @}*/ diff --git a/src/libcharon/sa/ike_sa_manager.c b/src/libcharon/sa/ike_sa_manager.c new file mode 100644 index 000000000..3ef0f3bb0 --- /dev/null +++ b/src/libcharon/sa/ike_sa_manager.c @@ -0,0 +1,1741 @@ +/* + * Copyright (C) 2008 Tobias Brunner + * Copyright (C) 2005-2008 Martin Willi + * Copyright (C) 2005 Jan Hutter + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include <string.h> + +#include "ike_sa_manager.h" + +#include <daemon.h> +#include <sa/ike_sa_id.h> +#include <bus/bus.h> +#include <threading/condvar.h> +#include <threading/mutex.h> +#include <threading/rwlock.h> +#include <utils/linked_list.h> +#include <crypto/hashers/hasher.h> + +/* the default size of the hash table (MUST be a power of 2) */ +#define DEFAULT_HASHTABLE_SIZE 1 + +/* the maximum size of the hash table (MUST be a power of 2) */ +#define MAX_HASHTABLE_SIZE (1 << 30) + +/* the default number of segments (MUST be a power of 2) */ +#define DEFAULT_SEGMENT_COUNT 1 + +typedef struct entry_t entry_t; + +/** + * An entry in the linked list, contains IKE_SA, locking and lookup data. + */ +struct entry_t { + + /** + * Number of threads waiting for this ike_sa_t object. + */ + int waiting_threads; + + /** + * Condvar where threads can wait until ike_sa_t object is free for use again. + */ + condvar_t *condvar; + + /** + * Is this ike_sa currently checked out? + */ + bool checked_out; + + /** + * Does this SA drives out new threads? + */ + bool driveout_new_threads; + + /** + * Does this SA drives out waiting threads? + */ + bool driveout_waiting_threads; + + /** + * Identification of an IKE_SA (SPIs). + */ + ike_sa_id_t *ike_sa_id; + + /** + * The contained ike_sa_t object. + */ + ike_sa_t *ike_sa; + + /** + * hash of the IKE_SA_INIT message, used to detect retransmissions + */ + chunk_t init_hash; + + /** + * remote host address, required for DoS detection + */ + host_t *other; + + /** + * As responder: Is this SA half-open? + */ + bool half_open; + + /** + * own identity, required for duplicate checking + */ + identification_t *my_id; + + /** + * remote identity, required for duplicate checking + */ + identification_t *other_id; + + /** + * message ID currently processing, if any + */ + u_int32_t message_id; +}; + +/** + * Implementation of entry_t.destroy. + */ +static status_t entry_destroy(entry_t *this) +{ + /* also destroy IKE SA */ + this->ike_sa->destroy(this->ike_sa); + this->ike_sa_id->destroy(this->ike_sa_id); + chunk_free(&this->init_hash); + DESTROY_IF(this->other); + DESTROY_IF(this->my_id); + DESTROY_IF(this->other_id); + this->condvar->destroy(this->condvar); + free(this); + return SUCCESS; +} + +/** + * Creates a new entry for the ike_sa_t list. + */ +static entry_t *entry_create() +{ + entry_t *this = malloc_thing(entry_t); + + this->waiting_threads = 0; + this->condvar = condvar_create(CONDVAR_TYPE_DEFAULT); + + /* we set checkout flag when we really give it out */ + this->checked_out = FALSE; + this->driveout_new_threads = FALSE; + this->driveout_waiting_threads = FALSE; + this->message_id = -1; + this->init_hash = chunk_empty; + this->other = NULL; + this->half_open = FALSE; + this->my_id = NULL; + this->other_id = NULL; + this->ike_sa_id = NULL; + this->ike_sa = NULL; + + return this; +} + +/** + * Function that matches entry_t objects by initiator SPI and the hash of the + * IKE_SA_INIT message. + */ +static bool entry_match_by_hash(entry_t *entry, ike_sa_id_t *id, chunk_t *hash) +{ + return id->get_responder_spi(id) == 0 && + id->is_initiator(id) == entry->ike_sa_id->is_initiator(entry->ike_sa_id) && + id->get_initiator_spi(id) == entry->ike_sa_id->get_initiator_spi(entry->ike_sa_id) && + chunk_equals(*hash, entry->init_hash); +} + +/** + * Function that matches entry_t objects by ike_sa_id_t. + */ +static bool entry_match_by_id(entry_t *entry, ike_sa_id_t *id) +{ + if (id->equals(id, entry->ike_sa_id)) + { + return TRUE; + } + if ((id->get_responder_spi(id) == 0 || + entry->ike_sa_id->get_responder_spi(entry->ike_sa_id) == 0) && + id->is_initiator(id) == entry->ike_sa_id->is_initiator(entry->ike_sa_id) && + id->get_initiator_spi(id) == entry->ike_sa_id->get_initiator_spi(entry->ike_sa_id)) + { + /* this is TRUE for IKE_SAs that we initiated but have not yet received a response */ + return TRUE; + } + return FALSE; +} + +/** + * Function that matches entry_t objects by ike_sa_t pointers. + */ +static bool entry_match_by_sa(entry_t *entry, ike_sa_t *ike_sa) +{ + return entry->ike_sa == ike_sa; +} + +/** + * Hash function for ike_sa_id_t objects. + */ +static u_int ike_sa_id_hash(ike_sa_id_t *ike_sa_id) +{ + /* we always use initiator spi as key */ + return ike_sa_id->get_initiator_spi(ike_sa_id); +} + +typedef struct half_open_t half_open_t; + +/** + * Struct to manage half-open IKE_SAs per peer. + */ +struct half_open_t { + /** chunk of remote host address */ + chunk_t other; + + /** the number of half-open IKE_SAs with that host */ + u_int count; +}; + +/** + * Destroys a half_open_t object. + */ +static void half_open_destroy(half_open_t *this) +{ + chunk_free(&this->other); + free(this); +} + +/** + * Function that matches half_open_t objects by the given IP address chunk. + */ +static bool half_open_match(half_open_t *half_open, chunk_t *addr) +{ + return chunk_equals(*addr, half_open->other); +} + +typedef struct connected_peers_t connected_peers_t; + +struct connected_peers_t { + /** own identity */ + identification_t *my_id; + + /** remote identity */ + identification_t *other_id; + + /** list of ike_sa_id_t objects of IKE_SAs between the two identities */ + linked_list_t *sas; +}; + +static void connected_peers_destroy(connected_peers_t *this) +{ + this->my_id->destroy(this->my_id); + this->other_id->destroy(this->other_id); + this->sas->destroy(this->sas); + free(this); +} + +/** + * Function that matches connected_peers_t objects by the given ids. + */ +static bool connected_peers_match(connected_peers_t *connected_peers, + identification_t *my_id, identification_t *other_id) +{ + return my_id->equals(my_id, connected_peers->my_id) && + other_id->equals(other_id, connected_peers->other_id); +} + +typedef struct segment_t segment_t; + +/** + * Struct to manage segments of the hash table. + */ +struct segment_t { + /** mutex to access a segment exclusively */ + mutex_t *mutex; + + /** the number of entries in this segment */ + u_int count; +}; + +typedef struct shareable_segment_t shareable_segment_t; + +/** + * Struct to manage segments of the "half-open" and "connected peers" hash tables. + */ +struct shareable_segment_t { + /** rwlock to access a segment non-/exclusively */ + rwlock_t *lock; + + /** the number of entries in this segment - in case of the "half-open table" + * it's the sum of all half_open_t.count in a segment. */ + u_int count; +}; + +typedef struct private_ike_sa_manager_t private_ike_sa_manager_t; + +/** + * Additional private members of ike_sa_manager_t. + */ +struct private_ike_sa_manager_t { + /** + * Public interface of ike_sa_manager_t. + */ + ike_sa_manager_t public; + + /** + * Hash table with entries for the ike_sa_t objects. + */ + linked_list_t **ike_sa_table; + + /** + * The size of the hash table. + */ + u_int table_size; + + /** + * Mask to map the hashes to table rows. + */ + u_int table_mask; + + /** + * Segments of the hash table. + */ + segment_t *segments; + + /** + * The number of segments. + */ + u_int segment_count; + + /** + * Mask to map a table row to a segment. + */ + u_int segment_mask; + + /** + * Hash table with half_open_t objects. + */ + linked_list_t **half_open_table; + + /** + * Segments of the "half-open" hash table. + */ + shareable_segment_t *half_open_segments; + + /** + * Hash table with connected_peers_t objects. + */ + linked_list_t **connected_peers_table; + + /** + * Segments of the "connected peers" hash table. + */ + shareable_segment_t *connected_peers_segments; + + /** + * RNG to get random SPIs for our side + */ + rng_t *rng; + + /** + * SHA1 hasher for IKE_SA_INIT retransmit detection + */ + hasher_t *hasher; + + /** + * reuse existing IKE_SAs in checkout_by_config + */ + bool reuse_ikesa; +}; + +/** + * Acquire a lock to access the segment of the table row with the given index. + * It also works with the segment index directly. + */ +static void lock_single_segment(private_ike_sa_manager_t *this, u_int index) +{ + mutex_t *lock = this->segments[index & this->segment_mask].mutex; + + lock->lock(lock); +} + +/** + * Release the lock required to access the segment of the table row with the given index. + * It also works with the segment index directly. + */ +static void unlock_single_segment(private_ike_sa_manager_t *this, u_int index) +{ + mutex_t *lock = this->segments[index & this->segment_mask].mutex; + + lock->unlock(lock); +} + +/** + * Lock all segments + */ +static void lock_all_segments(private_ike_sa_manager_t *this) +{ + u_int i; + + for (i = 0; i < this->segment_count; ++i) + { + this->segments[i].mutex->lock(this->segments[i].mutex); + } +} + +/** + * Unlock all segments + */ +static void unlock_all_segments(private_ike_sa_manager_t *this) +{ + u_int i; + + for (i = 0; i < this->segment_count; ++i) + { + this->segments[i].mutex->unlock(this->segments[i].mutex); + } +} + +typedef struct private_enumerator_t private_enumerator_t; + +/** + * hash table enumerator implementation + */ +struct private_enumerator_t { + + /** + * implements enumerator interface + */ + enumerator_t enumerator; + + /** + * associated ike_sa_manager_t + */ + private_ike_sa_manager_t *manager; + + /** + * current segment index + */ + u_int segment; + + /** + * currently enumerating entry + */ + entry_t *entry; + + /** + * current table row index + */ + u_int row; + + /** + * enumerator for the current table row + */ + enumerator_t *current; +}; + +/** + * Implementation of private_enumerator_t.enumerator.enumerate. + */ +static bool enumerate(private_enumerator_t *this, entry_t **entry, u_int *segment) +{ + if (this->entry) + { + this->entry->condvar->signal(this->entry->condvar); + this->entry = NULL; + } + while (this->segment < this->manager->segment_count) + { + while (this->row < this->manager->table_size) + { + if (this->current) + { + entry_t *item; + + if (this->current->enumerate(this->current, &item)) + { + *entry = this->entry = item; + *segment = this->segment; + return TRUE; + } + this->current->destroy(this->current); + this->current = NULL; + unlock_single_segment(this->manager, this->segment); + } + else + { + linked_list_t *list; + + lock_single_segment(this->manager, this->segment); + if ((list = this->manager->ike_sa_table[this->row]) != NULL && + list->get_count(list)) + { + this->current = list->create_enumerator(list); + continue; + } + unlock_single_segment(this->manager, this->segment); + } + this->row += this->manager->segment_count; + } + this->segment++; + this->row = this->segment; + } + return FALSE; +} + +/** + * Implementation of private_enumerator_t.enumerator.destroy. + */ +static void enumerator_destroy(private_enumerator_t *this) +{ + if (this->entry) + { + this->entry->condvar->signal(this->entry->condvar); + } + if (this->current) + { + this->current->destroy(this->current); + unlock_single_segment(this->manager, this->segment); + } + free(this); +} + +/** + * Creates an enumerator to enumerate the entries in the hash table. + */ +static enumerator_t* create_table_enumerator(private_ike_sa_manager_t *this) +{ + private_enumerator_t *enumerator = malloc_thing(private_enumerator_t); + + enumerator->enumerator.enumerate = (void*)enumerate; + enumerator->enumerator.destroy = (void*)enumerator_destroy; + enumerator->manager = this; + enumerator->segment = 0; + enumerator->entry = NULL; + enumerator->row = 0; + enumerator->current = NULL; + + return &enumerator->enumerator; +} + +/** + * Put an entry into the hash table. + * Note: The caller has to unlock the returned segment. + */ +static u_int put_entry(private_ike_sa_manager_t *this, entry_t *entry) +{ + linked_list_t *list; + u_int row = ike_sa_id_hash(entry->ike_sa_id) & this->table_mask; + u_int segment = row & this->segment_mask; + + lock_single_segment(this, segment); + if ((list = this->ike_sa_table[row]) == NULL) + { + list = this->ike_sa_table[row] = linked_list_create(); + } + list->insert_last(list, entry); + this->segments[segment].count++; + return segment; +} + +/** + * Remove an entry from the hash table. + * Note: The caller MUST have a lock on the segment of this entry. + */ +static void remove_entry(private_ike_sa_manager_t *this, entry_t *entry) +{ + linked_list_t *list; + u_int row = ike_sa_id_hash(entry->ike_sa_id) & this->table_mask; + u_int segment = row & this->segment_mask; + + if ((list = this->ike_sa_table[row]) != NULL) + { + entry_t *current; + + enumerator_t *enumerator = list->create_enumerator(list); + while (enumerator->enumerate(enumerator, ¤t)) + { + if (current == entry) + { + list->remove_at(list, enumerator); + this->segments[segment].count--; + break; + } + } + enumerator->destroy(enumerator); + } +} + +/** + * Remove the entry at the current enumerator position. + */ +static void remove_entry_at(private_enumerator_t *this) +{ + this->entry = NULL; + if (this->current) + { + linked_list_t *list = this->manager->ike_sa_table[this->row]; + list->remove_at(list, this->current); + this->manager->segments[this->segment].count--; + } +} + +/** + * Find an entry using the provided match function to compare the entries for + * equality. + */ +static status_t get_entry_by_match_function(private_ike_sa_manager_t *this, + ike_sa_id_t *ike_sa_id, entry_t **entry, u_int *segment, + linked_list_match_t match, void *p1, void *p2) +{ + entry_t *current; + linked_list_t *list; + u_int row = ike_sa_id_hash(ike_sa_id) & this->table_mask; + u_int seg = row & this->segment_mask; + + lock_single_segment(this, seg); + if ((list = this->ike_sa_table[row]) != NULL) + { + if (list->find_first(list, match, (void**)¤t, p1, p2) == SUCCESS) + { + *entry = current; + *segment = seg; + /* the locked segment has to be unlocked by the caller */ + return SUCCESS; + } + } + unlock_single_segment(this, seg); + return NOT_FOUND; +} + +/** + * Find an entry by ike_sa_id_t. + * Note: On SUCCESS, the caller has to unlock the segment. + */ +static status_t get_entry_by_id(private_ike_sa_manager_t *this, + ike_sa_id_t *ike_sa_id, entry_t **entry, u_int *segment) +{ + return get_entry_by_match_function(this, ike_sa_id, entry, segment, + (linked_list_match_t)entry_match_by_id, ike_sa_id, NULL); +} + +/** + * Find an entry by initiator SPI and IKE_SA_INIT hash. + * Note: On SUCCESS, the caller has to unlock the segment. + */ +static status_t get_entry_by_hash(private_ike_sa_manager_t *this, + ike_sa_id_t *ike_sa_id, chunk_t hash, entry_t **entry, u_int *segment) +{ + return get_entry_by_match_function(this, ike_sa_id, entry, segment, + (linked_list_match_t)entry_match_by_hash, ike_sa_id, &hash); +} + +/** + * Find an entry by IKE_SA pointer. + * Note: On SUCCESS, the caller has to unlock the segment. + */ +static status_t get_entry_by_sa(private_ike_sa_manager_t *this, + ike_sa_id_t *ike_sa_id, ike_sa_t *ike_sa, entry_t **entry, u_int *segment) +{ + return get_entry_by_match_function(this, ike_sa_id, entry, segment, + (linked_list_match_t)entry_match_by_sa, ike_sa, NULL); +} + +/** + * Wait until no other thread is using an IKE_SA, return FALSE if entry not + * acquirable. + */ +static bool wait_for_entry(private_ike_sa_manager_t *this, entry_t *entry, + u_int segment) +{ + if (entry->driveout_new_threads) + { + /* we are not allowed to get this */ + return FALSE; + } + while (entry->checked_out && !entry->driveout_waiting_threads) + { + /* so wait until we can get it for us. + * we register us as waiting. */ + entry->waiting_threads++; + entry->condvar->wait(entry->condvar, this->segments[segment].mutex); + entry->waiting_threads--; + } + /* hm, a deletion request forbids us to get this SA, get next one */ + if (entry->driveout_waiting_threads) + { + /* we must signal here, others may be waiting on it, too */ + entry->condvar->signal(entry->condvar); + return FALSE; + } + return TRUE; +} + +/** + * Put a half-open SA into the hash table. + */ +static void put_half_open(private_ike_sa_manager_t *this, entry_t *entry) +{ + half_open_t *half_open = NULL; + linked_list_t *list; + chunk_t addr = entry->other->get_address(entry->other); + u_int row = chunk_hash(addr) & this->table_mask; + u_int segment = row & this->segment_mask; + + rwlock_t *lock = this->half_open_segments[segment].lock; + lock->write_lock(lock); + if ((list = this->half_open_table[row]) == NULL) + { + list = this->half_open_table[row] = linked_list_create(); + } + else + { + half_open_t *current; + if (list->find_first(list, (linked_list_match_t)half_open_match, + (void**)¤t, &addr) == SUCCESS) + { + half_open = current; + half_open->count++; + this->half_open_segments[segment].count++; + } + } + + if (!half_open) + { + half_open = malloc_thing(half_open_t); + half_open->other = chunk_clone(addr); + half_open->count = 1; + list->insert_last(list, half_open); + this->half_open_segments[segment].count++; + } + lock->unlock(lock); +} + +/** + * Remove a half-open SA from the hash table. + */ +static void remove_half_open(private_ike_sa_manager_t *this, entry_t *entry) +{ + linked_list_t *list; + chunk_t addr = entry->other->get_address(entry->other); + u_int row = chunk_hash(addr) & this->table_mask; + u_int segment = row & this->segment_mask; + + rwlock_t *lock = this->half_open_segments[segment].lock; + lock->write_lock(lock); + if ((list = this->half_open_table[row]) != NULL) + { + half_open_t *current; + enumerator_t *enumerator = list->create_enumerator(list); + while (enumerator->enumerate(enumerator, ¤t)) + { + if (half_open_match(current, &addr)) + { + if (--current->count == 0) + { + list->remove_at(list, enumerator); + half_open_destroy(current); + } + this->half_open_segments[segment].count--; + break; + } + } + enumerator->destroy(enumerator); + } + lock->unlock(lock); +} + +/** + * Put an SA between two peers into the hash table. + */ +static void put_connected_peers(private_ike_sa_manager_t *this, entry_t *entry) +{ + linked_list_t *list; + connected_peers_t *connected_peers = NULL; + chunk_t my_id = entry->my_id->get_encoding(entry->my_id), + other_id = entry->other_id->get_encoding(entry->other_id); + u_int row = chunk_hash_inc(other_id, chunk_hash(my_id)) & this->table_mask; + u_int segment = row & this->segment_mask; + + rwlock_t *lock = this->connected_peers_segments[segment].lock; + lock->write_lock(lock); + if ((list = this->connected_peers_table[row]) == NULL) + { + list = this->connected_peers_table[row] = linked_list_create(); + } + else + { + connected_peers_t *current; + if (list->find_first(list, (linked_list_match_t)connected_peers_match, + (void**)¤t, entry->my_id, entry->other_id) == SUCCESS) + { + connected_peers = current; + if (connected_peers->sas->find_first(connected_peers->sas, + (linked_list_match_t)entry->ike_sa_id->equals, + NULL, entry->ike_sa_id) == SUCCESS) + { + lock->unlock(lock); + return; + } + } + } + + if (!connected_peers) + { + connected_peers = malloc_thing(connected_peers_t); + connected_peers->my_id = entry->my_id->clone(entry->my_id); + connected_peers->other_id = entry->other_id->clone(entry->other_id); + connected_peers->sas = linked_list_create(); + list->insert_last(list, connected_peers); + } + connected_peers->sas->insert_last(connected_peers->sas, + entry->ike_sa_id->clone(entry->ike_sa_id)); + this->connected_peers_segments[segment].count++; + lock->unlock(lock); +} + +/** + * Remove an SA between two peers from the hash table. + */ +static void remove_connected_peers(private_ike_sa_manager_t *this, entry_t *entry) +{ + linked_list_t *list; + chunk_t my_id = entry->my_id->get_encoding(entry->my_id), + other_id = entry->other_id->get_encoding(entry->other_id); + u_int row = chunk_hash_inc(other_id, chunk_hash(my_id)) & this->table_mask; + u_int segment = row & this->segment_mask; + + rwlock_t *lock = this->connected_peers_segments[segment].lock; + lock->write_lock(lock); + if ((list = this->connected_peers_table[row]) != NULL) + { + connected_peers_t *current; + enumerator_t *enumerator = list->create_enumerator(list); + while (enumerator->enumerate(enumerator, ¤t)) + { + if (connected_peers_match(current, entry->my_id, entry->other_id)) + { + ike_sa_id_t *ike_sa_id; + enumerator_t *inner = current->sas->create_enumerator(current->sas); + while (inner->enumerate(inner, &ike_sa_id)) + { + if (ike_sa_id->equals(ike_sa_id, entry->ike_sa_id)) + { + current->sas->remove_at(current->sas, inner); + ike_sa_id->destroy(ike_sa_id); + this->connected_peers_segments[segment].count--; + break; + } + } + inner->destroy(inner); + if (current->sas->get_count(current->sas) == 0) + { + list->remove_at(list, enumerator); + connected_peers_destroy(current); + } + break; + } + } + enumerator->destroy(enumerator); + } + lock->unlock(lock); +} + +/** + * Implementation of private_ike_sa_manager_t.get_next_spi. + */ +static u_int64_t get_next_spi(private_ike_sa_manager_t *this) +{ + u_int64_t spi; + + this->rng->get_bytes(this->rng, sizeof(spi), (u_int8_t*)&spi); + return spi; +} + +/** + * Implementation of of ike_sa_manager.checkout. + */ +static ike_sa_t* checkout(private_ike_sa_manager_t *this, ike_sa_id_t *ike_sa_id) +{ + ike_sa_t *ike_sa = NULL; + entry_t *entry; + u_int segment; + + DBG2(DBG_MGR, "checkout IKE_SA"); + + if (get_entry_by_id(this, ike_sa_id, &entry, &segment) == SUCCESS) + { + if (wait_for_entry(this, entry, segment)) + { + DBG2(DBG_MGR, "IKE_SA successfully checked out"); + entry->checked_out = TRUE; + ike_sa = entry->ike_sa; + } + unlock_single_segment(this, segment); + } + charon->bus->set_sa(charon->bus, ike_sa); + return ike_sa; +} + +/** + * Implementation of of ike_sa_manager.checkout_new. + */ +static ike_sa_t *checkout_new(private_ike_sa_manager_t* this, bool initiator) +{ + ike_sa_id_t *ike_sa_id; + ike_sa_t *ike_sa; + entry_t *entry; + u_int segment; + + if (initiator) + { + ike_sa_id = ike_sa_id_create(get_next_spi(this), 0, TRUE); + } + else + { + ike_sa_id = ike_sa_id_create(0, get_next_spi(this), FALSE); + } + ike_sa = ike_sa_create(ike_sa_id); + + DBG2(DBG_MGR, "created IKE_SA"); + + if (!initiator) + { + ike_sa_id->destroy(ike_sa_id); + return ike_sa; + } + + entry = entry_create(); + entry->ike_sa_id = ike_sa_id; + entry->ike_sa = ike_sa; + segment = put_entry(this, entry); + entry->checked_out = TRUE; + unlock_single_segment(this, segment); + return entry->ike_sa; +} + +/** + * Implementation of of ike_sa_manager.checkout_by_message. + */ +static ike_sa_t* checkout_by_message(private_ike_sa_manager_t* this, + message_t *message) +{ + u_int segment; + entry_t *entry; + ike_sa_t *ike_sa = NULL; + ike_sa_id_t *id = message->get_ike_sa_id(message); + + id = id->clone(id); + id->switch_initiator(id); + + DBG2(DBG_MGR, "checkout IKE_SA by message"); + + if (message->get_request(message) && + message->get_exchange_type(message) == IKE_SA_INIT) + { + /* IKE_SA_INIT request. Check for an IKE_SA with such a message hash. */ + chunk_t data, hash; + + data = message->get_packet_data(message); + this->hasher->allocate_hash(this->hasher, data, &hash); + chunk_free(&data); + + if (get_entry_by_hash(this, id, hash, &entry, &segment) == SUCCESS) + { + if (entry->message_id == 0) + { + unlock_single_segment(this, segment); + chunk_free(&hash); + id->destroy(id); + DBG1(DBG_MGR, "ignoring IKE_SA_INIT, already processing"); + return NULL; + } + else if (wait_for_entry(this, entry, segment)) + { + DBG2(DBG_MGR, "IKE_SA checked out by hash"); + entry->checked_out = TRUE; + entry->message_id = message->get_message_id(message); + ike_sa = entry->ike_sa; + } + unlock_single_segment(this, segment); + } + + if (ike_sa == NULL) + { + if (id->get_responder_spi(id) == 0 && + message->get_exchange_type(message) == IKE_SA_INIT) + { + /* no IKE_SA found, create a new one */ + id->set_responder_spi(id, get_next_spi(this)); + entry = entry_create(); + entry->ike_sa = ike_sa_create(id); + entry->ike_sa_id = id->clone(id); + + segment = put_entry(this, entry); + entry->checked_out = TRUE; + unlock_single_segment(this, segment); + + entry->message_id = message->get_message_id(message); + entry->init_hash = hash; + ike_sa = entry->ike_sa; + + DBG2(DBG_MGR, "created IKE_SA"); + } + else + { + chunk_free(&hash); + DBG1(DBG_MGR, "ignoring message, no such IKE_SA"); + } + } + else + { + chunk_free(&hash); + } + id->destroy(id); + charon->bus->set_sa(charon->bus, ike_sa); + return ike_sa; + } + + if (get_entry_by_id(this, id, &entry, &segment) == SUCCESS) + { + /* only check out if we are not processing this request */ + if (message->get_request(message) && + message->get_message_id(message) == entry->message_id) + { + DBG1(DBG_MGR, "ignoring request with ID %d, already processing", + entry->message_id); + } + else if (wait_for_entry(this, entry, segment)) + { + ike_sa_id_t *ike_id = entry->ike_sa->get_id(entry->ike_sa); + DBG2(DBG_MGR, "IKE_SA successfully checked out"); + entry->checked_out = TRUE; + entry->message_id = message->get_message_id(message); + if (ike_id->get_responder_spi(ike_id) == 0) + { + ike_id->set_responder_spi(ike_id, id->get_responder_spi(id)); + } + ike_sa = entry->ike_sa; + } + unlock_single_segment(this, segment); + } + id->destroy(id); + charon->bus->set_sa(charon->bus, ike_sa); + return ike_sa; +} + +/** + * Implementation of of ike_sa_manager.checkout_by_config. + */ +static ike_sa_t* checkout_by_config(private_ike_sa_manager_t *this, + peer_cfg_t *peer_cfg) +{ + enumerator_t *enumerator; + entry_t *entry; + ike_sa_t *ike_sa = NULL; + peer_cfg_t *current_peer; + ike_cfg_t *current_ike; + u_int segment; + + if (!this->reuse_ikesa) + { /* IKE_SA reuse disable by config */ + ike_sa = checkout_new(this, TRUE); + charon->bus->set_sa(charon->bus, ike_sa); + return ike_sa; + } + + enumerator = create_table_enumerator(this); + while (enumerator->enumerate(enumerator, &entry, &segment)) + { + if (!wait_for_entry(this, entry, segment)) + { + continue; + } + if (entry->ike_sa->get_state(entry->ike_sa) == IKE_DELETING) + { /* skip IKE_SAs which are not usable */ + continue; + } + + current_peer = entry->ike_sa->get_peer_cfg(entry->ike_sa); + if (current_peer && current_peer->equals(current_peer, peer_cfg)) + { + current_ike = current_peer->get_ike_cfg(current_peer); + if (current_ike->equals(current_ike, peer_cfg->get_ike_cfg(peer_cfg))) + { + DBG2(DBG_MGR, "found an existing IKE_SA with a '%s' config", + current_peer->get_name(current_peer)); + entry->checked_out = TRUE; + ike_sa = entry->ike_sa; + break; + } + } + } + enumerator->destroy(enumerator); + + if (!ike_sa) + { /* no IKE_SA using such a config, hand out a new */ + ike_sa = checkout_new(this, TRUE); + } + charon->bus->set_sa(charon->bus, ike_sa); + return ike_sa; +} + +/** + * Implementation of of ike_sa_manager.checkout_by_id. + */ +static ike_sa_t* checkout_by_id(private_ike_sa_manager_t *this, u_int32_t id, + bool child) +{ + enumerator_t *enumerator; + iterator_t *children; + entry_t *entry; + ike_sa_t *ike_sa = NULL; + child_sa_t *child_sa; + u_int segment; + + enumerator = create_table_enumerator(this); + while (enumerator->enumerate(enumerator, &entry, &segment)) + { + if (wait_for_entry(this, entry, segment)) + { + /* look for a child with such a reqid ... */ + if (child) + { + children = entry->ike_sa->create_child_sa_iterator(entry->ike_sa); + while (children->iterate(children, (void**)&child_sa)) + { + if (child_sa->get_reqid(child_sa) == id) + { + ike_sa = entry->ike_sa; + break; + } + } + children->destroy(children); + } + else /* ... or for a IKE_SA with such a unique id */ + { + if (entry->ike_sa->get_unique_id(entry->ike_sa) == id) + { + ike_sa = entry->ike_sa; + } + } + /* got one, return */ + if (ike_sa) + { + entry->checked_out = TRUE; + break; + } + } + } + enumerator->destroy(enumerator); + + charon->bus->set_sa(charon->bus, ike_sa); + return ike_sa; +} + +/** + * Implementation of of ike_sa_manager.checkout_by_name. + */ +static ike_sa_t* checkout_by_name(private_ike_sa_manager_t *this, char *name, + bool child) +{ + enumerator_t *enumerator; + iterator_t *children; + entry_t *entry; + ike_sa_t *ike_sa = NULL; + child_sa_t *child_sa; + u_int segment; + + enumerator = create_table_enumerator(this); + while (enumerator->enumerate(enumerator, &entry, &segment)) + { + if (wait_for_entry(this, entry, segment)) + { + /* look for a child with such a policy name ... */ + if (child) + { + children = entry->ike_sa->create_child_sa_iterator(entry->ike_sa); + while (children->iterate(children, (void**)&child_sa)) + { + if (streq(child_sa->get_name(child_sa), name)) + { + ike_sa = entry->ike_sa; + break; + } + } + children->destroy(children); + } + else /* ... or for a IKE_SA with such a connection name */ + { + if (streq(entry->ike_sa->get_name(entry->ike_sa), name)) + { + ike_sa = entry->ike_sa; + } + } + /* got one, return */ + if (ike_sa) + { + entry->checked_out = TRUE; + break; + } + } + } + enumerator->destroy(enumerator); + + charon->bus->set_sa(charon->bus, ike_sa); + return ike_sa; +} + +/** + * enumerator filter function + */ +static bool enumerator_filter(private_ike_sa_manager_t *this, + entry_t **in, ike_sa_t **out, u_int *segment) +{ + if (wait_for_entry(this, *in, *segment)) + { + *out = (*in)->ike_sa; + return TRUE; + } + return FALSE; +} + +/** + * Implementation of ike_sa_manager_t.create_enumerator. + */ +static enumerator_t *create_enumerator(private_ike_sa_manager_t* this) +{ + return enumerator_create_filter( + create_table_enumerator(this), + (void*)enumerator_filter, this, NULL); +} + +/** + * Implementation of ike_sa_manager_t.checkin. + */ +static void checkin(private_ike_sa_manager_t *this, ike_sa_t *ike_sa) +{ + /* to check the SA back in, we look for the pointer of the ike_sa + * in all entries. + * The lookup is done by initiator SPI, so even if the SPI has changed (e.g. + * on reception of a IKE_SA_INIT response) the lookup will work but + * updating of the SPI MAY be necessary... + */ + entry_t *entry; + ike_sa_id_t *ike_sa_id; + host_t *other; + identification_t *my_id, *other_id; + u_int segment; + + ike_sa_id = ike_sa->get_id(ike_sa); + my_id = ike_sa->get_my_id(ike_sa); + other_id = ike_sa->get_other_id(ike_sa); + other = ike_sa->get_other_host(ike_sa); + + DBG2(DBG_MGR, "checkin IKE_SA"); + + /* look for the entry */ + if (get_entry_by_sa(this, ike_sa_id, ike_sa, &entry, &segment) == SUCCESS) + { + /* ike_sa_id must be updated */ + entry->ike_sa_id->replace_values(entry->ike_sa_id, ike_sa->get_id(ike_sa)); + /* signal waiting threads */ + entry->checked_out = FALSE; + entry->message_id = -1; + /* check if this SA is half-open */ + if (entry->half_open && ike_sa->get_state(ike_sa) != IKE_CONNECTING) + { + /* not half open anymore */ + entry->half_open = FALSE; + remove_half_open(this, entry); + } + else if (entry->half_open && !other->ip_equals(other, entry->other)) + { + /* the other host's IP has changed, we must update the hash table */ + remove_half_open(this, entry); + DESTROY_IF(entry->other); + entry->other = other->clone(other); + put_half_open(this, entry); + } + else if (!entry->half_open && + !entry->ike_sa_id->is_initiator(entry->ike_sa_id) && + ike_sa->get_state(ike_sa) == IKE_CONNECTING) + { + /* this is a new half-open SA */ + entry->half_open = TRUE; + entry->other = other->clone(other); + put_half_open(this, entry); + } + DBG2(DBG_MGR, "check-in of IKE_SA successful."); + entry->condvar->signal(entry->condvar); + } + else + { + entry = entry_create(); + entry->ike_sa_id = ike_sa_id->clone(ike_sa_id); + entry->ike_sa = ike_sa; + segment = put_entry(this, entry); + } + + /* apply identities for duplicate test (only as responder) */ + if (!entry->ike_sa_id->is_initiator(entry->ike_sa_id) && + ike_sa->get_state(ike_sa) == IKE_ESTABLISHED && + entry->my_id == NULL && entry->other_id == NULL) + { + entry->my_id = my_id->clone(my_id); + entry->other_id = other_id->clone(other_id); + put_connected_peers(this, entry); + } + + unlock_single_segment(this, segment); + + charon->bus->set_sa(charon->bus, NULL); +} + +/** + * Implementation of ike_sa_manager_t.checkin_and_destroy. + */ +static void checkin_and_destroy(private_ike_sa_manager_t *this, ike_sa_t *ike_sa) +{ + /* deletion is a bit complex, we must ensure that no thread is waiting for + * this SA. + * We take this SA from the table, and start signaling while threads + * are in the condvar. + */ + entry_t *entry; + ike_sa_id_t *ike_sa_id; + u_int segment; + + ike_sa_id = ike_sa->get_id(ike_sa); + + DBG2(DBG_MGR, "checkin and destroy IKE_SA"); + + if (get_entry_by_sa(this, ike_sa_id, ike_sa, &entry, &segment) == SUCCESS) + { + /* drive out waiting threads, as we are in hurry */ + entry->driveout_waiting_threads = TRUE; + /* mark it, so no new threads can get this entry */ + entry->driveout_new_threads = TRUE; + /* wait until all workers have done their work */ + while (entry->waiting_threads) + { + /* wake up all */ + entry->condvar->broadcast(entry->condvar); + /* they will wake us again when their work is done */ + entry->condvar->wait(entry->condvar, this->segments[segment].mutex); + } + remove_entry(this, entry); + unlock_single_segment(this, segment); + + if (entry->half_open) + { + remove_half_open(this, entry); + } + if (!entry->ike_sa_id->is_initiator(entry->ike_sa_id) && + entry->my_id && entry->other_id) + { + remove_connected_peers(this, entry); + } + + entry_destroy(entry); + + DBG2(DBG_MGR, "check-in and destroy of IKE_SA successful"); + } + else + { + DBG1(DBG_MGR, "tried to check-in and delete nonexisting IKE_SA"); + ike_sa->destroy(ike_sa); + } + charon->bus->set_sa(charon->bus, NULL); +} + + +/** + * Implementation of ike_sa_manager_t.check_uniqueness. + */ +static bool check_uniqueness(private_ike_sa_manager_t *this, ike_sa_t *ike_sa) +{ + bool cancel = FALSE; + peer_cfg_t *peer_cfg; + unique_policy_t policy; + linked_list_t *list, *duplicate_ids = NULL; + enumerator_t *enumerator; + ike_sa_id_t *duplicate_id = NULL; + identification_t *me, *other; + u_int row, segment; + rwlock_t *lock; + + peer_cfg = ike_sa->get_peer_cfg(ike_sa); + policy = peer_cfg->get_unique_policy(peer_cfg); + if (policy == UNIQUE_NO) + { + return FALSE; + } + + me = ike_sa->get_my_id(ike_sa); + other = ike_sa->get_other_id(ike_sa); + + row = chunk_hash_inc(other->get_encoding(other), + chunk_hash(me->get_encoding(me))) & this->table_mask; + segment = row & this->segment_mask; + + lock = this->connected_peers_segments[segment & this->segment_mask].lock; + lock->read_lock(lock); + if ((list = this->connected_peers_table[row]) != NULL) + { + connected_peers_t *current; + + if (list->find_first(list, (linked_list_match_t)connected_peers_match, + (void**)¤t, me, other) == SUCCESS) + { + /* clone the list, so we can release the lock */ + duplicate_ids = current->sas->clone_offset(current->sas, + offsetof(ike_sa_id_t, clone)); + } + } + lock->unlock(lock); + + if (!duplicate_ids) + { + return FALSE; + } + + enumerator = duplicate_ids->create_enumerator(duplicate_ids); + while (enumerator->enumerate(enumerator, &duplicate_id)) + { + status_t status = SUCCESS; + ike_sa_t *duplicate; + + duplicate = checkout(this, duplicate_id); + if (!duplicate) + { + continue; + } + peer_cfg = duplicate->get_peer_cfg(duplicate); + if (peer_cfg && peer_cfg->equals(peer_cfg, ike_sa->get_peer_cfg(ike_sa))) + { + switch (duplicate->get_state(duplicate)) + { + case IKE_ESTABLISHED: + case IKE_REKEYING: + switch (policy) + { + case UNIQUE_REPLACE: + DBG1(DBG_IKE, "deleting duplicate IKE_SA for peer " + "'%Y' due to uniqueness policy", other); + status = duplicate->delete(duplicate); + break; + case UNIQUE_KEEP: + cancel = TRUE; + /* we keep the first IKE_SA and delete all + * other duplicates that might exist */ + policy = UNIQUE_REPLACE; + break; + default: + break; + } + break; + default: + break; + } + } + if (status == DESTROY_ME) + { + checkin_and_destroy(this, duplicate); + } + else + { + checkin(this, duplicate); + } + } + enumerator->destroy(enumerator); + duplicate_ids->destroy_offset(duplicate_ids, offsetof(ike_sa_id_t, destroy)); + /* reset thread's current IKE_SA after checkin */ + charon->bus->set_sa(charon->bus, ike_sa); + return cancel; +} + +/** + * Implementation of ike_sa_manager_t.get_half_open_count. + */ +static int get_half_open_count(private_ike_sa_manager_t *this, host_t *ip) +{ + int count = 0; + + if (ip) + { + linked_list_t *list; + chunk_t addr = ip->get_address(ip); + u_int row = chunk_hash(addr) & this->table_mask; + u_int segment = row & this->segment_mask; + + rwlock_t *lock = this->half_open_segments[segment & this->segment_mask].lock; + lock->read_lock(lock); + if ((list = this->half_open_table[row]) != NULL) + { + half_open_t *current; + + if (list->find_first(list, (linked_list_match_t)half_open_match, + (void**)¤t, &addr) == SUCCESS) + { + count = current->count; + } + } + lock->unlock(lock); + } + else + { + u_int segment; + + for (segment = 0; segment < this->segment_count; ++segment) + { + rwlock_t *lock; + lock = this->half_open_segments[segment & this->segment_mask].lock; + lock->read_lock(lock); + count += this->half_open_segments[segment].count; + lock->unlock(lock); + } + } + + return count; +} + +/** + * Implementation of ike_sa_manager_t.flush. + */ +static void flush(private_ike_sa_manager_t *this) +{ + /* destroy all list entries */ + enumerator_t *enumerator; + entry_t *entry; + u_int segment; + + lock_all_segments(this); + DBG2(DBG_MGR, "going to destroy IKE_SA manager and all managed IKE_SA's"); + /* Step 1: drive out all waiting threads */ + DBG2(DBG_MGR, "set driveout flags for all stored IKE_SA's"); + enumerator = create_table_enumerator(this); + while (enumerator->enumerate(enumerator, &entry, &segment)) + { + /* do not accept new threads, drive out waiting threads */ + entry->driveout_new_threads = TRUE; + entry->driveout_waiting_threads = TRUE; + } + enumerator->destroy(enumerator); + DBG2(DBG_MGR, "wait for all threads to leave IKE_SA's"); + /* Step 2: wait until all are gone */ + enumerator = create_table_enumerator(this); + while (enumerator->enumerate(enumerator, &entry, &segment)) + { + while (entry->waiting_threads || entry->checked_out) + { + /* wake up all */ + entry->condvar->broadcast(entry->condvar); + /* go sleeping until they are gone */ + entry->condvar->wait(entry->condvar, this->segments[segment].mutex); + } + } + enumerator->destroy(enumerator); + DBG2(DBG_MGR, "delete all IKE_SA's"); + /* Step 3: initiate deletion of all IKE_SAs */ + enumerator = create_table_enumerator(this); + while (enumerator->enumerate(enumerator, &entry, &segment)) + { + charon->bus->set_sa(charon->bus, entry->ike_sa); + /* as the delete never gets processed, fire down events */ + switch (entry->ike_sa->get_state(entry->ike_sa)) + { + case IKE_ESTABLISHED: + case IKE_REKEYING: + case IKE_DELETING: + charon->bus->ike_updown(charon->bus, entry->ike_sa, FALSE); + break; + default: + break; + } + entry->ike_sa->delete(entry->ike_sa); + } + enumerator->destroy(enumerator); + + DBG2(DBG_MGR, "destroy all entries"); + /* Step 4: destroy all entries */ + enumerator = create_table_enumerator(this); + while (enumerator->enumerate(enumerator, &entry, &segment)) + { + charon->bus->set_sa(charon->bus, entry->ike_sa); + if (entry->half_open) + { + remove_half_open(this, entry); + } + if (!entry->ike_sa_id->is_initiator(entry->ike_sa_id) && + entry->my_id && entry->other_id) + { + remove_connected_peers(this, entry); + } + remove_entry_at((private_enumerator_t*)enumerator); + entry_destroy(entry); + } + enumerator->destroy(enumerator); + charon->bus->set_sa(charon->bus, NULL); + unlock_all_segments(this); +} + +/** + * Implementation of ike_sa_manager_t.destroy. + */ +static void destroy(private_ike_sa_manager_t *this) +{ + u_int i; + + for (i = 0; i < this->table_size; ++i) + { + linked_list_t *list; + + if ((list = this->ike_sa_table[i]) != NULL) + { + list->destroy(list); + } + if ((list = this->half_open_table[i]) != NULL) + { + list->destroy(list); + } + if ((list = this->connected_peers_table[i]) != NULL) + { + list->destroy(list); + } + } + free(this->ike_sa_table); + free(this->half_open_table); + free(this->connected_peers_table); + for (i = 0; i < this->segment_count; ++i) + { + this->segments[i].mutex->destroy(this->segments[i].mutex); + this->half_open_segments[i].lock->destroy(this->half_open_segments[i].lock); + this->connected_peers_segments[i].lock->destroy(this->connected_peers_segments[i].lock); + } + free(this->segments); + free(this->half_open_segments); + free(this->connected_peers_segments); + + this->rng->destroy(this->rng); + this->hasher->destroy(this->hasher); + free(this); +} + +/** + * This function returns the next-highest power of two for the given number. + * The algorithm works by setting all bits on the right-hand side of the most + * significant 1 to 1 and then increments the whole number so it rolls over + * to the nearest power of two. Note: returns 0 for n == 0 + */ +static u_int get_nearest_powerof2(u_int n) +{ + u_int i; + + --n; + for (i = 1; i < sizeof(u_int) * 8; i <<= 1) + { + n |= n >> i; + } + return ++n; +} + +/* + * Described in header. + */ +ike_sa_manager_t *ike_sa_manager_create() +{ + u_int i; + private_ike_sa_manager_t *this = malloc_thing(private_ike_sa_manager_t); + + /* assign public functions */ + this->public.flush = (void(*)(ike_sa_manager_t*))flush; + this->public.destroy = (void(*)(ike_sa_manager_t*))destroy; + this->public.checkout = (ike_sa_t*(*)(ike_sa_manager_t*, ike_sa_id_t*))checkout; + this->public.checkout_new = (ike_sa_t*(*)(ike_sa_manager_t*,bool))checkout_new; + this->public.checkout_by_message = (ike_sa_t*(*)(ike_sa_manager_t*,message_t*))checkout_by_message; + this->public.checkout_by_config = (ike_sa_t*(*)(ike_sa_manager_t*,peer_cfg_t*))checkout_by_config; + this->public.checkout_by_id = (ike_sa_t*(*)(ike_sa_manager_t*,u_int32_t,bool))checkout_by_id; + this->public.checkout_by_name = (ike_sa_t*(*)(ike_sa_manager_t*,char*,bool))checkout_by_name; + this->public.check_uniqueness = (bool(*)(ike_sa_manager_t*, ike_sa_t *ike_sa))check_uniqueness; + this->public.create_enumerator = (enumerator_t*(*)(ike_sa_manager_t*))create_enumerator; + this->public.checkin = (void(*)(ike_sa_manager_t*,ike_sa_t*))checkin; + this->public.checkin_and_destroy = (void(*)(ike_sa_manager_t*,ike_sa_t*))checkin_and_destroy; + this->public.get_half_open_count = (int(*)(ike_sa_manager_t*,host_t*))get_half_open_count; + + /* initialize private variables */ + this->hasher = lib->crypto->create_hasher(lib->crypto, HASH_PREFERRED); + if (this->hasher == NULL) + { + DBG1(DBG_MGR, "manager initialization failed, no hasher supported"); + free(this); + return NULL; + } + this->rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK); + if (this->rng == NULL) + { + DBG1(DBG_MGR, "manager initialization failed, no RNG supported"); + this->hasher->destroy(this->hasher); + free(this); + return NULL; + } + this->table_size = get_nearest_powerof2(lib->settings->get_int(lib->settings, + "charon.ikesa_table_size", DEFAULT_HASHTABLE_SIZE)); + this->table_size = max(1, min(this->table_size, MAX_HASHTABLE_SIZE)); + this->table_mask = this->table_size - 1; + + this->segment_count = get_nearest_powerof2(lib->settings->get_int(lib->settings, + "charon.ikesa_table_segments", DEFAULT_SEGMENT_COUNT)); + this->segment_count = max(1, min(this->segment_count, this->table_size)); + this->segment_mask = this->segment_count - 1; + + this->ike_sa_table = calloc(this->table_size, sizeof(linked_list_t*)); + + this->segments = (segment_t*)calloc(this->segment_count, sizeof(segment_t)); + for (i = 0; i < this->segment_count; ++i) + { + this->segments[i].mutex = mutex_create(MUTEX_TYPE_RECURSIVE); + this->segments[i].count = 0; + } + + /* we use the same table parameters for the table to track half-open SAs */ + this->half_open_table = calloc(this->table_size, sizeof(linked_list_t*)); + this->half_open_segments = calloc(this->segment_count, sizeof(shareable_segment_t)); + for (i = 0; i < this->segment_count; ++i) + { + this->half_open_segments[i].lock = rwlock_create(RWLOCK_TYPE_DEFAULT); + this->half_open_segments[i].count = 0; + } + + /* also for the hash table used for duplicate tests */ + this->connected_peers_table = calloc(this->table_size, sizeof(linked_list_t*)); + this->connected_peers_segments = calloc(this->segment_count, sizeof(shareable_segment_t)); + for (i = 0; i < this->segment_count; ++i) + { + this->connected_peers_segments[i].lock = rwlock_create(RWLOCK_TYPE_DEFAULT); + this->connected_peers_segments[i].count = 0; + } + + this->reuse_ikesa = lib->settings->get_bool(lib->settings, + "charon.reuse_ikesa", TRUE); + return &this->public; +} diff --git a/src/libcharon/sa/ike_sa_manager.h b/src/libcharon/sa/ike_sa_manager.h new file mode 100644 index 000000000..38f5454e1 --- /dev/null +++ b/src/libcharon/sa/ike_sa_manager.h @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2008 Tobias Brunner + * Copyright (C) 2005-2008 Martin Willi + * Copyright (C) 2005 Jan Hutter + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup ike_sa_manager ike_sa_manager + * @{ @ingroup sa + */ + +#ifndef IKE_SA_MANAGER_H_ +#define IKE_SA_MANAGER_H_ + +typedef struct ike_sa_manager_t ike_sa_manager_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <encoding/message.h> +#include <config/peer_cfg.h> + +/** + * Manages and synchronizes access to all IKE_SAs. + * + * To synchronize access to thread-unsave IKE_SAs, they are checked out for + * use and checked in afterwards. A checked out SA is exclusively accessible + * by the owning thread. + */ +struct ike_sa_manager_t { + + /** + * Checkout an existing IKE_SA. + * + * @param ike_sa_id the SA identifier, will be updated + * @returns + * - checked out IKE_SA if found + * - NULL, if specified IKE_SA is not found. + */ + ike_sa_t* (*checkout) (ike_sa_manager_t* this, ike_sa_id_t *sa_id); + + /** + * Create and check out a new IKE_SA. + * + * @note If initiator equals FALSE, the returned IKE_SA is not registered + * in the manager. + * + * @param initiator TRUE for initiator, FALSE otherwise + * @returns created and checked out IKE_SA + */ + ike_sa_t* (*checkout_new) (ike_sa_manager_t* this, bool initiator); + + /** + * Checkout an IKE_SA by a message. + * + * In some situations, it is necessary that the manager knows the + * message to use for the checkout. This has the following reasons: + * + * 1. If the targeted IKE_SA is already processing a message, we do not + * check it out if the message ID is the same. + * 2. If it is an IKE_SA_INIT request, we have to check if it is a + * retransmission. If so, we have to drop the message, we would + * create another unneeded IKE_SA for each retransmitted packet. + * + * A call to checkout_by_message() returns a (maybe new created) IKE_SA. + * If processing the message does not make sense (for the reasons above), + * NULL is returned. + * + * @param ike_sa_id the SA identifier, will be updated + * @returns + * - checked out/created IKE_SA + * - NULL to not process message further + */ + ike_sa_t* (*checkout_by_message) (ike_sa_manager_t* this, message_t *message); + + /** + * Checkout an IKE_SA for initiation by a peer_config. + * + * To initiate, a CHILD_SA may be established within an existing IKE_SA. + * This call checks for an existing IKE_SA by comparing the configuration. + * If the CHILD_SA can be created in an existing IKE_SA, the matching SA + * is returned. + * If no IKE_SA is found, a new one is created. This is also the case when + * the found IKE_SA is in the DELETING state. + * + * @param peer_cfg configuration used to find an existing IKE_SA + * @return checked out/created IKE_SA + */ + ike_sa_t* (*checkout_by_config) (ike_sa_manager_t* this, + peer_cfg_t *peer_cfg); + + /** + * Check for duplicates of the given IKE_SA. + * + * Measures are taken according to the uniqueness policy of the IKE_SA. + * The return value indicates whether duplicates have been found and if + * further measures should be taken (e.g. cancelling an IKE_AUTH exchange). + * check_uniqueness() must be called before the IKE_SA is complete, + * deadlocks occur otherwise. + * + * @param ike_sa ike_sa to check + * @return TRUE, if the given IKE_SA has duplicates and + * should be deleted + */ + bool (*check_uniqueness)(ike_sa_manager_t *this, ike_sa_t *ike_sa); + + /** + * Check out an IKE_SA a unique ID. + * + * Every IKE_SA and every CHILD_SA is uniquely identified by an ID. + * These checkout function uses, depending + * on the child parameter, the unique ID of the IKE_SA or the reqid + * of one of a IKE_SAs CHILD_SA. + * + * @param id unique ID of the object + * @param child TRUE to use CHILD, FALSE to use IKE_SA + * @return + * - checked out IKE_SA, if found + * - NULL, if not found + */ + ike_sa_t* (*checkout_by_id) (ike_sa_manager_t* this, u_int32_t id, + bool child); + + /** + * Check out an IKE_SA by the policy/connection name. + * + * Check out the IKE_SA by the configuration name, either from the IKE- or + * one of its CHILD_SAs. + * + * @param name name of the connection/policy + * @param child TRUE to use policy name, FALSE to use conn name + * @return + * - checked out IKE_SA, if found + * - NULL, if not found + */ + ike_sa_t* (*checkout_by_name) (ike_sa_manager_t* this, char *name, + bool child); + + /** + * Create an enumerator over all stored IKE_SAs. + * + * While enumerating an IKE_SA, it is temporarily checked out and + * automatically checked in after the current enumeration step. + * + * @return enumerator over all IKE_SAs. + */ + enumerator_t *(*create_enumerator) (ike_sa_manager_t* this); + + /** + * Checkin the SA after usage. + * + * If the IKE_SA is not registered in the manager, a new entry is created. + * + * @param ike_sa_id the SA identifier, will be updated + * @param ike_sa checked out SA + */ + void (*checkin) (ike_sa_manager_t* this, ike_sa_t *ike_sa); + + /** + * Destroy a checked out SA. + * + * The IKE SA is destroyed without notification of the remote peer. + * Use this only if the other peer doesn't respond or behaves not + * as predicted. + * Checking in and destruction is an atomic operation (for the IKE_SA), + * so this can be called if the SA is in a "unclean" state, without the + * risk that another thread can get the SA. + * + * @param ike_sa SA to delete + */ + void (*checkin_and_destroy) (ike_sa_manager_t* this, ike_sa_t *ike_sa); + + /** + * Get the number of IKE_SAs which are in the connecting state. + * + * To prevent the server from resource exhaustion, cookies and other + * mechanisms are used. The number of half open IKE_SAs is a good + * indicator to see if a peer is flooding the server. + * If a host is supplied, only the number of half open IKE_SAs initiated + * from this IP are counted. + * Only SAs for which we are the responder are counted. + * + * @param ip NULL for all, IP for half open IKE_SAs with IP + * @return number of half open IKE_SAs + */ + int (*get_half_open_count) (ike_sa_manager_t *this, host_t *ip); + + /** + * Delete all existing IKE_SAs and destroy them immediately. + * + * Threads will be driven out, so all SAs can be deleted cleanly. + */ + void (*flush)(ike_sa_manager_t *this); + + /** + * Destroys the manager with all associated SAs. + * + * A call to flush() is required before calling destroy. + */ + void (*destroy) (ike_sa_manager_t *this); +}; + +/** + * Create the IKE_SA manager. + * + * @returns ike_sa_manager_t object, NULL if initialization fails + */ +ike_sa_manager_t *ike_sa_manager_create(void); + +#endif /** IKE_SA_MANAGER_H_ @}*/ diff --git a/src/libcharon/sa/keymat.c b/src/libcharon/sa/keymat.c new file mode 100644 index 000000000..837cbe428 --- /dev/null +++ b/src/libcharon/sa/keymat.c @@ -0,0 +1,617 @@ +/* + * Copyright (C) 2008 Martin Willi + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "keymat.h" + +#include <daemon.h> +#include <crypto/prf_plus.h> + +typedef struct private_keymat_t private_keymat_t; + +/** + * Private data of an keymat_t object. + */ +struct private_keymat_t { + + /** + * Public keymat_t interface. + */ + keymat_t public; + + /** + * IKE_SA Role, initiator or responder + */ + bool initiator; + + /** + * inbound signer (verify) + */ + signer_t *signer_in; + + /** + * outbound signer (sign) + */ + signer_t *signer_out; + + /** + * inbound crypter (decrypt) + */ + crypter_t *crypter_in; + + /** + * outbound crypter (encrypt) + */ + crypter_t *crypter_out; + + /** + * General purpose PRF + */ + prf_t *prf; + + /** + * Negotiated PRF algorithm + */ + pseudo_random_function_t prf_alg; + + /** + * Key to derive key material from for CHILD_SAs, rekeying + */ + chunk_t skd; + + /** + * Key to build outging authentication data (SKp) + */ + chunk_t skp_build; + + /** + * Key to verify incoming authentication data (SKp) + */ + chunk_t skp_verify; +}; + +typedef struct keylen_entry_t keylen_entry_t; + +/** + * Implicit key length for an algorithm + */ +struct keylen_entry_t { + /** IKEv2 algorithm identifier */ + int algo; + /** key length in bits */ + int len; +}; + +#define END_OF_LIST -1 + +/** + * Keylen for encryption algos + */ +keylen_entry_t keylen_enc[] = { + {ENCR_DES, 64}, + {ENCR_3DES, 192}, + {END_OF_LIST, 0} +}; + +/** + * Keylen for integrity algos + */ +keylen_entry_t keylen_int[] = { + {AUTH_HMAC_MD5_96, 128}, + {AUTH_HMAC_SHA1_96, 160}, + {AUTH_HMAC_SHA2_256_96, 256}, + {AUTH_HMAC_SHA2_256_128, 256}, + {AUTH_HMAC_SHA2_384_192, 384}, + {AUTH_HMAC_SHA2_512_256, 512}, + {AUTH_AES_XCBC_96, 128}, + {END_OF_LIST, 0} +}; + +/** + * Lookup key length of an algorithm + */ +static int lookup_keylen(keylen_entry_t *list, int algo) +{ + while (list->algo != END_OF_LIST) + { + if (algo == list->algo) + { + return list->len; + } + list++; + } + return 0; +} + +/** + * Implementation of keymat_t.create_dh + */ +static diffie_hellman_t* create_dh(private_keymat_t *this, + diffie_hellman_group_t group) +{ + return lib->crypto->create_dh(lib->crypto, group);; +} + +/** + * Implementation of keymat_t.derive_keys + */ +static bool derive_ike_keys(private_keymat_t *this, proposal_t *proposal, + diffie_hellman_t *dh, chunk_t nonce_i, + chunk_t nonce_r, ike_sa_id_t *id, + pseudo_random_function_t rekey_function, + chunk_t rekey_skd) +{ + chunk_t skeyseed, key, secret, full_nonce, fixed_nonce, prf_plus_seed; + chunk_t spi_i, spi_r; + crypter_t *crypter_i, *crypter_r; + signer_t *signer_i, *signer_r; + prf_plus_t *prf_plus; + u_int16_t alg, key_size; + prf_t *rekey_prf = NULL; + + spi_i = chunk_alloca(sizeof(u_int64_t)); + spi_r = chunk_alloca(sizeof(u_int64_t)); + + if (dh->get_shared_secret(dh, &secret) != SUCCESS) + { + return FALSE; + } + + /* Create SAs general purpose PRF first, we may use it here */ + if (!proposal->get_algorithm(proposal, PSEUDO_RANDOM_FUNCTION, &alg, NULL)) + { + DBG1(DBG_IKE, "no %N selected", + transform_type_names, PSEUDO_RANDOM_FUNCTION); + return FALSE; + } + this->prf_alg = alg; + this->prf = lib->crypto->create_prf(lib->crypto, alg); + if (this->prf == NULL) + { + DBG1(DBG_IKE, "%N %N not supported!", + transform_type_names, PSEUDO_RANDOM_FUNCTION, + pseudo_random_function_names, alg); + return FALSE; + } + DBG4(DBG_IKE, "shared Diffie Hellman secret %B", &secret); + /* full nonce is used as seed for PRF+ ... */ + full_nonce = chunk_cat("cc", nonce_i, nonce_r); + /* but the PRF may need a fixed key which only uses the first bytes of + * the nonces. */ + switch (alg) + { + case PRF_AES128_XCBC: + /* while rfc4434 defines variable keys for AES-XCBC, rfc3664 does + * not and therefore fixed key semantics apply to XCBC for key + * derivation. */ + key_size = this->prf->get_key_size(this->prf)/2; + nonce_i.len = min(nonce_i.len, key_size); + nonce_r.len = min(nonce_r.len, key_size); + break; + default: + /* all other algorithms use variable key length, full nonce */ + break; + } + fixed_nonce = chunk_cat("cc", nonce_i, nonce_r); + *((u_int64_t*)spi_i.ptr) = id->get_initiator_spi(id); + *((u_int64_t*)spi_r.ptr) = id->get_responder_spi(id); + prf_plus_seed = chunk_cat("ccc", full_nonce, spi_i, spi_r); + + /* KEYMAT = prf+ (SKEYSEED, Ni | Nr | SPIi | SPIr) + * + * if we are rekeying, SKEYSEED is built on another way + */ + if (rekey_function == PRF_UNDEFINED) /* not rekeying */ + { + /* SKEYSEED = prf(Ni | Nr, g^ir) */ + this->prf->set_key(this->prf, fixed_nonce); + this->prf->allocate_bytes(this->prf, secret, &skeyseed); + this->prf->set_key(this->prf, skeyseed); + prf_plus = prf_plus_create(this->prf, prf_plus_seed); + } + else + { + /* SKEYSEED = prf(SK_d (old), [g^ir (new)] | Ni | Nr) + * use OLD SAs PRF functions for both prf_plus and prf */ + rekey_prf = lib->crypto->create_prf(lib->crypto, rekey_function); + if (!rekey_prf) + { + DBG1(DBG_IKE, "PRF of old SA %N not supported!", + pseudo_random_function_names, rekey_function); + chunk_free(&full_nonce); + chunk_free(&fixed_nonce); + chunk_clear(&prf_plus_seed); + return FALSE; + } + secret = chunk_cat("mc", secret, full_nonce); + rekey_prf->set_key(rekey_prf, rekey_skd); + rekey_prf->allocate_bytes(rekey_prf, secret, &skeyseed); + rekey_prf->set_key(rekey_prf, skeyseed); + prf_plus = prf_plus_create(rekey_prf, prf_plus_seed); + } + DBG4(DBG_IKE, "SKEYSEED %B", &skeyseed); + + chunk_clear(&skeyseed); + chunk_clear(&secret); + chunk_free(&full_nonce); + chunk_free(&fixed_nonce); + chunk_clear(&prf_plus_seed); + + /* KEYMAT = SK_d | SK_ai | SK_ar | SK_ei | SK_er | SK_pi | SK_pr */ + + /* SK_d is used for generating CHILD_SA key mat => store for later use */ + key_size = this->prf->get_key_size(this->prf); + prf_plus->allocate_bytes(prf_plus, key_size, &this->skd); + DBG4(DBG_IKE, "Sk_d secret %B", &this->skd); + + /* SK_ai/SK_ar used for integrity protection => signer_in/signer_out */ + if (!proposal->get_algorithm(proposal, INTEGRITY_ALGORITHM, &alg, NULL)) + { + DBG1(DBG_IKE, "no %N selected", + transform_type_names, INTEGRITY_ALGORITHM); + prf_plus->destroy(prf_plus); + DESTROY_IF(rekey_prf); + return FALSE; + } + signer_i = lib->crypto->create_signer(lib->crypto, alg); + signer_r = lib->crypto->create_signer(lib->crypto, alg); + if (signer_i == NULL || signer_r == NULL) + { + DBG1(DBG_IKE, "%N %N not supported!", + transform_type_names, INTEGRITY_ALGORITHM, + integrity_algorithm_names ,alg); + prf_plus->destroy(prf_plus); + DESTROY_IF(rekey_prf); + return FALSE; + } + key_size = signer_i->get_key_size(signer_i); + + prf_plus->allocate_bytes(prf_plus, key_size, &key); + DBG4(DBG_IKE, "Sk_ai secret %B", &key); + signer_i->set_key(signer_i, key); + chunk_clear(&key); + + prf_plus->allocate_bytes(prf_plus, key_size, &key); + DBG4(DBG_IKE, "Sk_ar secret %B", &key); + signer_r->set_key(signer_r, key); + chunk_clear(&key); + + if (this->initiator) + { + this->signer_in = signer_r; + this->signer_out = signer_i; + } + else + { + this->signer_in = signer_i; + this->signer_out = signer_r; + } + + /* SK_ei/SK_er used for encryption => crypter_in/crypter_out */ + if (!proposal->get_algorithm(proposal, ENCRYPTION_ALGORITHM, &alg, &key_size)) + { + DBG1(DBG_IKE, "no %N selected", + transform_type_names, ENCRYPTION_ALGORITHM); + prf_plus->destroy(prf_plus); + DESTROY_IF(rekey_prf); + return FALSE; + } + crypter_i = lib->crypto->create_crypter(lib->crypto, alg, key_size / 8); + crypter_r = lib->crypto->create_crypter(lib->crypto, alg, key_size / 8); + if (crypter_i == NULL || crypter_r == NULL) + { + DBG1(DBG_IKE, "%N %N (key size %d) not supported!", + transform_type_names, ENCRYPTION_ALGORITHM, + encryption_algorithm_names, alg, key_size); + prf_plus->destroy(prf_plus); + DESTROY_IF(rekey_prf); + return FALSE; + } + key_size = crypter_i->get_key_size(crypter_i); + + prf_plus->allocate_bytes(prf_plus, key_size, &key); + DBG4(DBG_IKE, "Sk_ei secret %B", &key); + crypter_i->set_key(crypter_i, key); + chunk_clear(&key); + + prf_plus->allocate_bytes(prf_plus, key_size, &key); + DBG4(DBG_IKE, "Sk_er secret %B", &key); + crypter_r->set_key(crypter_r, key); + chunk_clear(&key); + + if (this->initiator) + { + this->crypter_in = crypter_r; + this->crypter_out = crypter_i; + } + else + { + this->crypter_in = crypter_i; + this->crypter_out = crypter_r; + } + + /* SK_pi/SK_pr used for authentication => stored for later */ + key_size = this->prf->get_key_size(this->prf); + prf_plus->allocate_bytes(prf_plus, key_size, &key); + DBG4(DBG_IKE, "Sk_pi secret %B", &key); + if (this->initiator) + { + this->skp_build = key; + } + else + { + this->skp_verify = key; + } + prf_plus->allocate_bytes(prf_plus, key_size, &key); + DBG4(DBG_IKE, "Sk_pr secret %B", &key); + if (this->initiator) + { + this->skp_verify = key; + } + else + { + this->skp_build = key; + } + + /* all done, prf_plus not needed anymore */ + prf_plus->destroy(prf_plus); + DESTROY_IF(rekey_prf); + + return TRUE; +} + +/** + * Implementation of keymat_t.derive_child_keys + */ +static bool derive_child_keys(private_keymat_t *this, + proposal_t *proposal, diffie_hellman_t *dh, + chunk_t nonce_i, chunk_t nonce_r, + chunk_t *encr_i, chunk_t *integ_i, + chunk_t *encr_r, chunk_t *integ_r) +{ + u_int16_t enc_alg, int_alg, enc_size = 0, int_size = 0; + chunk_t seed, secret = chunk_empty; + prf_plus_t *prf_plus; + + if (dh) + { + if (dh->get_shared_secret(dh, &secret) != SUCCESS) + { + return FALSE; + } + DBG4(DBG_CHD, "DH secret %B", &secret); + } + seed = chunk_cata("mcc", secret, nonce_i, nonce_r); + DBG4(DBG_CHD, "seed %B", &seed); + + if (proposal->get_algorithm(proposal, ENCRYPTION_ALGORITHM, + &enc_alg, &enc_size)) + { + DBG2(DBG_CHD, " using %N for encryption", + encryption_algorithm_names, enc_alg); + + if (!enc_size) + { + enc_size = lookup_keylen(keylen_enc, enc_alg); + } + if (enc_alg != ENCR_NULL && !enc_size) + { + DBG1(DBG_CHD, "no keylength defined for %N", + encryption_algorithm_names, enc_alg); + return FALSE; + } + /* to bytes */ + enc_size /= 8; + + /* CCM/GCM/CTR/GMAC needs additional bytes */ + switch (enc_alg) + { + case ENCR_AES_CCM_ICV8: + case ENCR_AES_CCM_ICV12: + case ENCR_AES_CCM_ICV16: + case ENCR_CAMELLIA_CCM_ICV8: + case ENCR_CAMELLIA_CCM_ICV12: + case ENCR_CAMELLIA_CCM_ICV16: + enc_size += 3; + break; + case ENCR_AES_GCM_ICV8: + case ENCR_AES_GCM_ICV12: + case ENCR_AES_GCM_ICV16: + case ENCR_AES_CTR: + case ENCR_NULL_AUTH_AES_GMAC: + enc_size += 4; + break; + default: + break; + } + } + + if (proposal->get_algorithm(proposal, INTEGRITY_ALGORITHM, + &int_alg, &int_size)) + { + DBG2(DBG_CHD, " using %N for integrity", + integrity_algorithm_names, int_alg); + + if (!int_size) + { + int_size = lookup_keylen(keylen_int, int_alg); + } + if (!int_size) + { + DBG1(DBG_CHD, "no keylength defined for %N", + integrity_algorithm_names, int_alg); + return FALSE; + } + /* to bytes */ + int_size /= 8; + } + + this->prf->set_key(this->prf, this->skd); + prf_plus = prf_plus_create(this->prf, seed); + + prf_plus->allocate_bytes(prf_plus, enc_size, encr_i); + prf_plus->allocate_bytes(prf_plus, int_size, integ_i); + prf_plus->allocate_bytes(prf_plus, enc_size, encr_r); + prf_plus->allocate_bytes(prf_plus, int_size, integ_r); + + prf_plus->destroy(prf_plus); + + if (enc_size) + { + DBG4(DBG_CHD, "encryption initiator key %B", encr_i); + DBG4(DBG_CHD, "encryption responder key %B", encr_r); + } + if (int_size) + { + DBG4(DBG_CHD, "integrity initiator key %B", integ_i); + DBG4(DBG_CHD, "integrity responder key %B", integ_r); + } + return TRUE; +} + +/** + * Implementation of keymat_t.get_skd + */ +static pseudo_random_function_t get_skd(private_keymat_t *this, chunk_t *skd) +{ + *skd = this->skd; + return this->prf_alg; +} + +/** + * Implementation of keymat_t.get_signer + */ +static signer_t* get_signer(private_keymat_t *this, bool in) +{ + return in ? this->signer_in : this->signer_out; +} + +/** + * Implementation of keymat_t.get_crypter + */ +static crypter_t* get_crypter(private_keymat_t *this, bool in) +{ + return in ? this->crypter_in : this->crypter_out; +} + +/** + * Implementation of keymat_t.get_auth_octets + */ +static chunk_t get_auth_octets(private_keymat_t *this, bool verify, + chunk_t ike_sa_init, chunk_t nonce, + identification_t *id) +{ + chunk_t chunk, idx, octets; + chunk_t skp; + + skp = verify ? this->skp_verify : this->skp_build; + + chunk = chunk_alloca(4); + memset(chunk.ptr, 0, chunk.len); + chunk.ptr[0] = id->get_type(id); + idx = chunk_cata("cc", chunk, id->get_encoding(id)); + + DBG3(DBG_IKE, "IDx' %B", &idx); + DBG3(DBG_IKE, "SK_p %B", &skp); + this->prf->set_key(this->prf, skp); + this->prf->allocate_bytes(this->prf, idx, &chunk); + + octets = chunk_cat("ccm", ike_sa_init, nonce, chunk); + DBG3(DBG_IKE, "octets = message + nonce + prf(Sk_px, IDx') %B", &octets); + return octets; +} + +/** + * Key pad for the AUTH method SHARED_KEY_MESSAGE_INTEGRITY_CODE. + */ +#define IKEV2_KEY_PAD "Key Pad for IKEv2" +#define IKEV2_KEY_PAD_LENGTH 17 + +/** + * Implementation of keymat_t.get_psk_sig + */ +static chunk_t get_psk_sig(private_keymat_t *this, bool verify, + chunk_t ike_sa_init, chunk_t nonce, chunk_t secret, + identification_t *id) +{ + chunk_t key_pad, key, sig, octets; + + if (!secret.len) + { /* EAP uses SK_p if no MSK has been established */ + secret = verify ? this->skp_verify : this->skp_build; + } + octets = get_auth_octets(this, verify, ike_sa_init, nonce, id); + /* AUTH = prf(prf(Shared Secret,"Key Pad for IKEv2"), <msg octets>) */ + key_pad = chunk_create(IKEV2_KEY_PAD, IKEV2_KEY_PAD_LENGTH); + this->prf->set_key(this->prf, secret); + this->prf->allocate_bytes(this->prf, key_pad, &key); + this->prf->set_key(this->prf, key); + this->prf->allocate_bytes(this->prf, octets, &sig); + DBG4(DBG_IKE, "secret %B", &secret); + DBG4(DBG_IKE, "prf(secret, keypad) %B", &key); + DBG3(DBG_IKE, "AUTH = prf(prf(secret, keypad), octets) %B", &sig); + chunk_free(&octets); + chunk_free(&key); + + return sig; +} + +/** + * Implementation of keymat_t.destroy. + */ +static void destroy(private_keymat_t *this) +{ + DESTROY_IF(this->signer_in); + DESTROY_IF(this->signer_out); + DESTROY_IF(this->crypter_in); + DESTROY_IF(this->crypter_out); + DESTROY_IF(this->prf); + chunk_clear(&this->skd); + chunk_clear(&this->skp_verify); + chunk_clear(&this->skp_build); + free(this); +} + +/** + * See header + */ +keymat_t *keymat_create(bool initiator) +{ + private_keymat_t *this = malloc_thing(private_keymat_t); + + this->public.create_dh = (diffie_hellman_t*(*)(keymat_t*, diffie_hellman_group_t group))create_dh; + this->public.derive_ike_keys = (bool(*)(keymat_t*, proposal_t *proposal, diffie_hellman_t *dh, chunk_t nonce_i, chunk_t nonce_r, ike_sa_id_t *id, pseudo_random_function_t,chunk_t))derive_ike_keys; + this->public.derive_child_keys = (bool(*)(keymat_t*, proposal_t *proposal, diffie_hellman_t *dh, chunk_t nonce_i, chunk_t nonce_r, chunk_t *encr_i, chunk_t *integ_i, chunk_t *encr_r, chunk_t *integ_r))derive_child_keys; + this->public.get_skd = (pseudo_random_function_t(*)(keymat_t*, chunk_t *skd))get_skd; + this->public.get_signer = (signer_t*(*)(keymat_t*, bool in))get_signer; + this->public.get_crypter = (crypter_t*(*)(keymat_t*, bool in))get_crypter; + this->public.get_auth_octets = (chunk_t(*)(keymat_t *, bool verify, chunk_t ike_sa_init, chunk_t nonce, identification_t *id))get_auth_octets; + this->public.get_psk_sig = (chunk_t(*)(keymat_t*, bool verify, chunk_t ike_sa_init, chunk_t nonce, chunk_t secret, identification_t *id))get_psk_sig; + this->public.destroy = (void(*)(keymat_t*))destroy; + + this->initiator = initiator; + + this->signer_in = NULL; + this->signer_out = NULL; + this->crypter_in = NULL; + this->crypter_out = NULL; + this->prf = NULL; + this->prf_alg = PRF_UNDEFINED; + this->skd = chunk_empty; + this->skp_verify = chunk_empty; + this->skp_build = chunk_empty; + + return &this->public; +} + diff --git a/src/libcharon/sa/keymat.h b/src/libcharon/sa/keymat.h new file mode 100644 index 000000000..e51709e8d --- /dev/null +++ b/src/libcharon/sa/keymat.h @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2008 Martin Willi + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup keymat keymat + * @{ @ingroup sa + */ + +#ifndef KEYMAT_H_ +#define KEYMAT_H_ + +#include <library.h> +#include <utils/identification.h> +#include <crypto/prfs/prf.h> +#include <crypto/crypters/crypter.h> +#include <crypto/signers/signer.h> +#include <config/proposal.h> +#include <sa/ike_sa_id.h> + +typedef struct keymat_t keymat_t; + +/** + * Derivation an management of sensitive keying material. + */ +struct keymat_t { + + /** + * Create a diffie hellman object for key agreement. + * + * The diffie hellman is either for IKE negotiation/rekeying or + * CHILD_SA rekeying (using PFS). The resulting DH object must be passed + * to derive_keys or to derive_child_keys and destroyed after use + * + * @param group diffie hellman group + * @return DH object, NULL if group not supported + */ + diffie_hellman_t* (*create_dh)(keymat_t *this, diffie_hellman_group_t group); + + /** + * Derive keys for the IKE_SA. + * + * These keys are not handed out, but are used by the associated signers, + * crypters and authentication functions. + * + * @param proposal selected algorithms + * @param dh diffie hellman key allocated by create_dh() + * @param nonce_i initiators nonce value + * @param nonce_r responders nonce value + * @param id IKE_SA identifier + * @param rekey_prf PRF of old SA if rekeying, PRF_UNDEFINED otherwise + * @param rekey_sdk SKd of old SA if rekeying + * @return TRUE on success + */ + bool (*derive_ike_keys)(keymat_t *this, proposal_t *proposal, + diffie_hellman_t *dh, chunk_t nonce_i, + chunk_t nonce_r, ike_sa_id_t *id, + pseudo_random_function_t rekey_function, + chunk_t rekey_skd); + /** + * Derive keys for a CHILD_SA. + * + * The keys for the CHILD_SA are allocated in the integ and encr chunks. + * An implementation might hand out encrypted keys only, which are + * decrypted in the kernel before use. + * If no PFS is used for the CHILD_SA, dh can be NULL. + * + * @param proposal selected algorithms + * @param dh diffie hellman key allocated by create_dh(), or NULL + * @param nonce_i initiators nonce value + * @param nonce_r responders nonce value + * @param encr_i chunk to write initiators encryption key to + * @param integ_i chunk to write initiators integrity key to + * @param encr_r chunk to write responders encryption key to + * @param integ_r chunk to write responders integrity key to + * @return TRUE on success + */ + bool (*derive_child_keys)(keymat_t *this, + proposal_t *proposal, diffie_hellman_t *dh, + chunk_t nonce_i, chunk_t nonce_r, + chunk_t *encr_i, chunk_t *integ_i, + chunk_t *encr_r, chunk_t *integ_r); + /** + * Get SKd to pass to derive_ikey_keys() during rekeying. + * + * @param skd chunk to write SKd to (internal data) + * @return PRF function to derive keymat + */ + pseudo_random_function_t (*get_skd)(keymat_t *this, chunk_t *skd); + + /** + * Get a signer to sign/verify IKE messages. + * + * @param in TRUE for inbound (verify), FALSE for outbound (sign) + * @return signer + */ + signer_t* (*get_signer)(keymat_t *this, bool in); + + /* + * Get a crypter to en-/decrypt IKE messages. + * + * @param in TRUE for inbound (decrypt), FALSE for outbound (encrypt) + * @return crypter + */ + crypter_t* (*get_crypter)(keymat_t *this, bool in); + + /** + * Generate octets to use for authentication procedure (RFC4306 2.15). + * + * This method creates the plain octets and is usually signed by a private + * key. PSK and EAP authentication include a secret into the data, use + * the get_psk_sig() method instead. + * + * @param verify TRUE to create for verfification, FALSE to sign + * @param ike_sa_init encoded ike_sa_init message + * @param nonce nonce value + * @param id identity + * @return authentication octets + */ + chunk_t (*get_auth_octets)(keymat_t *this, bool verify, chunk_t ike_sa_init, + chunk_t nonce, identification_t *id); + /** + * Build the shared secret signature used for PSK and EAP authentication. + * + * This method wraps the get_auth_octets() method and additionally + * includes the secret into the signature. If no secret is given, SK_p is + * used as secret (used for EAP methods without MSK). + * + * @param verify TRUE to create for verfification, FALSE to sign + * @param ike_sa_init encoded ike_sa_init message + * @param nonce nonce value + * @param secret optional secret to include into signature + * @param id identity + * @return signature octets + */ + chunk_t (*get_psk_sig)(keymat_t *this, bool verify, chunk_t ike_sa_init, + chunk_t nonce, chunk_t secret, identification_t *id); + /** + * Destroy a keymat_t. + */ + void (*destroy)(keymat_t *this); +}; + +/** + * Create a keymat instance. + * + * @param initiator TRUE if we are the initiator + * @return keymat instance + */ +keymat_t *keymat_create(bool initiator); + +#endif /** KEYMAT_H_ @}*/ diff --git a/src/libcharon/sa/mediation_manager.c b/src/libcharon/sa/mediation_manager.c new file mode 100644 index 000000000..035f49053 --- /dev/null +++ b/src/libcharon/sa/mediation_manager.c @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2007 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 "mediation_manager.h" + +#include <daemon.h> +#include <threading/mutex.h> +#include <utils/linked_list.h> +#include <processing/jobs/mediation_job.h> + +typedef struct peer_t peer_t; + +/** + * An entry in the linked list. + */ +struct peer_t { + /** id of the peer */ + identification_t *id; + + /** sa id of the peer, NULL if offline */ + ike_sa_id_t *ike_sa_id; + + /** list of peer ids that reuested this peer */ + linked_list_t *requested_by; +}; + +/** + * Implementation of peer_t.destroy. + */ +static void peer_destroy(peer_t *this) +{ + DESTROY_IF(this->id); + DESTROY_IF(this->ike_sa_id); + this->requested_by->destroy_offset(this->requested_by, + offsetof(identification_t, destroy)); + free(this); +} + +/** + * Creates a new entry for the list. + */ +static peer_t *peer_create(identification_t *id, ike_sa_id_t* ike_sa_id) +{ + peer_t *this = malloc_thing(peer_t); + + /* clone everything */ + this->id = id->clone(id); + this->ike_sa_id = ike_sa_id ? ike_sa_id->clone(ike_sa_id) : NULL; + this->requested_by = linked_list_create(); + + return this; +} + +typedef struct private_mediation_manager_t private_mediation_manager_t; + +/** + * Additional private members of mediation_manager_t. + */ +struct private_mediation_manager_t { + /** + * Public interface of mediation_manager_t. + */ + mediation_manager_t public; + + /** + * Lock for exclusivly accessing the manager. + */ + mutex_t *mutex; + + /** + * Linked list with state entries. + */ + linked_list_t *peers; +}; + +/** + * Registers a peer's ID at another peer, if it is not yet registered + */ +static void register_peer(peer_t *peer, identification_t *peer_id) +{ + iterator_t *iterator; + identification_t *current; + + iterator = peer->requested_by->create_iterator(peer->requested_by, TRUE); + while (iterator->iterate(iterator, (void**)¤t)) + { + if (peer_id->equals(peer_id, current)) + { + iterator->destroy(iterator); + return; + } + } + iterator->destroy(iterator); + + peer->requested_by->insert_last(peer->requested_by, + peer_id->clone(peer_id)); +} + +/** + * Get a peer_t object by a peer's id + */ +static status_t get_peer_by_id(private_mediation_manager_t *this, + identification_t *id, peer_t **peer) +{ + iterator_t *iterator; + peer_t *current; + status_t status = NOT_FOUND; + + iterator = this->peers->create_iterator(this->peers, TRUE); + while (iterator->iterate(iterator, (void**)¤t)) + { + if (id->equals(id, current->id)) + { + if (peer) + { + *peer = current; + } + status = SUCCESS; + break; + } + } + iterator->destroy(iterator); + + return status; +} + +/** + * Check if a given peer is registered at other peers. If so, remove it there + * and then remove peers completely that are not online and have no registered + * peers. + */ +static void unregister_peer(private_mediation_manager_t *this, + identification_t *peer_id) +{ + iterator_t *iterator, *iterator_r; + peer_t *peer; + identification_t *registered; + + iterator = this->peers->create_iterator(this->peers, TRUE); + while (iterator->iterate(iterator, (void**)&peer)) + { + iterator_r = peer->requested_by->create_iterator(peer->requested_by, + TRUE); + while (iterator_r->iterate(iterator_r, (void**)®istered)) + { + if (peer_id->equals(peer_id, registered)) + { + iterator_r->remove(iterator_r); + registered->destroy(registered); + break; + } + } + iterator_r->destroy(iterator_r); + + if (!peer->ike_sa_id && !peer->requested_by->get_count(peer->requested_by)) + { + iterator->remove(iterator); + peer_destroy(peer); + break; + } + } + iterator->destroy(iterator); +} + +/** + * Implementation of mediation_manager_t.remove + */ +static void remove_sa(private_mediation_manager_t *this, ike_sa_id_t *ike_sa_id) +{ + iterator_t *iterator; + peer_t *peer; + + this->mutex->lock(this->mutex); + + iterator = this->peers->create_iterator(this->peers, TRUE); + while (iterator->iterate(iterator, (void**)&peer)) + { + if (ike_sa_id->equals(ike_sa_id, peer->ike_sa_id)) + { + iterator->remove(iterator); + + unregister_peer(this, peer->id); + + peer_destroy(peer); + break; + } + } + iterator->destroy(iterator); + + this->mutex->unlock(this->mutex); +} + +/** + * Implementation of mediation_manager_t.update_sa_id + */ +static void update_sa_id(private_mediation_manager_t *this, identification_t *peer_id, ike_sa_id_t *ike_sa_id) +{ + iterator_t *iterator; + peer_t *peer; + bool found = FALSE; + + this->mutex->lock(this->mutex); + + iterator = this->peers->create_iterator(this->peers, TRUE); + while (iterator->iterate(iterator, (void**)&peer)) + { + if (peer_id->equals(peer_id, peer->id)) + { + DESTROY_IF(peer->ike_sa_id); + found = TRUE; + break; + } + } + iterator->destroy(iterator); + + if (!found) + { + DBG2(DBG_IKE, "adding peer '%Y'", peer_id); + peer = peer_create(peer_id, NULL); + this->peers->insert_last(this->peers, peer); + } + + DBG2(DBG_IKE, "changing registered IKE_SA ID of peer '%Y'", peer_id); + peer->ike_sa_id = ike_sa_id ? ike_sa_id->clone(ike_sa_id) : NULL; + + /* send callbacks to registered peers */ + identification_t *requester; + while(peer->requested_by->remove_last(peer->requested_by, + (void**)&requester) == SUCCESS) + { + job_t *job = (job_t*)mediation_callback_job_create(requester, peer_id); + charon->processor->queue_job(charon->processor, job); + requester->destroy(requester); + } + + this->mutex->unlock(this->mutex); +} + +/** + * Implementation of mediation_manager_t.check. + */ +static ike_sa_id_t *check(private_mediation_manager_t *this, + identification_t *peer_id) +{ + peer_t *peer; + ike_sa_id_t *ike_sa_id; + + this->mutex->lock(this->mutex); + + if (get_peer_by_id(this, peer_id, &peer) != SUCCESS) + { + this->mutex->unlock(this->mutex); + return NULL; + } + + ike_sa_id = peer->ike_sa_id; + + this->mutex->unlock(this->mutex); + + return ike_sa_id; +} + +/** + * Implementation of mediation_manager_t.check_and_register. + */ +static ike_sa_id_t *check_and_register(private_mediation_manager_t *this, + identification_t *peer_id, identification_t *requester) +{ + peer_t *peer; + ike_sa_id_t *ike_sa_id; + + this->mutex->lock(this->mutex); + + if (get_peer_by_id(this, peer_id, &peer) != SUCCESS) + { + DBG2(DBG_IKE, "adding peer %Y", peer_id); + peer = peer_create(peer_id, NULL); + this->peers->insert_last(this->peers, peer); + } + + if (!peer->ike_sa_id) + { + /* the peer is not online */ + DBG2(DBG_IKE, "requested peer '%Y' is offline, registering peer '%Y'", + peer_id, requester); + register_peer(peer, requester); + this->mutex->unlock(this->mutex); + return NULL; + } + + ike_sa_id = peer->ike_sa_id; + + this->mutex->unlock(this->mutex); + + return ike_sa_id; +} + +/** + * Implementation of mediation_manager_t.destroy. + */ +static void destroy(private_mediation_manager_t *this) +{ + this->mutex->lock(this->mutex); + + this->peers->destroy_function(this->peers, (void*)peer_destroy); + + this->mutex->unlock(this->mutex); + this->mutex->destroy(this->mutex); + free(this); +} + +/* + * Described in header. + */ +mediation_manager_t *mediation_manager_create() +{ + private_mediation_manager_t *this = malloc_thing(private_mediation_manager_t); + + this->public.destroy = (void(*)(mediation_manager_t*))destroy; + this->public.remove = (void(*)(mediation_manager_t*,ike_sa_id_t*))remove_sa; + this->public.update_sa_id = (void(*)(mediation_manager_t*,identification_t*,ike_sa_id_t*))update_sa_id; + this->public.check = (ike_sa_id_t*(*)(mediation_manager_t*,identification_t*))check; + this->public.check_and_register = (ike_sa_id_t*(*)(mediation_manager_t*,identification_t*,identification_t*))check_and_register; + + this->peers = linked_list_create(); + this->mutex = mutex_create(MUTEX_TYPE_DEFAULT); + + return (mediation_manager_t*)this; +} diff --git a/src/libcharon/sa/mediation_manager.h b/src/libcharon/sa/mediation_manager.h new file mode 100644 index 000000000..31a16f69c --- /dev/null +++ b/src/libcharon/sa/mediation_manager.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2007 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 mediation_manager mediation_manager + * @{ @ingroup sa + */ + +#ifndef MEDIATION_MANAGER_H_ +#define MEDIATION_MANAGER_H_ + +typedef struct mediation_manager_t mediation_manager_t; + +#include <sa/ike_sa_id.h> +#include <utils/identification.h> + +/** + * The mediation manager is responsible for managing currently online + * peers and registered requests for offline peers on the mediation server. + */ +struct mediation_manager_t { + + /** + * Remove the IKE_SA of a peer. + * + * @param ike_sa_id the IKE_SA ID of the peer's SA + */ + void (*remove) (mediation_manager_t* this, ike_sa_id_t *ike_sa_id); + + /** + * Update the ike_sa_id that is assigned to a peer's ID. If the peer + * is new, it gets a new record assigned. + * + * @param peer_id the peer's ID + * @param ike_sa_id the IKE_SA ID of the peer's SA + */ + void (*update_sa_id) (mediation_manager_t* this, identification_t *peer_id, + ike_sa_id_t *ike_sa_id); + + /** + * Checks if a specific peer is online. + * + * @param peer_id the peer's ID + * @returns + * - IKE_SA ID of the peer's SA. + * - NULL, if the peer is not online. + */ + ike_sa_id_t* (*check) (mediation_manager_t* this, + identification_t *peer_id); + + /** + * Checks if a specific peer is online and registers the requesting + * peer if it is not. + * + * @param peer_id the peer's ID + * @param requester the requesters ID + * @returns + * - IKE_SA ID of the peer's SA. + * - NULL, if the peer is not online. + */ + ike_sa_id_t* (*check_and_register) (mediation_manager_t* this, + identification_t *peer_id, + identification_t *requester); + + /** + * Destroys the manager with all data. + */ + void (*destroy) (mediation_manager_t *this); +}; + +/** + * Create a manager. + * + * @returns mediation_manager_t object + */ +mediation_manager_t *mediation_manager_create(void); + +#endif /** MEDIATION_MANAGER_H_ @}*/ diff --git a/src/libcharon/sa/task_manager.c b/src/libcharon/sa/task_manager.c new file mode 100644 index 000000000..eeda6c860 --- /dev/null +++ b/src/libcharon/sa/task_manager.c @@ -0,0 +1,1083 @@ +/* + * Copyright (C) 2007 Tobias Brunner + * Copyright (C) 2007 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.h" + +#include <math.h> + +#include <daemon.h> +#include <sa/tasks/ike_init.h> +#include <sa/tasks/ike_natd.h> +#include <sa/tasks/ike_mobike.h> +#include <sa/tasks/ike_auth.h> +#include <sa/tasks/ike_auth_lifetime.h> +#include <sa/tasks/ike_cert_pre.h> +#include <sa/tasks/ike_cert_post.h> +#include <sa/tasks/ike_rekey.h> +#include <sa/tasks/ike_delete.h> +#include <sa/tasks/ike_config.h> +#include <sa/tasks/ike_dpd.h> +#include <sa/tasks/ike_vendor.h> +#include <sa/tasks/child_create.h> +#include <sa/tasks/child_rekey.h> +#include <sa/tasks/child_delete.h> +#include <encoding/payloads/delete_payload.h> +#include <processing/jobs/retransmit_job.h> + +#ifdef ME +#include <sa/tasks/ike_me.h> +#endif + +typedef struct exchange_t exchange_t; + +/** + * 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_t public; + + /** + * associated IKE_SA we are serving + */ + ike_sa_t *ike_sa; + + /** + * Exchange we are currently handling as responder + */ + struct { + /** + * Message ID of the exchange + */ + u_int32_t mid; + + /** + * packet for retransmission + */ + packet_t *packet; + + } responding; + + /** + * Exchange we are currently handling as initiator + */ + struct { + /** + * Message ID of the exchange + */ + u_int32_t mid; + + /** + * how many times we have retransmitted so far + */ + u_int retransmitted; + + /** + * packet for retransmission + */ + packet_t *packet; + + /** + * type of the initated exchange + */ + exchange_type_t type; + + } initiating; + + /** + * List of queued tasks not yet in action + */ + linked_list_t *queued_tasks; + + /** + * List of active tasks, initiated by ourselve + */ + linked_list_t *active_tasks; + + /** + * List of tasks initiated by peer + */ + linked_list_t *passive_tasks; + + /** + * the task manager has been reset + */ + bool reset; + + /** + * Number of times we retransmit messages before giving up + */ + u_int retransmit_tries; + + /** + * Retransmission timeout + */ + double retransmit_timeout; + + /** + * Base to calculate retransmission timeout + */ + double retransmit_base; +}; + +/** + * flush all tasks in the task manager + */ +static void flush(private_task_manager_t *this) +{ + this->queued_tasks->destroy_offset(this->queued_tasks, + offsetof(task_t, destroy)); + this->passive_tasks->destroy_offset(this->passive_tasks, + offsetof(task_t, destroy)); + this->active_tasks->destroy_offset(this->active_tasks, + offsetof(task_t, destroy)); + this->queued_tasks = linked_list_create(); + this->passive_tasks = linked_list_create(); + this->active_tasks = linked_list_create(); +} + +/** + * move a task of a specific type from the queue to the active list + */ +static bool activate_task(private_task_manager_t *this, task_type_t type) +{ + iterator_t *iterator; + task_t *task; + bool found = FALSE; + + iterator = this->queued_tasks->create_iterator(this->queued_tasks, TRUE); + while (iterator->iterate(iterator, (void**)&task)) + { + if (task->get_type(task) == type) + { + DBG2(DBG_IKE, " activating %N task", task_type_names, type); + iterator->remove(iterator); + this->active_tasks->insert_last(this->active_tasks, task); + found = TRUE; + break; + } + } + iterator->destroy(iterator); + return found; +} + +/** + * Implementation of task_manager_t.retransmit + */ +static status_t retransmit(private_task_manager_t *this, u_int32_t message_id) +{ + if (message_id == this->initiating.mid) + { + u_int32_t timeout; + job_t *job; + iterator_t *iterator; + packet_t *packet; + task_t *task; + ike_mobike_t *mobike = NULL; + + /* check if we are retransmitting a MOBIKE routability check */ + iterator = this->active_tasks->create_iterator(this->active_tasks, TRUE); + while (iterator->iterate(iterator, (void*)&task)) + { + if (task->get_type(task) == IKE_MOBIKE) + { + mobike = (ike_mobike_t*)task; + if (!mobike->is_probing(mobike)) + { + mobike = NULL; + } + break; + } + } + iterator->destroy(iterator); + + if (mobike == NULL) + { + if (this->initiating.retransmitted <= this->retransmit_tries) + { + timeout = (u_int32_t)(this->retransmit_timeout * 1000.0 * + pow(this->retransmit_base, this->initiating.retransmitted)); + } + else + { + DBG1(DBG_IKE, "giving up after %d retransmits", + this->initiating.retransmitted - 1); + if (this->ike_sa->get_state(this->ike_sa) != IKE_CONNECTING) + { + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + } + return DESTROY_ME; + } + + if (this->initiating.retransmitted) + { + DBG1(DBG_IKE, "retransmit %d of request with message ID %d", + this->initiating.retransmitted, message_id); + } + packet = this->initiating.packet->clone(this->initiating.packet); + charon->sender->send(charon->sender, packet); + } + else + { /* for routeability checks, we use a more aggressive behavior */ + if (this->initiating.retransmitted <= ROUTEABILITY_CHECK_TRIES) + { + timeout = ROUTEABILITY_CHECK_INTERVAL; + } + else + { + DBG1(DBG_IKE, "giving up after %d path probings", + this->initiating.retransmitted - 1); + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + return DESTROY_ME; + } + + if (this->initiating.retransmitted) + { + DBG1(DBG_IKE, "path probing attempt %d", + this->initiating.retransmitted); + } + mobike->transmit(mobike, this->initiating.packet); + } + + this->initiating.retransmitted++; + job = (job_t*)retransmit_job_create(this->initiating.mid, + this->ike_sa->get_id(this->ike_sa)); + charon->scheduler->schedule_job_ms(charon->scheduler, job, timeout); + } + return SUCCESS; +} + +/** + * build a request using the active task list + * Implementation of task_manager_t.initiate + */ +static status_t build_request(private_task_manager_t *this) +{ + iterator_t *iterator; + task_t *task; + message_t *message; + host_t *me, *other; + status_t status; + exchange_type_t exchange = 0; + + if (this->initiating.type != EXCHANGE_TYPE_UNDEFINED) + { + DBG2(DBG_IKE, "delaying task initiation, exchange in progress"); + /* do not initiate if we already have a message in the air */ + return SUCCESS; + } + + if (this->active_tasks->get_count(this->active_tasks) == 0) + { + DBG2(DBG_IKE, "activating new tasks"); + switch (this->ike_sa->get_state(this->ike_sa)) + { + case IKE_CREATED: + activate_task(this, IKE_VENDOR); + if (activate_task(this, IKE_INIT)) + { + this->initiating.mid = 0; + exchange = IKE_SA_INIT; + activate_task(this, IKE_NATD); + activate_task(this, IKE_CERT_PRE); +#ifdef ME + /* this task has to be activated before the IKE_AUTHENTICATE + * task, because that task pregenerates the packet after + * which no payloads can be added to the message anymore. + */ + activate_task(this, IKE_ME); +#endif /* ME */ + activate_task(this, IKE_AUTHENTICATE); + activate_task(this, IKE_CERT_POST); + activate_task(this, IKE_CONFIG); + activate_task(this, CHILD_CREATE); + activate_task(this, IKE_AUTH_LIFETIME); + activate_task(this, IKE_MOBIKE); + } + break; + case IKE_ESTABLISHED: + if (activate_task(this, CHILD_CREATE)) + { + exchange = CREATE_CHILD_SA; + break; + } + if (activate_task(this, CHILD_DELETE)) + { + exchange = INFORMATIONAL; + break; + } + if (activate_task(this, CHILD_REKEY)) + { + exchange = CREATE_CHILD_SA; + break; + } + if (activate_task(this, IKE_DELETE)) + { + exchange = INFORMATIONAL; + break; + } + if (activate_task(this, IKE_REKEY)) + { + exchange = CREATE_CHILD_SA; + break; + } + if (activate_task(this, IKE_REAUTH)) + { + exchange = INFORMATIONAL; + break; + } + if (activate_task(this, IKE_MOBIKE)) + { + exchange = INFORMATIONAL; + break; + } + if (activate_task(this, IKE_DPD)) + { + exchange = INFORMATIONAL; + break; + } +#ifdef ME + if (activate_task(this, IKE_ME)) + { + exchange = ME_CONNECT; + break; + } +#endif /* ME */ + case IKE_REKEYING: + if (activate_task(this, IKE_DELETE)) + { + exchange = INFORMATIONAL; + break; + } + case IKE_DELETING: + default: + break; + } + } + else + { + DBG2(DBG_IKE, "reinitiating already active tasks"); + iterator = this->active_tasks->create_iterator(this->active_tasks, TRUE); + while (iterator->iterate(iterator, (void**)&task)) + { + DBG2(DBG_IKE, " %N task", task_type_names, task->get_type(task)); + switch (task->get_type(task)) + { + case IKE_INIT: + exchange = IKE_SA_INIT; + break; + case IKE_AUTHENTICATE: + exchange = IKE_AUTH; + break; + case CHILD_CREATE: + case CHILD_REKEY: + case IKE_REKEY: + exchange = CREATE_CHILD_SA; + break; + case IKE_MOBIKE: + exchange = INFORMATIONAL; + default: + continue; + } + break; + } + iterator->destroy(iterator); + } + + if (exchange == 0) + { + DBG2(DBG_IKE, "nothing to initiate"); + /* nothing to do yet... */ + return SUCCESS; + } + + me = this->ike_sa->get_my_host(this->ike_sa); + other = this->ike_sa->get_other_host(this->ike_sa); + + message = message_create(); + message->set_message_id(message, this->initiating.mid); + message->set_source(message, me->clone(me)); + message->set_destination(message, other->clone(other)); + message->set_exchange_type(message, exchange); + this->initiating.type = exchange; + this->initiating.retransmitted = 0; + + iterator = this->active_tasks->create_iterator(this->active_tasks, TRUE); + while (iterator->iterate(iterator, (void*)&task)) + { + switch (task->build(task, message)) + { + case SUCCESS: + /* task completed, remove it */ + iterator->remove(iterator); + task->destroy(task); + break; + case NEED_MORE: + /* processed, but task needs another exchange */ + break; + case FAILED: + default: + if (this->ike_sa->get_state(this->ike_sa) != IKE_CONNECTING) + { + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + } + /* FALL */ + case DESTROY_ME: + /* critical failure, destroy IKE_SA */ + iterator->destroy(iterator); + message->destroy(message); + flush(this); + return DESTROY_ME; + } + } + iterator->destroy(iterator); + + /* update exchange type if a task changed it */ + this->initiating.type = message->get_exchange_type(message); + + charon->bus->message(charon->bus, message, FALSE); + status = this->ike_sa->generate_message(this->ike_sa, message, + &this->initiating.packet); + if (status != SUCCESS) + { + /* message generation failed. There is nothing more to do than to + * close the SA */ + message->destroy(message); + flush(this); + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + return DESTROY_ME; + } + message->destroy(message); + + return retransmit(this, this->initiating.mid); +} + +/** + * handle an incoming response message + */ +static status_t process_response(private_task_manager_t *this, + message_t *message) +{ + iterator_t *iterator; + task_t *task; + + if (message->get_exchange_type(message) != this->initiating.type) + { + DBG1(DBG_IKE, "received %N response, but expected %N", + exchange_type_names, message->get_exchange_type(message), + exchange_type_names, this->initiating.type); + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + return DESTROY_ME; + } + + /* catch if we get resetted while processing */ + this->reset = FALSE; + iterator = this->active_tasks->create_iterator(this->active_tasks, TRUE); + while (iterator->iterate(iterator, (void*)&task)) + { + switch (task->process(task, message)) + { + case SUCCESS: + /* task completed, remove it */ + iterator->remove(iterator); + task->destroy(task); + break; + case NEED_MORE: + /* processed, but task needs another exchange */ + break; + case FAILED: + default: + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + /* FALL */ + case DESTROY_ME: + /* critical failure, destroy IKE_SA */ + iterator->remove(iterator); + iterator->destroy(iterator); + task->destroy(task); + return DESTROY_ME; + } + if (this->reset) + { /* start all over again if we were reset */ + this->reset = FALSE; + iterator->destroy(iterator); + return build_request(this); + } + } + iterator->destroy(iterator); + + this->initiating.mid++; + this->initiating.type = EXCHANGE_TYPE_UNDEFINED; + this->initiating.packet->destroy(this->initiating.packet); + this->initiating.packet = NULL; + + return build_request(this); +} + +/** + * handle exchange collisions + */ +static void handle_collisions(private_task_manager_t *this, task_t *task) +{ + iterator_t *iterator; + task_t *active; + task_type_t type; + + type = task->get_type(task); + + /* do we have to check */ + if (type == IKE_REKEY || type == CHILD_REKEY || + type == CHILD_DELETE || type == IKE_DELETE || type == IKE_REAUTH) + { + /* find an exchange collision, and notify these tasks */ + iterator = this->active_tasks->create_iterator(this->active_tasks, TRUE); + while (iterator->iterate(iterator, (void**)&active)) + { + switch (active->get_type(active)) + { + case IKE_REKEY: + if (type == IKE_REKEY || type == IKE_DELETE || + type == IKE_REAUTH) + { + ike_rekey_t *rekey = (ike_rekey_t*)active; + rekey->collide(rekey, task); + break; + } + continue; + case CHILD_REKEY: + if (type == CHILD_REKEY || type == CHILD_DELETE) + { + child_rekey_t *rekey = (child_rekey_t*)active; + rekey->collide(rekey, task); + break; + } + continue; + default: + continue; + } + iterator->destroy(iterator); + return; + } + iterator->destroy(iterator); + } + /* destroy task if not registered in any active task */ + task->destroy(task); +} + +/** + * build a response depending on the "passive" task list + */ +static status_t build_response(private_task_manager_t *this, message_t *request) +{ + iterator_t *iterator; + task_t *task; + message_t *message; + host_t *me, *other; + bool delete = FALSE; + status_t status; + + me = request->get_destination(request); + other = request->get_source(request); + + message = message_create(); + message->set_exchange_type(message, request->get_exchange_type(request)); + /* send response along the path the request came in */ + message->set_source(message, me->clone(me)); + message->set_destination(message, other->clone(other)); + message->set_message_id(message, this->responding.mid); + message->set_request(message, FALSE); + + iterator = this->passive_tasks->create_iterator(this->passive_tasks, TRUE); + while (iterator->iterate(iterator, (void*)&task)) + { + switch (task->build(task, message)) + { + case SUCCESS: + /* task completed, remove it */ + iterator->remove(iterator); + handle_collisions(this, task); + case NEED_MORE: + /* processed, but task needs another exchange */ + break; + case FAILED: + default: + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + /* FALL */ + case DESTROY_ME: + /* destroy IKE_SA, but SEND response first */ + delete = TRUE; + break; + } + if (delete) + { + break; + } + } + iterator->destroy(iterator); + + /* remove resonder SPI if IKE_SA_INIT failed */ + if (delete && request->get_exchange_type(request) == IKE_SA_INIT) + { + ike_sa_id_t *id = this->ike_sa->get_id(this->ike_sa); + id->set_responder_spi(id, 0); + } + + /* message complete, send it */ + DESTROY_IF(this->responding.packet); + this->responding.packet = NULL; + charon->bus->message(charon->bus, message, FALSE); + 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; +} + +/** + * handle an incoming request message + */ +static status_t process_request(private_task_manager_t *this, + message_t *message) +{ + enumerator_t *enumerator; + iterator_t *iterator; + task_t *task = NULL; + payload_t *payload; + notify_payload_t *notify; + delete_payload_t *delete; + + if (this->passive_tasks->get_count(this->passive_tasks) == 0) + { /* create tasks depending on request type, if not already some queued */ + switch (message->get_exchange_type(message)) + { + case IKE_SA_INIT: + { + task = (task_t*)ike_vendor_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t*)ike_init_create(this->ike_sa, FALSE, NULL); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t*)ike_natd_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t*)ike_cert_pre_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); +#ifdef ME + task = (task_t*)ike_me_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); +#endif /* ME */ + task = (task_t*)ike_auth_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t*)ike_cert_post_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t*)ike_config_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t*)child_create_create(this->ike_sa, NULL, FALSE, + NULL, NULL); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t*)ike_auth_lifetime_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t*)ike_mobike_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + break; + } + case CREATE_CHILD_SA: + { /* FIXME: we should prevent this on mediation connections */ + bool notify_found = FALSE, ts_found = FALSE; + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + switch (payload->get_type(payload)) + { + case NOTIFY: + { /* if we find a rekey notify, its CHILD_SA rekeying */ + notify = (notify_payload_t*)payload; + if (notify->get_notify_type(notify) == REKEY_SA && + (notify->get_protocol_id(notify) == PROTO_AH || + notify->get_protocol_id(notify) == PROTO_ESP)) + { + notify_found = TRUE; + } + break; + } + case TRAFFIC_SELECTOR_INITIATOR: + case TRAFFIC_SELECTOR_RESPONDER: + { /* if we don't find a TS, its IKE rekeying */ + ts_found = TRUE; + break; + } + default: + break; + } + } + enumerator->destroy(enumerator); + + if (ts_found) + { + if (notify_found) + { + task = (task_t*)child_rekey_create(this->ike_sa, + PROTO_NONE, 0); + } + else + { + task = (task_t*)child_create_create(this->ike_sa, NULL, + FALSE, NULL, NULL); + } + } + else + { + task = (task_t*)ike_rekey_create(this->ike_sa, FALSE); + } + this->passive_tasks->insert_last(this->passive_tasks, task); + break; + } + case INFORMATIONAL: + { + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + switch (payload->get_type(payload)) + { + case NOTIFY: + { + notify = (notify_payload_t*)payload; + switch (notify->get_notify_type(notify)) + { + case ADDITIONAL_IP4_ADDRESS: + case ADDITIONAL_IP6_ADDRESS: + case NO_ADDITIONAL_ADDRESSES: + case UPDATE_SA_ADDRESSES: + case NO_NATS_ALLOWED: + case UNACCEPTABLE_ADDRESSES: + case UNEXPECTED_NAT_DETECTED: + case COOKIE2: + case NAT_DETECTION_SOURCE_IP: + case NAT_DETECTION_DESTINATION_IP: + task = (task_t*)ike_mobike_create( + this->ike_sa, FALSE); + break; + case AUTH_LIFETIME: + task = (task_t*)ike_auth_lifetime_create( + this->ike_sa, FALSE); + break; + default: + break; + } + break; + } + case DELETE: + { + delete = (delete_payload_t*)payload; + if (delete->get_protocol_id(delete) == PROTO_IKE) + { + task = (task_t*)ike_delete_create(this->ike_sa, + FALSE); + } + else + { + task = (task_t*)child_delete_create(this->ike_sa, + PROTO_NONE, 0); + } + break; + } + default: + break; + } + if (task) + { + break; + } + } + enumerator->destroy(enumerator); + + if (task == NULL) + { + task = (task_t*)ike_dpd_create(FALSE); + } + this->passive_tasks->insert_last(this->passive_tasks, task); + break; + } +#ifdef ME + case ME_CONNECT: + { + task = (task_t*)ike_me_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + } +#endif /* ME */ + default: + break; + } + } + + /* let the tasks process the message */ + iterator = this->passive_tasks->create_iterator(this->passive_tasks, TRUE); + while (iterator->iterate(iterator, (void*)&task)) + { + switch (task->process(task, message)) + { + case SUCCESS: + /* task completed, remove it */ + iterator->remove(iterator); + task->destroy(task); + break; + case NEED_MORE: + /* processed, but task needs at least another call to build() */ + break; + case FAILED: + default: + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + /* FALL */ + case DESTROY_ME: + /* critical failure, destroy IKE_SA */ + iterator->remove(iterator); + iterator->destroy(iterator); + task->destroy(task); + return DESTROY_ME; + } + } + iterator->destroy(iterator); + + return build_response(this, message); +} + +/** + * Implementation of task_manager_t.process_message + */ +static status_t process_message(private_task_manager_t *this, message_t *msg) +{ + u_int32_t mid = msg->get_message_id(msg); + + if (msg->get_request(msg)) + { + if (mid == this->responding.mid) + { + charon->bus->message(charon->bus, msg, TRUE); + if (process_request(this, msg) != SUCCESS) + { + flush(this); + return DESTROY_ME; + } + this->responding.mid++; + } + else if ((mid == this->responding.mid - 1) && this->responding.packet) + { + packet_t *clone; + host_t *me, *other; + + DBG1(DBG_IKE, "received retransmit of request with ID %d, " + "retransmitting response", mid); + clone = this->responding.packet->clone(this->responding.packet); + me = msg->get_destination(msg); + other = msg->get_source(msg); + clone->set_source(clone, me->clone(me)); + clone->set_destination(clone, other->clone(other)); + charon->sender->send(charon->sender, clone); + } + else + { + DBG1(DBG_IKE, "received message ID %d, expected %d. Ignored", + mid, this->responding.mid); + } + } + else + { + if (mid == this->initiating.mid) + { + charon->bus->message(charon->bus, msg, TRUE); + if (process_response(this, msg) != SUCCESS) + { + flush(this); + return DESTROY_ME; + } + } + else + { + DBG1(DBG_IKE, "received message ID %d, expected %d. Ignored", + mid, this->initiating.mid); + return SUCCESS; + } + } + return SUCCESS; +} + +/** + * Implementation of task_manager_t.queue_task + */ +static void queue_task(private_task_manager_t *this, task_t *task) +{ + if (task->get_type(task) == IKE_MOBIKE) + { /* there is no need to queue more than one mobike task */ + iterator_t *iterator; + task_t *current; + + iterator = this->queued_tasks->create_iterator(this->queued_tasks, TRUE); + while (iterator->iterate(iterator, (void**)¤t)) + { + if (current->get_type(current) == IKE_MOBIKE) + { + iterator->destroy(iterator); + task->destroy(task); + return; + } + } + iterator->destroy(iterator); + } + DBG2(DBG_IKE, "queueing %N task", task_type_names, task->get_type(task)); + this->queued_tasks->insert_last(this->queued_tasks, task); +} + +/** + * Implementation of task_manager_t.adopt_tasks + */ +static void adopt_tasks(private_task_manager_t *this, private_task_manager_t *other) +{ + 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); + } +} + +/** + * Implementation of task_manager_t.busy + */ +static bool busy(private_task_manager_t *this) +{ + return (this->active_tasks->get_count(this->active_tasks) > 0); +} + +/** + * Implementation of task_manager_t.reset + */ +static void reset(private_task_manager_t *this, + u_int32_t initiate, u_int32_t respond) +{ + task_t *task; + + /* reset message counters and retransmit packets */ + DESTROY_IF(this->responding.packet); + DESTROY_IF(this->initiating.packet); + this->responding.packet = NULL; + this->initiating.packet = NULL; + if (initiate != UINT_MAX) + { + this->initiating.mid = initiate; + } + if (respond != UINT_MAX) + { + this->responding.mid = respond; + } + this->initiating.type = EXCHANGE_TYPE_UNDEFINED; + + /* reset active tasks */ + while (this->active_tasks->remove_last(this->active_tasks, + (void**)&task) == SUCCESS) + { + task->migrate(task, this->ike_sa); + this->queued_tasks->insert_first(this->queued_tasks, task); + } + + this->reset = TRUE; +} + +/** + * Implementation of task_manager_t.destroy + */ +static void destroy(private_task_manager_t *this) +{ + flush(this); + + this->active_tasks->destroy(this->active_tasks); + this->queued_tasks->destroy(this->queued_tasks); + this->passive_tasks->destroy(this->passive_tasks); + + DESTROY_IF(this->responding.packet); + DESTROY_IF(this->initiating.packet); + free(this); +} + +/* + * see header file + */ +task_manager_t *task_manager_create(ike_sa_t *ike_sa) +{ + private_task_manager_t *this = malloc_thing(private_task_manager_t); + + this->public.process_message = (status_t(*)(task_manager_t*,message_t*))process_message; + this->public.queue_task = (void(*)(task_manager_t*,task_t*))queue_task; + this->public.initiate = (status_t(*)(task_manager_t*))build_request; + this->public.retransmit = (status_t(*)(task_manager_t*,u_int32_t))retransmit; + this->public.reset = (void(*)(task_manager_t*,u_int32_t,u_int32_t))reset; + this->public.adopt_tasks = (void(*)(task_manager_t*,task_manager_t*))adopt_tasks; + this->public.busy = (bool(*)(task_manager_t*))busy; + this->public.destroy = (void(*)(task_manager_t*))destroy; + + this->ike_sa = ike_sa; + this->responding.packet = NULL; + this->initiating.packet = NULL; + this->responding.mid = 0; + this->initiating.mid = 0; + this->initiating.type = EXCHANGE_TYPE_UNDEFINED; + this->queued_tasks = linked_list_create(); + this->active_tasks = linked_list_create(); + this->passive_tasks = linked_list_create(); + this->reset = FALSE; + + this->retransmit_tries = lib->settings->get_int(lib->settings, + "charon.retransmit_tries", RETRANSMIT_TRIES); + this->retransmit_timeout = lib->settings->get_double(lib->settings, + "charon.retransmit_timeout", RETRANSMIT_TIMEOUT); + this->retransmit_base = lib->settings->get_double(lib->settings, + "charon.retransmit_base", RETRANSMIT_BASE); + + return &this->public; +} + diff --git a/src/libcharon/sa/task_manager.h b/src/libcharon/sa/task_manager.h new file mode 100644 index 000000000..731ed4898 --- /dev/null +++ b/src/libcharon/sa/task_manager.h @@ -0,0 +1,173 @@ +/* + * 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 task_manager task_manager + * @{ @ingroup sa + */ + +#ifndef TASK_MANAGER_H_ +#define TASK_MANAGER_H_ + +typedef struct task_manager_t task_manager_t; + +#include <limits.h> + +#include <library.h> +#include <encoding/message.h> +#include <sa/ike_sa.h> +#include <sa/tasks/task.h> + +/** + * First retransmit timeout in seconds. + */ +#define RETRANSMIT_TIMEOUT 4.0 + +/** + * Base which is raised to the power of the retransmission try. + */ +#define RETRANSMIT_BASE 1.8 + +/** + * Number of retransmits done before giving up. + */ +#define RETRANSMIT_TRIES 5 + +/** + * Interval for mobike routability checks in ms. + */ +#define ROUTEABILITY_CHECK_INTERVAL 2500 + +/** + * Number of routability checks before giving up + */ +#define ROUTEABILITY_CHECK_TRIES 10 + + +/** + * The task manager, juggles task and handles message exchanges. + * + * On incoming requests, the task manager creates new tasks on demand and + * juggles the request through all available tasks. Each task inspects the + * request and adds payloads as necessary to the response. + * On outgoing requests, the task manager delivers the request through the tasks + * to build it, the response gets processed by each task to complete. + * The task manager has an internal Queue to store task which should get + * completed. + * For the initial IKE_SA setup, several tasks are queued: One for the + * unauthenticated IKE_SA setup, one for authentication, one for CHILD_SA setup + * and maybe one for virtual IP assignement. + * The task manager is also responsible for retransmission. It uses a backoff + * algorithm. The timeout is calculated using + * RETRANSMIT_TIMEOUT * (RETRANSMIT_BASE ** try). + * When try reaches RETRANSMIT_TRIES, retransmission is given up. + * + * Using an initial TIMEOUT of 4s, a BASE of 1.8, and 5 TRIES gives us: + * @verbatim + | relative | absolute + --------------------------------------------------------- + 4s * (1.8 ** 0) = 4s 4s + 4s * (1.8 ** 1) = 7s 11s + 4s * (1.8 ** 2) = 13s 24s + 4s * (1.8 ** 3) = 23s 47s + 4s * (1.8 ** 4) = 42s 89s + 4s * (1.8 ** 5) = 76s 165s + + @endverbatim + * The peer is considered dead after 2min 45s when no reply comes in. + */ +struct task_manager_t { + + /** + * Process an incoming message. + * + * @param message message to add payloads to + * @return + * - DESTROY_ME if IKE_SA must be closed + * - SUCCESS otherwise + */ + status_t (*process_message) (task_manager_t *this, message_t *message); + + /** + * Initiate an exchange with the currently queued tasks. + */ + status_t (*initiate) (task_manager_t *this); + + /** + * Queue a task in the manager. + * + * @param task task to queue + */ + void (*queue_task) (task_manager_t *this, task_t *task); + + /** + * Retransmit a request if it hasn't been acknowledged yet. + * + * A return value of INVALID_STATE means that the message was already + * acknowledged and has not to be retransmitted. A return value of SUCCESS + * means retransmission was required and the message has been resent. + * + * @param message_id ID of the message to retransmit + * @return + * - INVALID_STATE if retransmission not required + * - SUCCESS if retransmission sent + */ + status_t (*retransmit) (task_manager_t *this, u_int32_t message_id); + + /** + * Migrate all tasks from other to this. + * + * To rekey or reestablish an IKE_SA completely, all queued or active + * tasks should get migrated to the new IKE_SA. + * + * @param other manager which gives away its tasks + */ + void (*adopt_tasks) (task_manager_t *this, task_manager_t *other); + + /** + * Reset message ID counters of the task manager. + * + * The IKEv2 protocol requires to restart exchanges with message IDs + * reset to zero (INVALID_KE_PAYLOAD, COOKIES, ...). The reset() method + * resets the message IDs and resets all active tasks using the migrate() + * method. + * Use a value of UINT_MAX to keep the current message ID. + * + * @param initiate message ID to initiate exchanges (send) + * @param respond message ID to respond to exchanges (expect) + */ + void (*reset) (task_manager_t *this, u_int32_t initiate, u_int32_t respond); + + /** + * Check if we are currently waiting for a reply. + * + * @return TRUE if we are waiting, FALSE otherwise + */ + bool (*busy) (task_manager_t *this); + + /** + * Destroy the task_manager_t. + */ + void (*destroy) (task_manager_t *this); +}; + +/** + * Create an instance of the task manager. + * + * @param ike_sa IKE_SA to manage. + */ +task_manager_t *task_manager_create(ike_sa_t *ike_sa); + +#endif /** TASK_MANAGER_H_ @}*/ diff --git a/src/libcharon/sa/tasks/child_create.c b/src/libcharon/sa/tasks/child_create.c new file mode 100644 index 000000000..bea4f73d5 --- /dev/null +++ b/src/libcharon/sa/tasks/child_create.c @@ -0,0 +1,1351 @@ +/* + * Copyright (C) 2008 Tobias Brunner + * Copyright (C) 2005-2008 Martin Willi + * Copyright (C) 2005 Jan Hutter + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "child_create.h" + +#include <daemon.h> +#include <crypto/diffie_hellman.h> +#include <credentials/certificates/x509.h> +#include <encoding/payloads/sa_payload.h> +#include <encoding/payloads/ke_payload.h> +#include <encoding/payloads/ts_payload.h> +#include <encoding/payloads/nonce_payload.h> +#include <encoding/payloads/notify_payload.h> +#include <processing/jobs/delete_ike_sa_job.h> +#include <processing/jobs/inactivity_job.h> + + +typedef struct private_child_create_t private_child_create_t; + +/** + * Private members of a child_create_t task. + */ +struct private_child_create_t { + + /** + * Public methods and task_t interface. + */ + child_create_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; + + /** + * Are we the initiator? + */ + bool initiator; + + /** + * nonce chosen by us + */ + chunk_t my_nonce; + + /** + * nonce chosen by peer + */ + chunk_t other_nonce; + + /** + * config to create the CHILD_SA from + */ + child_cfg_t *config; + + /** + * list of proposal candidates + */ + linked_list_t *proposals; + + /** + * selected proposal to use for CHILD_SA + */ + proposal_t *proposal; + + /** + * traffic selectors for initiators side + */ + linked_list_t *tsi; + + /** + * traffic selectors for responders side + */ + linked_list_t *tsr; + + /** + * source of triggering packet + */ + traffic_selector_t *packet_tsi; + + /** + * destination of triggering packet + */ + traffic_selector_t *packet_tsr; + + /** + * optional diffie hellman exchange + */ + diffie_hellman_t *dh; + + /** + * group used for DH exchange + */ + diffie_hellman_group_t dh_group; + + /** + * IKE_SAs keymat + */ + keymat_t *keymat; + + /** + * mode the new CHILD_SA uses (transport/tunnel/beet) + */ + ipsec_mode_t mode; + + /** + * IPComp transform to use + */ + ipcomp_transform_t ipcomp; + + /** + * IPComp transform proposed or accepted by the other peer + */ + ipcomp_transform_t ipcomp_received; + + /** + * Own allocated SPI + */ + u_int32_t my_spi; + + /** + * SPI received in proposal + */ + u_int32_t other_spi; + + /** + * Own allocated Compression Parameter Index (CPI) + */ + u_int16_t my_cpi; + + /** + * Other Compression Parameter Index (CPI), received via IPCOMP_SUPPORTED + */ + u_int16_t other_cpi; + + /** + * reqid to use if we are rekeying + */ + u_int32_t reqid; + + /** + * CHILD_SA which gets established + */ + child_sa_t *child_sa; + + /** + * successfully established the CHILD? + */ + bool established; + + /** + * whether the CHILD_SA rekeys an existing one + */ + bool rekey; +}; + +/** + * get the nonce from a message + */ +static status_t get_nonce(message_t *message, chunk_t *nonce) +{ + nonce_payload_t *payload; + + payload = (nonce_payload_t*)message->get_payload(message, NONCE); + if (payload == NULL) + { + return FAILED; + } + *nonce = payload->get_nonce(payload); + return NEED_MORE; +} + +/** + * generate a new nonce to include in a CREATE_CHILD_SA message + */ +static status_t generate_nonce(chunk_t *nonce) +{ + rng_t *rng; + + rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK); + if (!rng) + { + DBG1(DBG_IKE, "error generating nonce value, no RNG found"); + return FAILED; + } + rng->allocate_bytes(rng, NONCE_SIZE, nonce); + rng->destroy(rng); + return SUCCESS; +} + +/** + * Check a list of traffic selectors if any selector belongs to host + */ +static bool ts_list_is_host(linked_list_t *list, host_t *host) +{ + traffic_selector_t *ts; + bool is_host = TRUE; + iterator_t *iterator = list->create_iterator(list, TRUE); + + while (is_host && iterator->iterate(iterator, (void**)&ts)) + { + is_host = is_host && ts->is_host(ts, host); + } + iterator->destroy(iterator); + return is_host; +} + +/** + * Allocate SPIs and update proposals + */ +static bool allocate_spi(private_child_create_t *this) +{ + enumerator_t *enumerator; + proposal_t *proposal; + + /* TODO: allocate additional SPI for AH if we have such proposals */ + this->my_spi = this->child_sa->alloc_spi(this->child_sa, PROTO_ESP); + if (this->my_spi) + { + if (this->initiator) + { + enumerator = this->proposals->create_enumerator(this->proposals); + while (enumerator->enumerate(enumerator, &proposal)) + { + proposal->set_spi(proposal, this->my_spi); + } + enumerator->destroy(enumerator); + } + else + { + this->proposal->set_spi(this->proposal, this->my_spi); + } + return TRUE; + } + return FALSE; +} + +/** + * Schedule inactivity timeout for CHILD_SA with reqid, if enabled + */ +static void schedule_inactivity_timeout(private_child_create_t *this) +{ + u_int32_t timeout; + bool close_ike; + + timeout = this->config->get_inactivity(this->config); + if (timeout) + { + close_ike = lib->settings->get_bool(lib->settings, + "charon.inactivity_close_ike", FALSE); + charon->scheduler->schedule_job(charon->scheduler, (job_t*) + inactivity_job_create(this->child_sa->get_reqid(this->child_sa), + timeout, close_ike), timeout); + } +} + +/** + * Install a CHILD_SA for usage, return value: + * - FAILED: no acceptable proposal + * - INVALID_ARG: diffie hellman group inacceptable + * - NOT_FOUND: TS inacceptable + */ +static status_t select_and_install(private_child_create_t *this, bool no_dh) +{ + status_t status, status_i, status_o; + chunk_t nonce_i, nonce_r; + chunk_t encr_i = chunk_empty, encr_r = chunk_empty; + chunk_t integ_i = chunk_empty, integ_r = chunk_empty; + linked_list_t *my_ts, *other_ts; + host_t *me, *other, *other_vip, *my_vip; + bool private; + + if (this->proposals == NULL) + { + DBG1(DBG_IKE, "SA payload missing in message"); + return FAILED; + } + if (this->tsi == NULL || this->tsr == NULL) + { + DBG1(DBG_IKE, "TS payloads missing in message"); + return NOT_FOUND; + } + + me = this->ike_sa->get_my_host(this->ike_sa); + other = this->ike_sa->get_other_host(this->ike_sa); + my_vip = this->ike_sa->get_virtual_ip(this->ike_sa, TRUE); + other_vip = this->ike_sa->get_virtual_ip(this->ike_sa, FALSE); + + private = this->ike_sa->supports_extension(this->ike_sa, EXT_STRONGSWAN); + this->proposal = this->config->select_proposal(this->config, + this->proposals, no_dh, private); + if (this->proposal == NULL) + { + DBG1(DBG_IKE, "no acceptable proposal found"); + return FAILED; + } + this->other_spi = this->proposal->get_spi(this->proposal); + + if (!this->initiator && !allocate_spi(this)) + { /* responder has no SPI allocated yet */ + DBG1(DBG_IKE, "allocating SPI failed"); + return FAILED; + } + this->child_sa->set_proposal(this->child_sa, this->proposal); + + if (!this->proposal->has_dh_group(this->proposal, this->dh_group)) + { + u_int16_t group; + + if (this->proposal->get_algorithm(this->proposal, DIFFIE_HELLMAN_GROUP, + &group, NULL)) + { + DBG1(DBG_IKE, "DH group %N inacceptable, requesting %N", + diffie_hellman_group_names, this->dh_group, + diffie_hellman_group_names, group); + this->dh_group = group; + return INVALID_ARG; + } + /* the selected proposal does not use a DH group */ + DBG1(DBG_IKE, "ignoring KE exchange, agreed on a non-PFS proposal"); + DESTROY_IF(this->dh); + this->dh = NULL; + this->dh_group = MODP_NONE; + } + + if (my_vip == NULL) + { + my_vip = me; + } + if (other_vip == NULL) + { + other_vip = other; + } + + if (this->initiator) + { + nonce_i = this->my_nonce; + nonce_r = this->other_nonce; + my_ts = this->tsi; + other_ts = this->tsr; + } + else + { + nonce_r = this->my_nonce; + nonce_i = this->other_nonce; + my_ts = this->tsr; + other_ts = this->tsi; + } + my_ts = this->config->get_traffic_selectors(this->config, TRUE, my_ts, + my_vip); + other_ts = this->config->get_traffic_selectors(this->config, FALSE, other_ts, + other_vip); + + if (my_ts->get_count(my_ts) == 0 || other_ts->get_count(other_ts) == 0) + { + my_ts->destroy_offset(my_ts, offsetof(traffic_selector_t, destroy)); + other_ts->destroy_offset(other_ts, offsetof(traffic_selector_t, destroy)); + DBG1(DBG_IKE, "no acceptable traffic selectors found"); + return NOT_FOUND; + } + + this->tsr->destroy_offset(this->tsr, offsetof(traffic_selector_t, destroy)); + this->tsi->destroy_offset(this->tsi, offsetof(traffic_selector_t, destroy)); + if (this->initiator) + { + this->tsi = my_ts; + this->tsr = other_ts; + } + else + { + this->tsr = my_ts; + this->tsi = other_ts; + } + + if (!this->initiator) + { + /* check if requested mode is acceptable, downgrade if required */ + switch (this->mode) + { + case MODE_TRANSPORT: + if (!this->config->use_proxy_mode(this->config) && + (!ts_list_is_host(this->tsi, other) || + !ts_list_is_host(this->tsr, me)) + ) + { + this->mode = MODE_TUNNEL; + DBG1(DBG_IKE, "not using transport mode, not host-to-host"); + } + else if (this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY)) + { + this->mode = MODE_TUNNEL; + DBG1(DBG_IKE, "not using transport mode, connection NATed"); + } + break; + case MODE_BEET: + if (!ts_list_is_host(this->tsi, NULL) || + !ts_list_is_host(this->tsr, NULL)) + { + this->mode = MODE_TUNNEL; + DBG1(DBG_IKE, "not using BEET mode, not host-to-host"); + } + break; + default: + break; + } + } + + /* check for any certificate-based IP address block constraints */ + if (this->mode == MODE_BEET || this->mode == MODE_TUNNEL) + { + auth_cfg_t *auth; + enumerator_t *auth_enum; + certificate_t *cert = NULL; + + auth_enum = this->ike_sa->create_auth_cfg_enumerator(this->ike_sa, FALSE); + while (auth_enum->enumerate(auth_enum, &auth)) + { + cert = auth->get(auth, AUTH_HELPER_SUBJECT_CERT); + if (cert) + { + break; + } + } + auth_enum->destroy(auth_enum); + + if (cert && cert->get_type(cert) == CERT_X509) + { + x509_t *x509 = (x509_t*)cert; + + if (x509->get_flags(x509) & X509_IP_ADDR_BLOCKS) + { + enumerator_t *enumerator, *block_enum; + traffic_selector_t *ts, *block_ts; + + DBG1(DBG_IKE, "checking certificate-based traffic selector " + "constraints [RFC 3779]"); + enumerator = other_ts->create_enumerator(other_ts); + while (enumerator->enumerate(enumerator, &ts)) + { + bool contained = FALSE; + + block_enum = x509->create_ipAddrBlock_enumerator(x509); + while (block_enum->enumerate(block_enum, &block_ts)) + { + if (ts->is_contained_in(ts, block_ts)) + { + DBG1(DBG_IKE, " TS %R is contained in address block" + " constraint %R", ts, block_ts); + contained = TRUE; + break; + } + } + block_enum->destroy(block_enum); + + if (!contained) + { + DBG1(DBG_IKE, " TS %R is not contained in any" + " address block constraint", ts); + enumerator->destroy(enumerator); + return FAILED; + } + } + enumerator->destroy(enumerator); + } + } + } + + this->child_sa->set_state(this->child_sa, CHILD_INSTALLING); + this->child_sa->set_ipcomp(this->child_sa, this->ipcomp); + this->child_sa->set_mode(this->child_sa, this->mode); + this->child_sa->set_protocol(this->child_sa, + this->proposal->get_protocol(this->proposal)); + + if (this->my_cpi == 0 || this->other_cpi == 0 || this->ipcomp == IPCOMP_NONE) + { + this->my_cpi = this->other_cpi = 0; + this->ipcomp = IPCOMP_NONE; + } + status_i = status_o = FAILED; + if (this->keymat->derive_child_keys(this->keymat, this->proposal, + this->dh, nonce_i, 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->my_spi, this->my_cpi, TRUE, my_ts, other_ts); + status_o = this->child_sa->install(this->child_sa, encr_i, integ_i, + this->other_spi, this->other_cpi, FALSE, my_ts, other_ts); + } + else + { + status_i = this->child_sa->install(this->child_sa, encr_i, integ_i, + this->my_spi, this->my_cpi, TRUE, my_ts, other_ts); + status_o = this->child_sa->install(this->child_sa, encr_r, integ_r, + this->other_spi, this->other_cpi, FALSE, my_ts, other_ts); + } + } + 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 " : ""); + return FAILED; + } + + status = this->child_sa->add_policies(this->child_sa, my_ts, other_ts); + if (status != SUCCESS) + { + DBG1(DBG_IKE, "unable to install IPsec policies (SPD) in kernel"); + return NOT_FOUND; + } + + charon->bus->child_keys(charon->bus, this->child_sa, this->dh, + nonce_i, 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); + this->established = TRUE; + + if (!this->rekey) + { /* a rekeyed SA uses the same reqid, no need for a new job */ + schedule_inactivity_timeout(this); + } + return SUCCESS; +} + +/** + * build the payloads for the message + */ +static void build_payloads(private_child_create_t *this, message_t *message) +{ + sa_payload_t *sa_payload; + nonce_payload_t *nonce_payload; + ke_payload_t *ke_payload; + ts_payload_t *ts_payload; + + /* add SA payload */ + if (this->initiator) + { + sa_payload = sa_payload_create_from_proposal_list(this->proposals); + } + else + { + sa_payload = sa_payload_create_from_proposal(this->proposal); + } + message->add_payload(message, (payload_t*)sa_payload); + + /* add nonce payload if not in IKE_AUTH */ + if (message->get_exchange_type(message) == CREATE_CHILD_SA) + { + nonce_payload = nonce_payload_create(); + nonce_payload->set_nonce(nonce_payload, this->my_nonce); + message->add_payload(message, (payload_t*)nonce_payload); + } + + /* diffie hellman exchange, if PFS enabled */ + if (this->dh) + { + ke_payload = ke_payload_create_from_diffie_hellman(this->dh); + message->add_payload(message, (payload_t*)ke_payload); + } + + /* add TSi/TSr payloads */ + ts_payload = ts_payload_create_from_traffic_selectors(TRUE, this->tsi); + message->add_payload(message, (payload_t*)ts_payload); + ts_payload = ts_payload_create_from_traffic_selectors(FALSE, this->tsr); + message->add_payload(message, (payload_t*)ts_payload); + + /* add a notify if we are not in tunnel mode */ + switch (this->mode) + { + case MODE_TRANSPORT: + message->add_notify(message, FALSE, USE_TRANSPORT_MODE, chunk_empty); + break; + case MODE_BEET: + message->add_notify(message, FALSE, USE_BEET_MODE, chunk_empty); + break; + default: + break; + } +} + +/** + * Adds an IPCOMP_SUPPORTED notify to the message, allocating a CPI + */ +static void add_ipcomp_notify(private_child_create_t *this, + message_t *message, u_int8_t ipcomp) +{ + if (this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY)) + { + DBG1(DBG_IKE, "IPComp is not supported if either peer is natted, " + "IPComp disabled"); + return; + } + + this->my_cpi = this->child_sa->alloc_cpi(this->child_sa); + if (this->my_cpi) + { + this->ipcomp = ipcomp; + message->add_notify(message, FALSE, IPCOMP_SUPPORTED, + chunk_cata("cc", chunk_from_thing(this->my_cpi), + chunk_from_thing(ipcomp))); + } + else + { + DBG1(DBG_IKE, "unable to allocate a CPI from kernel, IPComp disabled"); + } +} + +/** + * handle a received notify payload + */ +static void handle_notify(private_child_create_t *this, notify_payload_t *notify) +{ + switch (notify->get_notify_type(notify)) + { + case USE_TRANSPORT_MODE: + this->mode = MODE_TRANSPORT; + break; + case USE_BEET_MODE: + if (this->ike_sa->supports_extension(this->ike_sa, EXT_STRONGSWAN)) + { /* handle private use notify only if we know its meaning */ + this->mode = MODE_BEET; + } + else + { + DBG1(DBG_IKE, "received a notify strongSwan uses for BEET " + "mode, but peer implementation unknown, skipped"); + } + break; + case IPCOMP_SUPPORTED: + { + ipcomp_transform_t ipcomp; + u_int16_t cpi; + chunk_t data; + + data = notify->get_notification_data(notify); + cpi = *(u_int16_t*)data.ptr; + ipcomp = (ipcomp_transform_t)(*(data.ptr + 2)); + switch (ipcomp) + { + case IPCOMP_DEFLATE: + this->other_cpi = cpi; + this->ipcomp_received = ipcomp; + break; + case IPCOMP_LZS: + case IPCOMP_LZJH: + default: + DBG1(DBG_IKE, "received IPCOMP_SUPPORTED notify with a " + "transform ID we don't support %N", + ipcomp_transform_names, ipcomp); + break; + } + } + default: + break; + } +} + +/** + * Read payloads from message + */ +static void process_payloads(private_child_create_t *this, message_t *message) +{ + enumerator_t *enumerator; + payload_t *payload; + sa_payload_t *sa_payload; + ke_payload_t *ke_payload; + ts_payload_t *ts_payload; + + /* defaults to TUNNEL mode */ + this->mode = MODE_TUNNEL; + + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + switch (payload->get_type(payload)) + { + case SECURITY_ASSOCIATION: + sa_payload = (sa_payload_t*)payload; + this->proposals = sa_payload->get_proposals(sa_payload); + break; + case KEY_EXCHANGE: + ke_payload = (ke_payload_t*)payload; + if (!this->initiator) + { + this->dh_group = ke_payload->get_dh_group_number(ke_payload); + this->dh = this->keymat->create_dh(this->keymat, this->dh_group); + } + if (this->dh) + { + this->dh->set_other_public_value(this->dh, + ke_payload->get_key_exchange_data(ke_payload)); + } + break; + case TRAFFIC_SELECTOR_INITIATOR: + ts_payload = (ts_payload_t*)payload; + this->tsi = ts_payload->get_traffic_selectors(ts_payload); + break; + case TRAFFIC_SELECTOR_RESPONDER: + ts_payload = (ts_payload_t*)payload; + this->tsr = ts_payload->get_traffic_selectors(ts_payload); + break; + case NOTIFY: + handle_notify(this, (notify_payload_t*)payload); + break; + default: + break; + } + } + enumerator->destroy(enumerator); +} + +/** + * Implementation of task_t.build for initiator + */ +static status_t build_i(private_child_create_t *this, message_t *message) +{ + host_t *me, *other, *vip; + peer_cfg_t *peer_cfg; + + switch (message->get_exchange_type(message)) + { + case IKE_SA_INIT: + return get_nonce(message, &this->my_nonce); + case CREATE_CHILD_SA: + if (generate_nonce(&this->my_nonce) != SUCCESS) + { + message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN, chunk_empty); + return SUCCESS; + } + if (this->dh_group == MODP_NONE) + { + this->dh_group = this->config->get_dh_group(this->config); + } + break; + case IKE_AUTH: + if (message->get_message_id(message) != 1) + { + /* send only in the first request, not in subsequent rounds */ + return NEED_MORE; + } + break; + default: + break; + } + + if (this->reqid) + { + DBG0(DBG_IKE, "establishing CHILD_SA %s{%d}", + this->config->get_name(this->config), this->reqid); + } + else + { + DBG0(DBG_IKE, "establishing CHILD_SA %s", + this->config->get_name(this->config)); + } + + /* reuse virtual IP if we already have one */ + me = this->ike_sa->get_virtual_ip(this->ike_sa, TRUE); + if (me == NULL) + { + me = this->ike_sa->get_my_host(this->ike_sa); + } + other = this->ike_sa->get_virtual_ip(this->ike_sa, FALSE); + if (other == NULL) + { + other = this->ike_sa->get_other_host(this->ike_sa); + } + + /* check if we want a virtual IP, but don't have one */ + peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); + vip = peer_cfg->get_virtual_ip(peer_cfg); + if (!this->reqid && vip) + { + /* propose a 0.0.0.0/0 or ::/0 subnet when we use virtual ip */ + vip = host_create_any(vip->get_family(vip)); + this->tsi = this->config->get_traffic_selectors(this->config, TRUE, + NULL, vip); + vip->destroy(vip); + } + else + { /* but narrow it for host2host / if we already have a vip */ + this->tsi = this->config->get_traffic_selectors(this->config, TRUE, + NULL, me); + } + this->tsr = this->config->get_traffic_selectors(this->config, FALSE, + NULL, other); + + if (this->packet_tsi) + { + this->tsi->insert_first(this->tsi, + this->packet_tsi->clone(this->packet_tsi)); + } + if (this->packet_tsr) + { + this->tsr->insert_first(this->tsr, + this->packet_tsr->clone(this->packet_tsr)); + } + this->proposals = this->config->get_proposals(this->config, + this->dh_group == MODP_NONE); + this->mode = this->config->get_mode(this->config); + if (this->mode == MODE_TRANSPORT && + this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY)) + { + this->mode = MODE_TUNNEL; + DBG1(DBG_IKE, "not using transport mode, connection NATed"); + } + + this->child_sa = child_sa_create(this->ike_sa->get_my_host(this->ike_sa), + this->ike_sa->get_other_host(this->ike_sa), this->config, this->reqid, + this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY)); + + if (!allocate_spi(this)) + { + DBG1(DBG_IKE, "unable to allocate SPIs from kernel"); + return FAILED; + } + + if (this->dh_group != MODP_NONE) + { + this->dh = this->keymat->create_dh(this->keymat, this->dh_group); + } + + if (this->config->use_ipcomp(this->config)) + { + /* IPCOMP_DEFLATE is the only transform we support at the moment */ + add_ipcomp_notify(this, message, IPCOMP_DEFLATE); + } + + build_payloads(this, message); + + this->tsi->destroy_offset(this->tsi, offsetof(traffic_selector_t, destroy)); + this->tsr->destroy_offset(this->tsr, offsetof(traffic_selector_t, destroy)); + this->proposals->destroy_offset(this->proposals, offsetof(proposal_t, destroy)); + this->tsi = NULL; + this->tsr = NULL; + this->proposals = NULL; + + return NEED_MORE; +} + +/** + * Implementation of task_t.process for responder + */ +static status_t process_r(private_child_create_t *this, message_t *message) +{ + switch (message->get_exchange_type(message)) + { + case IKE_SA_INIT: + return get_nonce(message, &this->other_nonce); + case CREATE_CHILD_SA: + get_nonce(message, &this->other_nonce); + break; + case IKE_AUTH: + if (message->get_message_id(message) != 1) + { + /* only handle first AUTH payload, not additional rounds */ + return NEED_MORE; + } + default: + break; + } + + process_payloads(this, message); + + return NEED_MORE; +} + +/** + * handle CHILD_SA setup failure + */ +static void handle_child_sa_failure(private_child_create_t *this, + message_t *message) +{ + if (message->get_exchange_type(message) == IKE_AUTH && + lib->settings->get_bool(lib->settings, + "charon.close_ike_on_child_failure", FALSE)) + { + /* we delay the delete for 100ms, as the IKE_AUTH response must arrive + * first */ + DBG1(DBG_IKE, "closing IKE_SA due CHILD_SA setup failure"); + charon->scheduler->schedule_job_ms(charon->scheduler, (job_t*) + delete_ike_sa_job_create(this->ike_sa->get_id(this->ike_sa), TRUE), + 100); + } +} + +/** + * Implementation of task_t.build for responder + */ +static status_t build_r(private_child_create_t *this, message_t *message) +{ + peer_cfg_t *peer_cfg; + payload_t *payload; + enumerator_t *enumerator; + bool no_dh = TRUE; + + switch (message->get_exchange_type(message)) + { + case IKE_SA_INIT: + return get_nonce(message, &this->my_nonce); + case CREATE_CHILD_SA: + if (generate_nonce(&this->my_nonce) != SUCCESS) + { + message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN, + chunk_empty); + return SUCCESS; + } + no_dh = FALSE; + break; + case IKE_AUTH: + if (this->ike_sa->get_state(this->ike_sa) != IKE_ESTABLISHED) + { /* wait until all authentication round completed */ + return NEED_MORE; + } + default: + break; + } + + if (this->ike_sa->get_state(this->ike_sa) == IKE_REKEYING) + { + DBG1(DBG_IKE, "unable to create CHILD_SA while rekeying IKE_SA"); + message->add_notify(message, TRUE, NO_ADDITIONAL_SAS, chunk_empty); + return SUCCESS; + } + + peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); + if (peer_cfg && this->tsi && this->tsr) + { + host_t *me, *other; + + me = this->ike_sa->get_virtual_ip(this->ike_sa, TRUE); + if (me == NULL) + { + me = this->ike_sa->get_my_host(this->ike_sa); + } + other = this->ike_sa->get_virtual_ip(this->ike_sa, FALSE); + if (other == NULL) + { + other = this->ike_sa->get_other_host(this->ike_sa); + } + this->config = peer_cfg->select_child_cfg(peer_cfg, this->tsr, + this->tsi, me, other); + } + + if (this->config == NULL) + { + DBG1(DBG_IKE, "traffic selectors %#R=== %#R inacceptable", + this->tsr, this->tsi); + message->add_notify(message, FALSE, TS_UNACCEPTABLE, chunk_empty); + handle_child_sa_failure(this, message); + return SUCCESS; + } + + /* check if ike_config_t included non-critical error notifies */ + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + if (payload->get_type(payload) == NOTIFY) + { + notify_payload_t *notify = (notify_payload_t*)payload; + + switch (notify->get_notify_type(notify)) + { + case INTERNAL_ADDRESS_FAILURE: + case FAILED_CP_REQUIRED: + { + DBG1(DBG_IKE,"configuration payload negotation " + "failed, no CHILD_SA built"); + enumerator->destroy(enumerator); + handle_child_sa_failure(this, message); + return SUCCESS; + } + default: + break; + } + } + } + enumerator->destroy(enumerator); + + this->child_sa = child_sa_create(this->ike_sa->get_my_host(this->ike_sa), + this->ike_sa->get_other_host(this->ike_sa), this->config, this->reqid, + this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY)); + + if (this->ipcomp_received != IPCOMP_NONE) + { + if (this->config->use_ipcomp(this->config)) + { + add_ipcomp_notify(this, message, this->ipcomp_received); + } + else + { + DBG1(DBG_IKE, "received %N notify but IPComp is disabled, ignoring", + notify_type_names, IPCOMP_SUPPORTED); + } + } + + switch (select_and_install(this, no_dh)) + { + case SUCCESS: + break; + case NOT_FOUND: + message->add_notify(message, FALSE, TS_UNACCEPTABLE, chunk_empty); + handle_child_sa_failure(this, message); + return SUCCESS; + case INVALID_ARG: + { + u_int16_t group = htons(this->dh_group); + message->add_notify(message, FALSE, INVALID_KE_PAYLOAD, + chunk_from_thing(group)); + handle_child_sa_failure(this, message); + return SUCCESS; + } + case FAILED: + default: + message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN, chunk_empty); + handle_child_sa_failure(this, message); + return SUCCESS; + } + + build_payloads(this, message); + + DBG0(DBG_IKE, "CHILD_SA %s{%d} established " + "with SPIs %.8x_i %.8x_o and TS %#R=== %#R", + this->child_sa->get_name(this->child_sa), + this->child_sa->get_reqid(this->child_sa), + ntohl(this->child_sa->get_spi(this->child_sa, TRUE)), + ntohl(this->child_sa->get_spi(this->child_sa, FALSE)), + this->child_sa->get_traffic_selectors(this->child_sa, TRUE), + this->child_sa->get_traffic_selectors(this->child_sa, FALSE)); + + if (!this->rekey) + { /* invoke the child_up() hook if we are not rekeying */ + charon->bus->child_updown(charon->bus, this->child_sa, TRUE); + } + return SUCCESS; +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t process_i(private_child_create_t *this, message_t *message) +{ + enumerator_t *enumerator; + payload_t *payload; + bool no_dh = TRUE; + + switch (message->get_exchange_type(message)) + { + case IKE_SA_INIT: + return get_nonce(message, &this->other_nonce); + case CREATE_CHILD_SA: + get_nonce(message, &this->other_nonce); + no_dh = FALSE; + break; + case IKE_AUTH: + if (this->ike_sa->get_state(this->ike_sa) != IKE_ESTABLISHED) + { /* wait until all authentication round completed */ + return NEED_MORE; + } + default: + break; + } + + /* check for erronous notifies */ + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + if (payload->get_type(payload) == NOTIFY) + { + notify_payload_t *notify = (notify_payload_t*)payload; + notify_type_t type = notify->get_notify_type(notify); + + switch (type) + { + /* handle notify errors related to CHILD_SA only */ + case NO_PROPOSAL_CHOSEN: + case SINGLE_PAIR_REQUIRED: + case NO_ADDITIONAL_SAS: + case INTERNAL_ADDRESS_FAILURE: + case FAILED_CP_REQUIRED: + case TS_UNACCEPTABLE: + case INVALID_SELECTORS: + { + DBG1(DBG_IKE, "received %N notify, no CHILD_SA built", + notify_type_names, type); + enumerator->destroy(enumerator); + handle_child_sa_failure(this, message); + /* an error in CHILD_SA creation is not critical */ + return SUCCESS; + } + case INVALID_KE_PAYLOAD: + { + chunk_t data; + u_int16_t group = MODP_NONE; + + data = notify->get_notification_data(notify); + if (data.len == sizeof(group)) + { + memcpy(&group, data.ptr, data.len); + group = ntohs(group); + } + DBG1(DBG_IKE, "peer didn't accept DH group %N, " + "it requested %N", diffie_hellman_group_names, + this->dh_group, diffie_hellman_group_names, group); + this->dh_group = group; + this->public.task.migrate(&this->public.task, this->ike_sa); + enumerator->destroy(enumerator); + return NEED_MORE; + } + default: + break; + } + } + } + enumerator->destroy(enumerator); + + process_payloads(this, message); + + if (this->ipcomp == IPCOMP_NONE && this->ipcomp_received != IPCOMP_NONE) + { + DBG1(DBG_IKE, "received an IPCOMP_SUPPORTED notify without requesting" + " one, no CHILD_SA built"); + handle_child_sa_failure(this, message); + return SUCCESS; + } + else if (this->ipcomp != IPCOMP_NONE && this->ipcomp_received == IPCOMP_NONE) + { + DBG1(DBG_IKE, "peer didn't accept our proposed IPComp transforms, " + "IPComp is disabled"); + this->ipcomp = IPCOMP_NONE; + } + else if (this->ipcomp != IPCOMP_NONE && this->ipcomp != this->ipcomp_received) + { + DBG1(DBG_IKE, "received an IPCOMP_SUPPORTED notify we didn't propose, " + "no CHILD_SA built"); + handle_child_sa_failure(this, message); + return SUCCESS; + } + + if (select_and_install(this, no_dh) == SUCCESS) + { + DBG0(DBG_IKE, "CHILD_SA %s{%d} established " + "with SPIs %.8x_i %.8x_o and TS %#R=== %#R", + this->child_sa->get_name(this->child_sa), + this->child_sa->get_reqid(this->child_sa), + ntohl(this->child_sa->get_spi(this->child_sa, TRUE)), + ntohl(this->child_sa->get_spi(this->child_sa, FALSE)), + this->child_sa->get_traffic_selectors(this->child_sa, TRUE), + this->child_sa->get_traffic_selectors(this->child_sa, FALSE)); + + if (!this->rekey) + { /* invoke the child_up() hook if we are not rekeying */ + charon->bus->child_updown(charon->bus, this->child_sa, TRUE); + } + } + else + { + handle_child_sa_failure(this, message); + } + return SUCCESS; +} + +/** + * Implementation of task_t.get_type + */ +static task_type_t get_type(private_child_create_t *this) +{ + return CHILD_CREATE; +} + +/** + * Implementation of child_create_t.use_reqid + */ +static void use_reqid(private_child_create_t *this, u_int32_t reqid) +{ + this->reqid = reqid; +} + +/** + * Implementation of child_create_t.get_child + */ +static child_sa_t* get_child(private_child_create_t *this) +{ + return this->child_sa; +} + +/** + * Implementation of child_create_t.get_lower_nonce + */ +static chunk_t get_lower_nonce(private_child_create_t *this) +{ + if (memcmp(this->my_nonce.ptr, this->other_nonce.ptr, + min(this->my_nonce.len, this->other_nonce.len)) < 0) + { + return this->my_nonce; + } + else + { + return this->other_nonce; + } +} + +/** + * Implementation of task_t.migrate + */ +static void migrate(private_child_create_t *this, ike_sa_t *ike_sa) +{ + chunk_free(&this->my_nonce); + chunk_free(&this->other_nonce); + if (this->tsi) + { + this->tsr->destroy_offset(this->tsr, offsetof(traffic_selector_t, destroy)); + } + if (this->tsr) + { + this->tsi->destroy_offset(this->tsi, offsetof(traffic_selector_t, destroy)); + } + DESTROY_IF(this->child_sa); + DESTROY_IF(this->proposal); + DESTROY_IF(this->dh); + if (this->proposals) + { + this->proposals->destroy_offset(this->proposals, offsetof(proposal_t, destroy)); + } + + this->ike_sa = ike_sa; + this->keymat = ike_sa->get_keymat(ike_sa); + this->proposal = NULL; + this->proposals = NULL; + this->tsi = NULL; + this->tsr = NULL; + this->dh = NULL; + this->child_sa = NULL; + this->mode = MODE_TUNNEL; + this->ipcomp = IPCOMP_NONE; + this->ipcomp_received = IPCOMP_NONE; + this->other_cpi = 0; + this->reqid = 0; + this->established = FALSE; +} + +/** + * Implementation of task_t.destroy + */ +static void destroy(private_child_create_t *this) +{ + chunk_free(&this->my_nonce); + chunk_free(&this->other_nonce); + if (this->tsr) + { + this->tsr->destroy_offset(this->tsr, offsetof(traffic_selector_t, destroy)); + } + if (this->tsi) + { + this->tsi->destroy_offset(this->tsi, offsetof(traffic_selector_t, destroy)); + } + if (!this->established) + { + DESTROY_IF(this->child_sa); + } + DESTROY_IF(this->packet_tsi); + DESTROY_IF(this->packet_tsr); + DESTROY_IF(this->proposal); + DESTROY_IF(this->dh); + if (this->proposals) + { + this->proposals->destroy_offset(this->proposals, offsetof(proposal_t, destroy)); + } + + DESTROY_IF(this->config); + free(this); +} + +/* + * Described in header. + */ +child_create_t *child_create_create(ike_sa_t *ike_sa, + child_cfg_t *config, bool rekey, + traffic_selector_t *tsi, traffic_selector_t *tsr) +{ + private_child_create_t *this = malloc_thing(private_child_create_t); + + this->public.get_child = (child_sa_t*(*)(child_create_t*))get_child; + this->public.get_lower_nonce = (chunk_t(*)(child_create_t*))get_lower_nonce; + this->public.use_reqid = (void(*)(child_create_t*,u_int32_t))use_reqid; + this->public.task.get_type = (task_type_t(*)(task_t*))get_type; + this->public.task.migrate = (void(*)(task_t*,ike_sa_t*))migrate; + this->public.task.destroy = (void(*)(task_t*))destroy; + if (config) + { + this->public.task.build = (status_t(*)(task_t*,message_t*))build_i; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_i; + this->initiator = TRUE; + config->get_ref(config); + } + else + { + this->public.task.build = (status_t(*)(task_t*,message_t*))build_r; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_r; + this->initiator = FALSE; + } + + this->ike_sa = ike_sa; + this->config = config; + this->my_nonce = chunk_empty; + this->other_nonce = chunk_empty; + this->proposals = NULL; + this->proposal = NULL; + this->tsi = NULL; + this->tsr = NULL; + this->packet_tsi = tsi ? tsi->clone(tsi) : NULL; + this->packet_tsr = tsr ? tsr->clone(tsr) : NULL; + this->dh = NULL; + this->dh_group = MODP_NONE; + this->keymat = ike_sa->get_keymat(ike_sa); + this->child_sa = NULL; + this->mode = MODE_TUNNEL; + this->ipcomp = IPCOMP_NONE; + this->ipcomp_received = IPCOMP_NONE; + this->my_spi = 0; + this->other_spi = 0; + this->my_cpi = 0; + this->other_cpi = 0; + this->reqid = 0; + this->established = FALSE; + this->rekey = rekey; + + return &this->public; +} diff --git a/src/libcharon/sa/tasks/child_create.h b/src/libcharon/sa/tasks/child_create.h new file mode 100644 index 000000000..5dedeb8b1 --- /dev/null +++ b/src/libcharon/sa/tasks/child_create.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2007 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 child_create child_create + * @{ @ingroup tasks + */ + +#ifndef CHILD_CREATE_H_ +#define CHILD_CREATE_H_ + +typedef struct child_create_t child_create_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/tasks/task.h> +#include <config/child_cfg.h> + +/** + * Task of type CHILD_CREATE, established a new CHILD_SA. + * + * This task may be included in the IKE_AUTH message or in a separate + * CREATE_CHILD_SA exchange. + */ +struct child_create_t { + + /** + * Implements the task_t interface + */ + task_t task; + + /** + * Use a specific reqid for the CHILD_SA. + * + * When this task is used for rekeying, the same reqid is used + * for the new CHILD_SA. + * + * @param reqid reqid to use + */ + void (*use_reqid) (child_create_t *this, u_int32_t reqid); + + /** + * Get the lower of the two nonces, used for rekey collisions. + * + * @return lower nonce + */ + chunk_t (*get_lower_nonce) (child_create_t *this); + + /** + * Get the CHILD_SA established/establishing by this task. + * + * @return child_sa + */ + child_sa_t* (*get_child) (child_create_t *this); +}; + +/** + * Create a new child_create task. + * + * @param ike_sa IKE_SA this task works for + * @param config child_cfg if task initiator, NULL if responder + * @param rekey whether we do a rekey or not + * @param tsi source of triggering packet, or NULL + * @param tsr destination of triggering packet, or NULL + * @return child_create task to handle by the task_manager + */ +child_create_t *child_create_create(ike_sa_t *ike_sa, + child_cfg_t *config, bool rekey, + traffic_selector_t *tsi, traffic_selector_t *tsr); + +#endif /** CHILD_CREATE_H_ @}*/ diff --git a/src/libcharon/sa/tasks/child_delete.c b/src/libcharon/sa/tasks/child_delete.c new file mode 100644 index 000000000..d7c6b0541 --- /dev/null +++ b/src/libcharon/sa/tasks/child_delete.c @@ -0,0 +1,402 @@ +/* + * Copyright (C) 2006-2007 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 "child_delete.h" + +#include <daemon.h> +#include <encoding/payloads/delete_payload.h> + + +typedef struct private_child_delete_t private_child_delete_t; + +/** + * Private members of a child_delete_t task. + */ +struct private_child_delete_t { + + /** + * Public methods and task_t interface. + */ + child_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; + + /** + * whether to enforce delete action policy + */ + bool check_delete_action; + + /** + * is this delete exchange following a rekey? + */ + bool rekeyed; + + /** + * CHILD_SAs which get deleted + */ + linked_list_t *child_sas; +}; + +/** + * build the delete payloads from the listed child_sas + */ +static void build_payloads(private_child_delete_t *this, message_t *message) +{ + delete_payload_t *ah = NULL, *esp = NULL; + iterator_t *iterator; + child_sa_t *child_sa; + + iterator = this->child_sas->create_iterator(this->child_sas, TRUE); + while (iterator->iterate(iterator, (void**)&child_sa)) + { + protocol_id_t protocol = child_sa->get_protocol(child_sa); + u_int32_t spi = child_sa->get_spi(child_sa, TRUE); + + switch (protocol) + { + case PROTO_ESP: + if (esp == NULL) + { + esp = delete_payload_create(PROTO_ESP); + message->add_payload(message, (payload_t*)esp); + } + esp->add_spi(esp, spi); + DBG1(DBG_IKE, "sending DELETE for %N CHILD_SA with SPI %.8x", + protocol_id_names, protocol, ntohl(spi)); + break; + case PROTO_AH: + if (ah == NULL) + { + ah = delete_payload_create(PROTO_AH); + message->add_payload(message, (payload_t*)ah); + } + ah->add_spi(ah, spi); + DBG1(DBG_IKE, "sending DELETE for %N CHILD_SA with SPI %.8x", + protocol_id_names, protocol, ntohl(spi)); + break; + default: + break; + } + child_sa->set_state(child_sa, CHILD_DELETING); + } + iterator->destroy(iterator); +} + +/** + * read in payloads and find the children to delete + */ +static void process_payloads(private_child_delete_t *this, message_t *message) +{ + enumerator_t *payloads; + iterator_t *spis; + payload_t *payload; + delete_payload_t *delete_payload; + u_int32_t *spi; + protocol_id_t protocol; + child_sa_t *child_sa; + + payloads = message->create_payload_enumerator(message); + while (payloads->enumerate(payloads, &payload)) + { + if (payload->get_type(payload) == DELETE) + { + 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_iterator(delete_payload); + while (spis->iterate(spis, (void**)&spi)) + { + child_sa = this->ike_sa->get_child_sa(this->ike_sa, protocol, + *spi, FALSE); + if (child_sa == NULL) + { + DBG1(DBG_IKE, "received DELETE for %N CHILD_SA with SPI %.8x, " + "but no such SA", protocol_id_names, protocol, ntohl(*spi)); + continue; + } + DBG1(DBG_IKE, "received DELETE for %N CHILD_SA with SPI %.8x", + protocol_id_names, protocol, ntohl(*spi)); + + switch (child_sa->get_state(child_sa)) + { + case CHILD_REKEYING: + this->rekeyed = TRUE; + /* we reply as usual, rekeying will fail */ + break; + case CHILD_DELETING: + /* we don't send back a delete if we initiated ourself */ + if (!this->initiator) + { + this->ike_sa->destroy_child_sa(this->ike_sa, + protocol, *spi); + continue; + } + case CHILD_INSTALLED: + if (!this->initiator) + { /* reestablish installed children if required */ + this->check_delete_action = TRUE; + } + default: + break; + } + + this->child_sas->insert_last(this->child_sas, child_sa); + } + spis->destroy(spis); + } + } + payloads->destroy(payloads); +} + +/** + * destroy the children listed in this->child_sas, reestablish by policy + */ +static status_t destroy_and_reestablish(private_child_delete_t *this) +{ + iterator_t *iterator; + child_sa_t *child_sa; + child_cfg_t *child_cfg; + protocol_id_t protocol; + u_int32_t spi; + status_t status = SUCCESS; + + iterator = this->child_sas->create_iterator(this->child_sas, TRUE); + while (iterator->iterate(iterator, (void**)&child_sa)) + { + /* signal child down event if we are not rekeying */ + if (!this->rekeyed) + { + charon->bus->child_updown(charon->bus, child_sa, FALSE); + } + spi = child_sa->get_spi(child_sa, TRUE); + protocol = child_sa->get_protocol(child_sa); + child_cfg = child_sa->get_config(child_sa); + child_cfg->get_ref(child_cfg); + this->ike_sa->destroy_child_sa(this->ike_sa, protocol, spi); + if (this->check_delete_action) + { /* enforce child_cfg policy if deleted passively */ + switch (child_cfg->get_close_action(child_cfg)) + { + case ACTION_RESTART: + child_cfg->get_ref(child_cfg); + status = this->ike_sa->initiate(this->ike_sa, child_cfg, 0, + NULL, NULL); + break; + case ACTION_ROUTE: + charon->traps->install(charon->traps, + this->ike_sa->get_peer_cfg(this->ike_sa), child_cfg); + break; + default: + break; + } + } + child_cfg->destroy(child_cfg); + if (status != SUCCESS) + { + break; + } + } + iterator->destroy(iterator); + return status; +} + +/** + * send closing signals for all CHILD_SAs over the bus + */ +static void log_children(private_child_delete_t *this) +{ + iterator_t *iterator; + child_sa_t *child_sa; + u_int64_t bytes_in, bytes_out; + + iterator = this->child_sas->create_iterator(this->child_sas, TRUE); + while (iterator->iterate(iterator, (void**)&child_sa)) + { + child_sa->get_usestats(child_sa, TRUE, NULL, &bytes_in); + child_sa->get_usestats(child_sa, FALSE, NULL, &bytes_out); + + DBG0(DBG_IKE, "closing CHILD_SA %s{%d} " + "with SPIs %.8x_i (%llu bytes) %.8x_o (%llu bytes) and TS %#R=== %#R", + child_sa->get_name(child_sa), child_sa->get_reqid(child_sa), + ntohl(child_sa->get_spi(child_sa, TRUE)), bytes_in, + ntohl(child_sa->get_spi(child_sa, FALSE)), bytes_out, + child_sa->get_traffic_selectors(child_sa, TRUE), + child_sa->get_traffic_selectors(child_sa, FALSE)); + } + iterator->destroy(iterator); +} + +/** + * Implementation of task_t.build for initiator + */ +static status_t build_i(private_child_delete_t *this, message_t *message) +{ + child_sa_t *child_sa; + + child_sa = this->ike_sa->get_child_sa(this->ike_sa, this->protocol, + this->spi, TRUE); + if (!child_sa) + { /* check if it is an outbound sa */ + child_sa = this->ike_sa->get_child_sa(this->ike_sa, this->protocol, + this->spi, FALSE); + if (!child_sa) + { /* child does not exist anymore */ + return SUCCESS; + } + /* we work only with the inbound SPI */ + this->spi = child_sa->get_spi(child_sa, TRUE); + } + this->child_sas->insert_last(this->child_sas, child_sa); + if (child_sa->get_state(child_sa) == CHILD_REKEYING) + { + this->rekeyed = TRUE; + } + log_children(this); + build_payloads(this, message); + return NEED_MORE; +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t process_i(private_child_delete_t *this, message_t *message) +{ + /* flush the list before adding new SAs */ + this->child_sas->destroy(this->child_sas); + this->child_sas = linked_list_create(); + + process_payloads(this, message); + DBG1(DBG_IKE, "CHILD_SA closed"); + return destroy_and_reestablish(this); +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t process_r(private_child_delete_t *this, message_t *message) +{ + process_payloads(this, message); + log_children(this); + return NEED_MORE; +} + +/** + * Implementation of task_t.build for responder + */ +static status_t build_r(private_child_delete_t *this, message_t *message) +{ + /* if we are rekeying, we send an empty informational */ + if (this->ike_sa->get_state(this->ike_sa) != IKE_REKEYING) + { + build_payloads(this, message); + } + DBG1(DBG_IKE, "CHILD_SA closed"); + return destroy_and_reestablish(this); +} + +/** + * Implementation of task_t.get_type + */ +static task_type_t get_type(private_child_delete_t *this) +{ + return CHILD_DELETE; +} + +/** + * Implementation of child_delete_t.get_child + */ +static child_sa_t* get_child(private_child_delete_t *this) +{ + child_sa_t *child_sa = NULL; + this->child_sas->get_first(this->child_sas, (void**)&child_sa); + return child_sa; +} + +/** + * Implementation of task_t.migrate + */ +static void migrate(private_child_delete_t *this, ike_sa_t *ike_sa) +{ + this->check_delete_action = FALSE; + this->ike_sa = ike_sa; + + this->child_sas->destroy(this->child_sas); + this->child_sas = linked_list_create(); +} + +/** + * Implementation of task_t.destroy + */ +static void destroy(private_child_delete_t *this) +{ + this->child_sas->destroy(this->child_sas); + free(this); +} + +/* + * Described in header. + */ +child_delete_t *child_delete_create(ike_sa_t *ike_sa, protocol_id_t protocol, + u_int32_t spi) +{ + private_child_delete_t *this = malloc_thing(private_child_delete_t); + + this->public.get_child = (child_sa_t*(*)(child_delete_t*))get_child; + this->public.task.get_type = (task_type_t(*)(task_t*))get_type; + this->public.task.migrate = (void(*)(task_t*,ike_sa_t*))migrate; + this->public.task.destroy = (void(*)(task_t*))destroy; + + this->ike_sa = ike_sa; + this->check_delete_action = FALSE; + this->child_sas = linked_list_create(); + this->protocol = protocol; + this->spi = spi; + this->rekeyed = FALSE; + + if (protocol != PROTO_NONE) + { + this->public.task.build = (status_t(*)(task_t*,message_t*))build_i; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_i; + this->initiator = TRUE; + } + else + { + this->public.task.build = (status_t(*)(task_t*,message_t*))build_r; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_r; + this->initiator = FALSE; + } + return &this->public; +} diff --git a/src/libcharon/sa/tasks/child_delete.h b/src/libcharon/sa/tasks/child_delete.h new file mode 100644 index 000000000..365807c68 --- /dev/null +++ b/src/libcharon/sa/tasks/child_delete.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2007 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 child_delete child_delete + * @{ @ingroup tasks + */ + +#ifndef CHILD_DELETE_H_ +#define CHILD_DELETE_H_ + +typedef struct child_delete_t child_delete_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/tasks/task.h> +#include <sa/child_sa.h> + +/** + * Task of type child_delete, delete a CHILD_SA. + */ +struct child_delete_t { + + /** + * Implements the task_t interface + */ + task_t task; + + /** + * Get the CHILD_SA to delete by this task. + * + * @return child_sa + */ + child_sa_t* (*get_child) (child_delete_t *this); +}; + +/** + * Create a new child_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 + * @return child_delete task to handle by the task_manager + */ +child_delete_t *child_delete_create(ike_sa_t *ike_sa, protocol_id_t protocol, + u_int32_t spi); + +#endif /** CHILD_DELETE_H_ @}*/ diff --git a/src/libcharon/sa/tasks/child_rekey.c b/src/libcharon/sa/tasks/child_rekey.c new file mode 100644 index 000000000..b5e4e84b4 --- /dev/null +++ b/src/libcharon/sa/tasks/child_rekey.c @@ -0,0 +1,430 @@ +/* + * Copyright (C) 2005-2007 Martin Willi + * Copyright (C) 2005 Jan Hutter + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "child_rekey.h" + +#include <daemon.h> +#include <encoding/payloads/notify_payload.h> +#include <sa/tasks/child_create.h> +#include <sa/tasks/child_delete.h> +#include <processing/jobs/rekey_child_sa_job.h> +#include <processing/jobs/rekey_ike_sa_job.h> + + +typedef struct private_child_rekey_t private_child_rekey_t; + +/** + * Private members of a child_rekey_t task. + */ +struct private_child_rekey_t { + + /** + * Public methods and task_t interface. + */ + child_rekey_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; + + /** + * Are we the initiator? + */ + bool initiator; + + /** + * Protocol of CHILD_SA to rekey + */ + protocol_id_t protocol; + + /** + * Inbound SPI of CHILD_SA to rekey + */ + u_int32_t spi; + + /** + * the CHILD_CREATE task which is reused to simplify rekeying + */ + child_create_t *child_create; + + /** + * the CHILD_DELETE task to delete rekeyed CHILD_SA + */ + child_delete_t *child_delete; + + /** + * CHILD_SA which gets rekeyed + */ + child_sa_t *child_sa; + + /** + * colliding task, may be delete or rekey + */ + task_t *collision; +}; + +/** + * Implementation of task_t.build for initiator, after rekeying + */ +static status_t build_i_delete(private_child_rekey_t *this, message_t *message) +{ + /* update exchange type to INFORMATIONAL for the delete */ + message->set_exchange_type(message, INFORMATIONAL); + + return this->child_delete->task.build(&this->child_delete->task, message); +} + +/** + * Implementation of task_t.process for initiator, after rekeying + */ +static status_t process_i_delete(private_child_rekey_t *this, message_t *message) +{ + return this->child_delete->task.process(&this->child_delete->task, message); +} + +/** + * find a child using the REKEY_SA notify + */ +static void find_child(private_child_rekey_t *this, message_t *message) +{ + notify_payload_t *notify; + protocol_id_t protocol; + u_int32_t spi; + + notify = message->get_notify(message, REKEY_SA); + if (notify) + { + protocol = notify->get_protocol_id(notify); + spi = notify->get_spi(notify); + + if (protocol == PROTO_ESP || protocol == PROTO_AH) + { + this->child_sa = this->ike_sa->get_child_sa(this->ike_sa, protocol, + spi, FALSE); + } + } +} + +/** + * Implementation of task_t.build for initiator + */ +static status_t build_i(private_child_rekey_t *this, message_t *message) +{ + notify_payload_t *notify; + u_int32_t reqid; + child_cfg_t *config; + + this->child_sa = this->ike_sa->get_child_sa(this->ike_sa, this->protocol, + this->spi, TRUE); + if (!this->child_sa) + { /* check if it is an outbound CHILD_SA */ + this->child_sa = this->ike_sa->get_child_sa(this->ike_sa, this->protocol, + this->spi, FALSE); + if (!this->child_sa) + { /* CHILD_SA is gone, unable to rekey. As an empty CREATE_CHILD_SA + * exchange is invalid, we fall back to an INFORMATIONAL exchange.*/ + message->set_exchange_type(message, INFORMATIONAL); + return SUCCESS; + } + /* we work only with the inbound SPI */ + this->spi = this->child_sa->get_spi(this->child_sa, TRUE); + } + config = this->child_sa->get_config(this->child_sa); + + /* we just need the rekey notify ... */ + notify = notify_payload_create_from_protocol_and_type(this->protocol, + REKEY_SA); + notify->set_spi(notify, this->spi); + message->add_payload(message, (payload_t*)notify); + + /* ... our CHILD_CREATE task does the hard work for us. */ + if (!this->child_create) + { + this->child_create = child_create_create(this->ike_sa, config, TRUE, + NULL, NULL); + } + reqid = this->child_sa->get_reqid(this->child_sa); + this->child_create->use_reqid(this->child_create, reqid); + this->child_create->task.build(&this->child_create->task, message); + + this->child_sa->set_state(this->child_sa, CHILD_REKEYING); + + return NEED_MORE; +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t process_r(private_child_rekey_t *this, message_t *message) +{ + /* let the CHILD_CREATE task process the message */ + this->child_create->task.process(&this->child_create->task, message); + + find_child(this, message); + + return NEED_MORE; +} + +/** + * Implementation of task_t.build for responder + */ +static status_t build_r(private_child_rekey_t *this, message_t *message) +{ + u_int32_t reqid; + + if (this->child_sa == NULL || + this->child_sa->get_state(this->child_sa) == CHILD_DELETING) + { + DBG1(DBG_IKE, "unable to rekey, CHILD_SA not found"); + message->add_notify(message, TRUE, NO_PROPOSAL_CHOSEN, chunk_empty); + return SUCCESS; + } + + /* let the CHILD_CREATE task build the response */ + reqid = this->child_sa->get_reqid(this->child_sa); + this->child_create->use_reqid(this->child_create, reqid); + this->child_create->task.build(&this->child_create->task, message); + + if (message->get_payload(message, SECURITY_ASSOCIATION) == NULL) + { + /* rekeying failed, reuse old child */ + this->child_sa->set_state(this->child_sa, CHILD_INSTALLED); + return SUCCESS; + } + + this->child_sa->set_state(this->child_sa, CHILD_REKEYING); + + /* invoke rekey hook */ + charon->bus->child_rekey(charon->bus, this->child_sa, + this->child_create->get_child(this->child_create)); + return SUCCESS; +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t process_i(private_child_rekey_t *this, message_t *message) +{ + protocol_id_t protocol; + u_int32_t spi; + child_sa_t *to_delete; + + if (message->get_notify(message, NO_ADDITIONAL_SAS)) + { + DBG1(DBG_IKE, "peer seems to not support CHILD_SA rekeying, " + "starting reauthentication"); + this->child_sa->set_state(this->child_sa, CHILD_INSTALLED); + charon->processor->queue_job(charon->processor, + (job_t*)rekey_ike_sa_job_create( + this->ike_sa->get_id(this->ike_sa), TRUE)); + return SUCCESS; + } + + if (this->child_create->task.process(&this->child_create->task, + message) == NEED_MORE) + { + /* bad DH group while rekeying, try again */ + this->child_create->task.migrate(&this->child_create->task, this->ike_sa); + return NEED_MORE; + } + if (message->get_payload(message, SECURITY_ASSOCIATION) == NULL) + { + /* establishing new child failed, reuse old. but not when we + * recieved a delete in the meantime */ + if (!(this->collision && + this->collision->get_type(this->collision) == CHILD_DELETE)) + { + job_t *job; + u_int32_t retry = RETRY_INTERVAL - (random() % RETRY_JITTER); + + job = (job_t*)rekey_child_sa_job_create( + this->child_sa->get_reqid(this->child_sa), + this->child_sa->get_protocol(this->child_sa), + this->child_sa->get_spi(this->child_sa, TRUE)); + DBG1(DBG_IKE, "CHILD_SA rekeying failed, " + "trying again in %d seconds", retry); + this->child_sa->set_state(this->child_sa, CHILD_INSTALLED); + charon->scheduler->schedule_job(charon->scheduler, job, retry); + } + return SUCCESS; + } + + to_delete = this->child_sa; + + /* check for rekey collisions */ + if (this->collision && + this->collision->get_type(this->collision) == CHILD_REKEY) + { + chunk_t this_nonce, other_nonce; + private_child_rekey_t *other = (private_child_rekey_t*)this->collision; + + this_nonce = this->child_create->get_lower_nonce(this->child_create); + other_nonce = other->child_create->get_lower_nonce(other->child_create); + + /* if we have the lower nonce, delete rekeyed SA. If not, delete + * the redundant. */ + if (memcmp(this_nonce.ptr, other_nonce.ptr, + min(this_nonce.len, other_nonce.len)) < 0) + { + DBG1(DBG_IKE, "CHILD_SA rekey collision won, deleting rekeyed child"); + } + else + { + DBG1(DBG_IKE, "CHILD_SA rekey collision lost, deleting redundant child"); + to_delete = this->child_create->get_child(this->child_create); + if (to_delete == NULL) + { + /* ooops, should not happen, fallback */ + to_delete = this->child_sa; + } + } + } + + if (to_delete != this->child_create->get_child(this->child_create)) + { /* invoke rekey hook if rekeying successful */ + charon->bus->child_rekey(charon->bus, this->child_sa, + this->child_create->get_child(this->child_create)); + } + + spi = to_delete->get_spi(to_delete, TRUE); + protocol = to_delete->get_protocol(to_delete); + + /* rekeying done, delete the obsolete CHILD_SA using a subtask */ + this->child_delete = child_delete_create(this->ike_sa, protocol, spi); + this->public.task.build = (status_t(*)(task_t*,message_t*))build_i_delete; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_i_delete; + + return NEED_MORE; +} + +/** + * Implementation of task_t.get_type + */ +static task_type_t get_type(private_child_rekey_t *this) +{ + return CHILD_REKEY; +} + +/** + * Implementation of child_rekey_t.collide + */ +static void collide(private_child_rekey_t *this, task_t *other) +{ + /* the task manager only detects exchange collision, but not if + * the collision is for the same child. we check it here. */ + if (other->get_type(other) == CHILD_REKEY) + { + private_child_rekey_t *rekey = (private_child_rekey_t*)other; + if (rekey == NULL || rekey->child_sa != this->child_sa) + { + /* not the same child => no collision */ + other->destroy(other); + return; + } + } + else if (other->get_type(other) == CHILD_DELETE) + { + child_delete_t *del = (child_delete_t*)other; + if (del == NULL || del->get_child(del) != this->child_sa) + { + /* not the same child => no collision */ + other->destroy(other); + return; + } + } + else + { + /* any other task is not critical for collisisions, ignore */ + other->destroy(other); + return; + } + DESTROY_IF(this->collision); + this->collision = other; +} + +/** + * Implementation of task_t.migrate + */ +static void migrate(private_child_rekey_t *this, ike_sa_t *ike_sa) +{ + if (this->child_create) + { + this->child_create->task.migrate(&this->child_create->task, ike_sa); + } + if (this->child_delete) + { + this->child_delete->task.migrate(&this->child_delete->task, ike_sa); + } + DESTROY_IF(this->collision); + + this->ike_sa = ike_sa; + this->collision = NULL; +} + +/** + * Implementation of task_t.destroy + */ +static void destroy(private_child_rekey_t *this) +{ + if (this->child_create) + { + this->child_create->task.destroy(&this->child_create->task); + } + if (this->child_delete) + { + this->child_delete->task.destroy(&this->child_delete->task); + } + DESTROY_IF(this->collision); + free(this); +} + +/* + * Described in header. + */ +child_rekey_t *child_rekey_create(ike_sa_t *ike_sa, protocol_id_t protocol, + u_int32_t spi) +{ + private_child_rekey_t *this = malloc_thing(private_child_rekey_t); + + this->public.collide = (void (*)(child_rekey_t*,task_t*))collide; + this->public.task.get_type = (task_type_t(*)(task_t*))get_type; + this->public.task.migrate = (void(*)(task_t*,ike_sa_t*))migrate; + this->public.task.destroy = (void(*)(task_t*))destroy; + if (protocol != PROTO_NONE) + { + this->public.task.build = (status_t(*)(task_t*,message_t*))build_i; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_i; + this->initiator = TRUE; + this->child_create = NULL; + } + else + { + this->public.task.build = (status_t(*)(task_t*,message_t*))build_r; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_r; + this->initiator = FALSE; + this->child_create = child_create_create(ike_sa, NULL, TRUE, NULL, NULL); + } + + this->ike_sa = ike_sa; + this->child_sa = NULL; + this->protocol = protocol; + this->spi = spi; + this->collision = NULL; + this->child_delete = NULL; + + return &this->public; +} diff --git a/src/libcharon/sa/tasks/child_rekey.h b/src/libcharon/sa/tasks/child_rekey.h new file mode 100644 index 000000000..9b1aea5fa --- /dev/null +++ b/src/libcharon/sa/tasks/child_rekey.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2007 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 child_rekey child_rekey + * @{ @ingroup tasks + */ + +#ifndef CHILD_REKEY_H_ +#define CHILD_REKEY_H_ + +typedef struct child_rekey_t child_rekey_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/child_sa.h> +#include <sa/tasks/task.h> + +/** + * Task of type CHILD_REKEY, rekey an established CHILD_SA. + */ +struct child_rekey_t { + + /** + * Implements the task_t interface + */ + task_t task; + + /** + * Register a rekeying task which collides with this one + * + * If two peers initiate rekeying at the same time, the collision must + * be handled gracefully. The task manager is aware of what exchanges + * are going on and notifies the outgoing task by passing the incoming. + * + * @param other incoming task + */ + void (*collide)(child_rekey_t* this, task_t *other); +}; + +/** + * Create a new CHILD_REKEY task. + * + * @param ike_sa IKE_SA this task works for + * @param protocol protocol of CHILD_SA to rekey, PROTO_NONE as responder + * @param spi inbound SPI of CHILD_SA to rekey + * @return child_rekey task to handle by the task_manager + */ +child_rekey_t *child_rekey_create(ike_sa_t *ike_sa, protocol_id_t protocol, + u_int32_t spi); + +#endif /** CHILD_REKEY_H_ @}*/ diff --git a/src/libcharon/sa/tasks/ike_auth.c b/src/libcharon/sa/tasks/ike_auth.c new file mode 100644 index 000000000..a07f96767 --- /dev/null +++ b/src/libcharon/sa/tasks/ike_auth.c @@ -0,0 +1,1041 @@ +/* + * Copyright (C) 2005-2009 Martin Willi + * Copyright (C) 2005 Jan Hutter + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details + */ + +#include "ike_auth.h" + +#include <string.h> + +#include <daemon.h> +#include <encoding/payloads/id_payload.h> +#include <encoding/payloads/auth_payload.h> +#include <encoding/payloads/eap_payload.h> +#include <encoding/payloads/nonce_payload.h> +#include <sa/authenticators/eap_authenticator.h> + +typedef struct private_ike_auth_t private_ike_auth_t; + +/** + * Private members of a ike_auth_t task. + */ +struct private_ike_auth_t { + + /** + * Public methods and task_t interface. + */ + ike_auth_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; + + /** + * Are we the initiator? + */ + bool initiator; + + /** + * Nonce chosen by us in ike_init + */ + chunk_t my_nonce; + + /** + * Nonce chosen by peer in ike_init + */ + chunk_t other_nonce; + + /** + * IKE_SA_INIT message sent by us + */ + packet_t *my_packet; + + /** + * IKE_SA_INIT message sent by peer + */ + packet_t *other_packet; + + /** + * currently active authenticator, to authenticate us + */ + authenticator_t *my_auth; + + /** + * currently active authenticator, to authenticate peer + */ + authenticator_t *other_auth; + + /** + * peer_cfg candidates, ordered by priority + */ + linked_list_t *candidates; + + /** + * selected peer config (might change when using multiple authentications) + */ + peer_cfg_t *peer_cfg; + + /** + * have we planned an(other) authentication exchange? + */ + bool do_another_auth; + + /** + * has the peer announced another authentication exchange? + */ + bool expect_another_auth; + + /** + * should we send a AUTHENTICATION_FAILED notify? + */ + bool authentication_failed; +}; + +/** + * check if multiple authentication extension is enabled, configuration-wise + */ +static bool multiple_auth_enabled() +{ + return lib->settings->get_bool(lib->settings, + "charon.multiple_authentication", TRUE); +} + +/** + * collect the needed information in the IKE_SA_INIT exchange from our message + */ +static status_t collect_my_init_data(private_ike_auth_t *this, + message_t *message) +{ + nonce_payload_t *nonce; + + /* get the nonce that was generated in ike_init */ + nonce = (nonce_payload_t*)message->get_payload(message, NONCE); + if (nonce == NULL) + { + return FAILED; + } + this->my_nonce = nonce->get_nonce(nonce); + + /* pre-generate the message, keep a copy */ + if (this->ike_sa->generate_message(this->ike_sa, message, + &this->my_packet) != SUCCESS) + { + return FAILED; + } + return NEED_MORE; +} + +/** + * collect the needed information in the IKE_SA_INIT exchange from others message + */ +static status_t collect_other_init_data(private_ike_auth_t *this, + message_t *message) +{ + /* we collect the needed information in the IKE_SA_INIT exchange */ + nonce_payload_t *nonce; + + /* get the nonce that was generated in ike_init */ + nonce = (nonce_payload_t*)message->get_payload(message, NONCE); + if (nonce == NULL) + { + return FAILED; + } + this->other_nonce = nonce->get_nonce(nonce); + + /* keep a copy of the received packet */ + this->other_packet = message->get_packet(message); + return NEED_MORE; +} + +/** + * Get the next authentication configuration + */ +static auth_cfg_t *get_auth_cfg(private_ike_auth_t *this, bool local) +{ + enumerator_t *e1, *e2; + auth_cfg_t *c1, *c2, *next = NULL; + + /* find an available config not already done */ + e1 = this->peer_cfg->create_auth_cfg_enumerator(this->peer_cfg, local); + while (e1->enumerate(e1, &c1)) + { + bool found = FALSE; + + e2 = this->ike_sa->create_auth_cfg_enumerator(this->ike_sa, local); + while (e2->enumerate(e2, &c2)) + { + if (c2->complies(c2, c1, FALSE)) + { + found = TRUE; + break; + } + } + e2->destroy(e2); + if (!found) + { + next = c1; + break; + } + } + e1->destroy(e1); + return next; +} + +/** + * Check if we have should initiate another authentication round + */ +static bool do_another_auth(private_ike_auth_t *this) +{ + bool do_another = FALSE; + enumerator_t *done, *todo; + auth_cfg_t *done_cfg, *todo_cfg; + + if (!this->ike_sa->supports_extension(this->ike_sa, EXT_MULTIPLE_AUTH)) + { + return FALSE; + } + + done = this->ike_sa->create_auth_cfg_enumerator(this->ike_sa, TRUE); + todo = this->peer_cfg->create_auth_cfg_enumerator(this->peer_cfg, TRUE); + while (todo->enumerate(todo, &todo_cfg)) + { + if (!done->enumerate(done, &done_cfg)) + { + done_cfg = this->ike_sa->get_auth_cfg(this->ike_sa, TRUE); + } + if (!done_cfg->complies(done_cfg, todo_cfg, FALSE)) + { + do_another = TRUE; + break; + } + } + done->destroy(done); + todo->destroy(todo); + return do_another; +} + +/** + * Get peer configuration candidates from backends + */ +static bool load_cfg_candidates(private_ike_auth_t *this) +{ + enumerator_t *enumerator; + peer_cfg_t *peer_cfg; + host_t *me, *other; + identification_t *my_id, *other_id; + + me = this->ike_sa->get_my_host(this->ike_sa); + other = this->ike_sa->get_other_host(this->ike_sa); + my_id = this->ike_sa->get_my_id(this->ike_sa); + other_id = this->ike_sa->get_other_id(this->ike_sa); + + enumerator = charon->backends->create_peer_cfg_enumerator(charon->backends, + me, other, my_id, other_id); + while (enumerator->enumerate(enumerator, &peer_cfg)) + { + peer_cfg->get_ref(peer_cfg); + if (this->peer_cfg == NULL) + { /* best match */ + this->peer_cfg = peer_cfg; + this->ike_sa->set_peer_cfg(this->ike_sa, peer_cfg); + } + else + { + this->candidates->insert_last(this->candidates, peer_cfg); + } + } + enumerator->destroy(enumerator); + if (this->peer_cfg) + { + DBG1(DBG_CFG, "selected peer config '%s'", + this->peer_cfg->get_name(this->peer_cfg)); + return TRUE; + } + DBG1(DBG_CFG, "no matching peer config found"); + return FALSE; +} + +/** + * update the current peer candidate if necessary, using candidates + */ +static bool update_cfg_candidates(private_ike_auth_t *this, bool strict) +{ + do + { + if (this->peer_cfg) + { + bool complies = TRUE; + enumerator_t *e1, *e2, *tmp; + auth_cfg_t *c1, *c2; + + e1 = this->ike_sa->create_auth_cfg_enumerator(this->ike_sa, FALSE); + e2 = this->peer_cfg->create_auth_cfg_enumerator(this->peer_cfg, FALSE); + + if (strict) + { /* swap lists in strict mode: all configured rounds must be + * fulfilled. If !strict, we check only the rounds done so far. */ + tmp = e1; + e1 = e2; + e2 = tmp; + } + while (e1->enumerate(e1, &c1)) + { + /* check if done authentications comply to configured ones */ + if ((!e2->enumerate(e2, &c2)) || + (!strict && !c1->complies(c1, c2, TRUE)) || + (strict && !c2->complies(c2, c1, TRUE))) + { + complies = FALSE; + break; + } + } + e1->destroy(e1); + e2->destroy(e2); + if (complies) + { + break; + } + DBG1(DBG_CFG, "selected peer config '%s' inacceptable", + this->peer_cfg->get_name(this->peer_cfg)); + this->peer_cfg->destroy(this->peer_cfg); + } + if (this->candidates->remove_first(this->candidates, + (void**)&this->peer_cfg) != SUCCESS) + { + DBG1(DBG_CFG, "no alternative config found"); + this->peer_cfg = NULL; + } + else + { + DBG1(DBG_CFG, "switching to peer config '%s'", + this->peer_cfg->get_name(this->peer_cfg)); + this->ike_sa->set_peer_cfg(this->ike_sa, this->peer_cfg); + } + } + while (this->peer_cfg); + + return this->peer_cfg != NULL; +} + +/** + * Implementation of task_t.build for initiator + */ +static status_t build_i(private_ike_auth_t *this, message_t *message) +{ + auth_cfg_t *cfg; + + if (message->get_exchange_type(message) == IKE_SA_INIT) + { + return collect_my_init_data(this, message); + } + + if (this->peer_cfg == NULL) + { + this->peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); + this->peer_cfg->get_ref(this->peer_cfg); + } + + if (message->get_message_id(message) == 1) + { /* in the first IKE_AUTH ... */ + if (this->ike_sa->supports_extension(this->ike_sa, EXT_MULTIPLE_AUTH)) + { /* indicate support for multiple authentication */ + message->add_notify(message, FALSE, MULTIPLE_AUTH_SUPPORTED, + chunk_empty); + } + /* indicate support for EAP-only authentication */ + message->add_notify(message, FALSE, EAP_ONLY_AUTHENTICATION, + chunk_empty); + } + + if (!this->do_another_auth && !this->my_auth) + { /* we have done our rounds */ + return NEED_MORE; + } + + /* check if an authenticator is in progress */ + if (this->my_auth == NULL) + { + identification_t *id; + id_payload_t *id_payload; + + /* clean up authentication config from a previous round */ + cfg = this->ike_sa->get_auth_cfg(this->ike_sa, TRUE); + cfg->purge(cfg, TRUE); + + /* add (optional) IDr */ + cfg = get_auth_cfg(this, FALSE); + if (cfg) + { + id = cfg->get(cfg, AUTH_RULE_IDENTITY); + if (id && !id->contains_wildcards(id)) + { + this->ike_sa->set_other_id(this->ike_sa, id->clone(id)); + id_payload = id_payload_create_from_identification( + ID_RESPONDER, id); + message->add_payload(message, (payload_t*)id_payload); + } + } + /* add IDi */ + cfg = this->ike_sa->get_auth_cfg(this->ike_sa, TRUE); + cfg->merge(cfg, get_auth_cfg(this, TRUE), TRUE); + id = cfg->get(cfg, AUTH_RULE_IDENTITY); + if (!id) + { + DBG1(DBG_CFG, "configuration misses IDi"); + return FAILED; + } + this->ike_sa->set_my_id(this->ike_sa, id->clone(id)); + id_payload = id_payload_create_from_identification(ID_INITIATOR, id); + message->add_payload(message, (payload_t*)id_payload); + + /* build authentication data */ + this->my_auth = authenticator_create_builder(this->ike_sa, cfg, + this->other_nonce, this->my_nonce, + this->other_packet->get_data(this->other_packet), + this->my_packet->get_data(this->my_packet)); + if (!this->my_auth) + { + return FAILED; + } + } + switch (this->my_auth->build(this->my_auth, message)) + { + case SUCCESS: + /* authentication step complete, reset authenticator */ + cfg = auth_cfg_create(); + cfg->merge(cfg, this->ike_sa->get_auth_cfg(this->ike_sa, TRUE), TRUE); + this->ike_sa->add_auth_cfg(this->ike_sa, TRUE, cfg); + this->my_auth->destroy(this->my_auth); + this->my_auth = NULL; + break; + case NEED_MORE: + break; + default: + return FAILED; + } + + /* check for additional authentication rounds */ + if (do_another_auth(this)) + { + if (message->get_payload(message, AUTHENTICATION)) + { + message->add_notify(message, FALSE, ANOTHER_AUTH_FOLLOWS, chunk_empty); + } + } + else + { + this->do_another_auth = FALSE; + } + return NEED_MORE; +} + +/** + * Implementation of task_t.process for responder + */ +static status_t process_r(private_ike_auth_t *this, message_t *message) +{ + auth_cfg_t *cfg, *cand; + id_payload_t *id_payload; + identification_t *id; + + if (message->get_exchange_type(message) == IKE_SA_INIT) + { + return collect_other_init_data(this, message); + } + + if (this->my_auth == NULL && this->do_another_auth) + { + /* handle (optional) IDr payload, apply proposed identity */ + id_payload = (id_payload_t*)message->get_payload(message, ID_RESPONDER); + if (id_payload) + { + id = id_payload->get_identification(id_payload); + } + else + { + id = identification_create_from_encoding(ID_ANY, chunk_empty); + } + this->ike_sa->set_my_id(this->ike_sa, id); + } + + if (!this->expect_another_auth) + { + return NEED_MORE; + } + + if (message->get_message_id(message) == 1) + { /* check for extensions in the first IKE_AUTH */ + if (message->get_notify(message, MULTIPLE_AUTH_SUPPORTED)) + { + this->ike_sa->enable_extension(this->ike_sa, EXT_MULTIPLE_AUTH); + } + if (this->ike_sa->supports_extension(this->ike_sa, EXT_STRONGSWAN) && + message->get_notify(message, EAP_ONLY_AUTHENTICATION)) + { /* EAP-only has no official notify, accept only from strongSwan */ + this->ike_sa->enable_extension(this->ike_sa, + EXT_EAP_ONLY_AUTHENTICATION); + } + } + + if (this->other_auth == NULL) + { + /* handle IDi payload */ + id_payload = (id_payload_t*)message->get_payload(message, ID_INITIATOR); + if (!id_payload) + { + DBG1(DBG_IKE, "IDi payload missing"); + return FAILED; + } + id = id_payload->get_identification(id_payload); + this->ike_sa->set_other_id(this->ike_sa, id); + cfg = this->ike_sa->get_auth_cfg(this->ike_sa, FALSE); + cfg->add(cfg, AUTH_RULE_IDENTITY, id->clone(id)); + + if (this->peer_cfg == NULL) + { + if (!load_cfg_candidates(this)) + { + this->authentication_failed = TRUE; + return NEED_MORE; + } + } + if (message->get_payload(message, AUTHENTICATION) == NULL) + { /* before authenticating with EAP, we need a EAP config */ + cand = get_auth_cfg(this, FALSE); + while (!cand || ( + (uintptr_t)cand->get(cand, AUTH_RULE_EAP_TYPE) == EAP_NAK && + (uintptr_t)cand->get(cand, AUTH_RULE_EAP_VENDOR) == 0)) + { /* peer requested EAP, but current config does not match */ + this->peer_cfg->destroy(this->peer_cfg); + this->peer_cfg = NULL; + if (!update_cfg_candidates(this, FALSE)) + { + this->authentication_failed = TRUE; + return NEED_MORE; + } + cand = get_auth_cfg(this, FALSE); + } + cfg->merge(cfg, cand, TRUE); + } + + /* verify authentication data */ + this->other_auth = authenticator_create_verifier(this->ike_sa, + message, this->other_nonce, this->my_nonce, + this->other_packet->get_data(this->other_packet), + this->my_packet->get_data(this->my_packet)); + if (!this->other_auth) + { + this->authentication_failed = TRUE; + return NEED_MORE; + } + } + switch (this->other_auth->process(this->other_auth, message)) + { + case SUCCESS: + this->other_auth->destroy(this->other_auth); + this->other_auth = NULL; + break; + case NEED_MORE: + if (message->get_payload(message, AUTHENTICATION)) + { /* AUTH verification successful, but another build() needed */ + break; + } + return NEED_MORE; + default: + this->authentication_failed = TRUE; + return NEED_MORE; + } + + /* store authentication information */ + cfg = auth_cfg_create(); + cfg->merge(cfg, this->ike_sa->get_auth_cfg(this->ike_sa, FALSE), FALSE); + this->ike_sa->add_auth_cfg(this->ike_sa, FALSE, cfg); + + /* another auth round done, invoke authorize hook */ + if (!charon->bus->authorize(charon->bus, FALSE)) + { + DBG1(DBG_IKE, "authorization hook forbids IKE_SA, cancelling"); + this->authentication_failed = TRUE; + return NEED_MORE; + } + + if (!update_cfg_candidates(this, FALSE)) + { + this->authentication_failed = TRUE; + return NEED_MORE; + } + + if (message->get_notify(message, ANOTHER_AUTH_FOLLOWS) == NULL) + { + this->expect_another_auth = FALSE; + if (!update_cfg_candidates(this, TRUE)) + { + this->authentication_failed = TRUE; + return NEED_MORE; + } + } + return NEED_MORE; +} + +/** + * Implementation of task_t.build for responder + */ +static status_t build_r(private_ike_auth_t *this, message_t *message) +{ + auth_cfg_t *cfg; + + if (message->get_exchange_type(message) == IKE_SA_INIT) + { + if (multiple_auth_enabled()) + { + message->add_notify(message, FALSE, MULTIPLE_AUTH_SUPPORTED, + chunk_empty); + } + return collect_my_init_data(this, message); + } + + if (this->authentication_failed || this->peer_cfg == NULL) + { + message->add_notify(message, TRUE, AUTHENTICATION_FAILED, chunk_empty); + return FAILED; + } + + if (this->my_auth == NULL && this->do_another_auth) + { + identification_t *id, *id_cfg; + id_payload_t *id_payload; + + /* add IDr */ + cfg = this->ike_sa->get_auth_cfg(this->ike_sa, TRUE); + cfg->purge(cfg, TRUE); + cfg->merge(cfg, get_auth_cfg(this, TRUE), TRUE); + + id_cfg = cfg->get(cfg, AUTH_RULE_IDENTITY); + id = this->ike_sa->get_my_id(this->ike_sa); + if (id->get_type(id) == ID_ANY) + { /* no IDr received, apply configured ID */ + if (!id_cfg || id_cfg->contains_wildcards(id_cfg)) + { + DBG1(DBG_CFG, "IDr not configured and negotiation failed"); + message->add_notify(message, TRUE, AUTHENTICATION_FAILED, + chunk_empty); + return FAILED; + } + this->ike_sa->set_my_id(this->ike_sa, id_cfg->clone(id_cfg)); + id = id_cfg; + } + else + { /* IDr received, check if it matches configuration */ + if (id_cfg && !id->matches(id, id_cfg)) + { + DBG1(DBG_CFG, "received IDr %Y, but require %Y", id, id_cfg); + message->add_notify(message, TRUE, AUTHENTICATION_FAILED, + chunk_empty); + return FAILED; + } + } + + id_payload = id_payload_create_from_identification(ID_RESPONDER, id); + message->add_payload(message, (payload_t*)id_payload); + + if ((uintptr_t)cfg->get(cfg, AUTH_RULE_AUTH_CLASS) == AUTH_CLASS_EAP) + { /* EAP-only authentication */ + if (!this->ike_sa->supports_extension(this->ike_sa, + EXT_EAP_ONLY_AUTHENTICATION)) + { + DBG1(DBG_IKE, "configured EAP-only authentication, but peer " + "does not support it"); + message->add_notify(message, TRUE, AUTHENTICATION_FAILED, + chunk_empty); + return FAILED; + } + } + else + { + /* build authentication data */ + this->my_auth = authenticator_create_builder(this->ike_sa, cfg, + this->other_nonce, this->my_nonce, + this->other_packet->get_data(this->other_packet), + this->my_packet->get_data(this->my_packet)); + if (!this->my_auth) + { + message->add_notify(message, TRUE, AUTHENTICATION_FAILED, + chunk_empty); + return FAILED; + } + } + } + + if (this->other_auth) + { + switch (this->other_auth->build(this->other_auth, message)) + { + case SUCCESS: + this->other_auth->destroy(this->other_auth); + this->other_auth = NULL; + break; + case NEED_MORE: + break; + default: + if (!message->get_payload(message, EXTENSIBLE_AUTHENTICATION)) + { /* skip AUTHENTICATION_FAILED if we have EAP_FAILURE */ + message->add_notify(message, TRUE, AUTHENTICATION_FAILED, + chunk_empty); + } + return FAILED; + } + } + if (this->my_auth) + { + switch (this->my_auth->build(this->my_auth, message)) + { + case SUCCESS: + cfg = auth_cfg_create(); + cfg->merge(cfg, this->ike_sa->get_auth_cfg(this->ike_sa, TRUE), + TRUE); + this->ike_sa->add_auth_cfg(this->ike_sa, TRUE, cfg); + this->my_auth->destroy(this->my_auth); + this->my_auth = NULL; + break; + case NEED_MORE: + break; + default: + message->add_notify(message, TRUE, AUTHENTICATION_FAILED, + chunk_empty); + return FAILED; + } + } + + /* check for additional authentication rounds */ + if (do_another_auth(this)) + { + message->add_notify(message, FALSE, ANOTHER_AUTH_FOLLOWS, chunk_empty); + } + else + { + this->do_another_auth = FALSE; + } + if (!this->do_another_auth && !this->expect_another_auth) + { + if (charon->ike_sa_manager->check_uniqueness(charon->ike_sa_manager, + this->ike_sa)) + { + DBG1(DBG_IKE, "cancelling IKE_SA setup due uniqueness policy"); + message->add_notify(message, TRUE, AUTHENTICATION_FAILED, + chunk_empty); + return FAILED; + } + if (!charon->bus->authorize(charon->bus, TRUE)) + { + DBG1(DBG_IKE, "final authorization hook forbids IKE_SA, cancelling"); + message->add_notify(message, TRUE, AUTHENTICATION_FAILED, + chunk_empty); + return FAILED; + } + DBG0(DBG_IKE, "IKE_SA %s[%d] established between %H[%Y]...%H[%Y]", + this->ike_sa->get_name(this->ike_sa), + this->ike_sa->get_unique_id(this->ike_sa), + this->ike_sa->get_my_host(this->ike_sa), + this->ike_sa->get_my_id(this->ike_sa), + this->ike_sa->get_other_host(this->ike_sa), + this->ike_sa->get_other_id(this->ike_sa)); + this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED); + charon->bus->ike_updown(charon->bus, this->ike_sa, TRUE); + return SUCCESS; + } + return NEED_MORE; +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t process_i(private_ike_auth_t *this, message_t *message) +{ + enumerator_t *enumerator; + payload_t *payload; + auth_cfg_t *cfg; + bool mutual_eap = FALSE; + + if (message->get_exchange_type(message) == IKE_SA_INIT) + { + if (message->get_notify(message, MULTIPLE_AUTH_SUPPORTED) && + multiple_auth_enabled()) + { + this->ike_sa->enable_extension(this->ike_sa, EXT_MULTIPLE_AUTH); + } + return collect_other_init_data(this, message); + } + + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + if (payload->get_type(payload) == NOTIFY) + { + notify_payload_t *notify = (notify_payload_t*)payload; + notify_type_t type = notify->get_notify_type(notify); + + switch (type) + { + case NO_PROPOSAL_CHOSEN: + case SINGLE_PAIR_REQUIRED: + case NO_ADDITIONAL_SAS: + case INTERNAL_ADDRESS_FAILURE: + case FAILED_CP_REQUIRED: + case TS_UNACCEPTABLE: + case INVALID_SELECTORS: + /* these are errors, but are not critical as only the + * CHILD_SA won't get build, but IKE_SA establishes anyway */ + break; + case MOBIKE_SUPPORTED: + case ADDITIONAL_IP4_ADDRESS: + case ADDITIONAL_IP6_ADDRESS: + /* handled in ike_mobike task */ + break; + case AUTH_LIFETIME: + /* handled in ike_auth_lifetime task */ + break; + case ME_ENDPOINT: + /* handled in ike_me task */ + break; + default: + { + if (type < 16383) + { + DBG1(DBG_IKE, "received %N notify error", + notify_type_names, type); + enumerator->destroy(enumerator); + return FAILED; + } + DBG2(DBG_IKE, "received %N notify", + notify_type_names, type); + break; + } + } + } + } + enumerator->destroy(enumerator); + + if (this->expect_another_auth) + { + if (this->other_auth == NULL) + { + id_payload_t *id_payload; + identification_t *id; + + /* handle IDr payload */ + id_payload = (id_payload_t*)message->get_payload(message, + ID_RESPONDER); + if (!id_payload) + { + DBG1(DBG_IKE, "IDr payload missing"); + return FAILED; + } + id = id_payload->get_identification(id_payload); + this->ike_sa->set_other_id(this->ike_sa, id); + cfg = this->ike_sa->get_auth_cfg(this->ike_sa, FALSE); + cfg->add(cfg, AUTH_RULE_IDENTITY, id->clone(id)); + + if (message->get_payload(message, AUTHENTICATION)) + { + /* verify authentication data */ + this->other_auth = authenticator_create_verifier(this->ike_sa, + message, this->other_nonce, this->my_nonce, + this->other_packet->get_data(this->other_packet), + this->my_packet->get_data(this->my_packet)); + if (!this->other_auth) + { + return FAILED; + } + } + else + { + /* responder omitted AUTH payload, indicating EAP-only */ + mutual_eap = TRUE; + } + } + if (this->other_auth) + { + switch (this->other_auth->process(this->other_auth, message)) + { + case SUCCESS: + break; + case NEED_MORE: + return NEED_MORE; + default: + return FAILED; + } + this->other_auth->destroy(this->other_auth); + this->other_auth = NULL; + } + /* store authentication information, reset authenticator */ + cfg = auth_cfg_create(); + cfg->merge(cfg, this->ike_sa->get_auth_cfg(this->ike_sa, FALSE), FALSE); + this->ike_sa->add_auth_cfg(this->ike_sa, FALSE, cfg); + + /* another auth round done, invoke authorize hook */ + if (!charon->bus->authorize(charon->bus, FALSE)) + { + DBG1(DBG_IKE, "authorization forbids IKE_SA, cancelling"); + return FAILED; + } + } + + if (this->my_auth) + { + switch (this->my_auth->process(this->my_auth, message)) + { + case SUCCESS: + cfg = auth_cfg_create(); + cfg->merge(cfg, this->ike_sa->get_auth_cfg(this->ike_sa, TRUE), + TRUE); + this->ike_sa->add_auth_cfg(this->ike_sa, TRUE, cfg); + this->my_auth->destroy(this->my_auth); + this->my_auth = NULL; + this->do_another_auth = do_another_auth(this); + break; + case NEED_MORE: + break; + default: + return FAILED; + } + } + if (mutual_eap) + { + if (!this->my_auth || !this->my_auth->is_mutual(this->my_auth)) + { + DBG1(DBG_IKE, "do not allow non-mutual EAP-only authentication"); + return FAILED; + } + DBG1(DBG_IKE, "allow mutual EAP-only authentication"); + } + + if (message->get_notify(message, ANOTHER_AUTH_FOLLOWS) == NULL) + { + this->expect_another_auth = FALSE; + } + if (!this->expect_another_auth && !this->do_another_auth && !this->my_auth) + { + if (!update_cfg_candidates(this, TRUE)) + { + return FAILED; + } + if (!charon->bus->authorize(charon->bus, TRUE)) + { + DBG1(DBG_IKE, "final authorization hook forbids IKE_SA, cancelling"); + return FAILED; + } + DBG0(DBG_IKE, "IKE_SA %s[%d] established between %H[%Y]...%H[%Y]", + this->ike_sa->get_name(this->ike_sa), + this->ike_sa->get_unique_id(this->ike_sa), + this->ike_sa->get_my_host(this->ike_sa), + this->ike_sa->get_my_id(this->ike_sa), + this->ike_sa->get_other_host(this->ike_sa), + this->ike_sa->get_other_id(this->ike_sa)); + this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED); + charon->bus->ike_updown(charon->bus, this->ike_sa, TRUE); + return SUCCESS; + } + return NEED_MORE; +} + +/** + * Implementation of task_t.get_type + */ +static task_type_t get_type(private_ike_auth_t *this) +{ + return IKE_AUTHENTICATE; +} + +/** + * Implementation of task_t.migrate + */ +static void migrate(private_ike_auth_t *this, ike_sa_t *ike_sa) +{ + chunk_free(&this->my_nonce); + chunk_free(&this->other_nonce); + DESTROY_IF(this->my_packet); + DESTROY_IF(this->other_packet); + DESTROY_IF(this->peer_cfg); + DESTROY_IF(this->my_auth); + DESTROY_IF(this->other_auth); + this->candidates->destroy_offset(this->candidates, offsetof(peer_cfg_t, destroy)); + + this->my_packet = NULL; + this->other_packet = NULL; + this->ike_sa = ike_sa; + this->peer_cfg = NULL; + this->my_auth = NULL; + this->other_auth = NULL; + this->do_another_auth = TRUE; + this->expect_another_auth = TRUE; + this->authentication_failed = FALSE; + this->candidates = linked_list_create(); +} + +/** + * Implementation of task_t.destroy + */ +static void destroy(private_ike_auth_t *this) +{ + chunk_free(&this->my_nonce); + chunk_free(&this->other_nonce); + DESTROY_IF(this->my_packet); + DESTROY_IF(this->other_packet); + DESTROY_IF(this->my_auth); + DESTROY_IF(this->other_auth); + DESTROY_IF(this->peer_cfg); + this->candidates->destroy_offset(this->candidates, offsetof(peer_cfg_t, destroy)); + free(this); +} + +/* + * Described in header. + */ +ike_auth_t *ike_auth_create(ike_sa_t *ike_sa, bool initiator) +{ + private_ike_auth_t *this = malloc_thing(private_ike_auth_t); + + this->public.task.get_type = (task_type_t(*)(task_t*))get_type; + this->public.task.migrate = (void(*)(task_t*,ike_sa_t*))migrate; + this->public.task.destroy = (void(*)(task_t*))destroy; + + if (initiator) + { + this->public.task.build = (status_t(*)(task_t*,message_t*))build_i; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_i; + } + else + { + this->public.task.build = (status_t(*)(task_t*,message_t*))build_r; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_r; + } + + this->ike_sa = ike_sa; + this->initiator = initiator; + this->my_nonce = chunk_empty; + this->other_nonce = chunk_empty; + this->my_packet = NULL; + this->other_packet = NULL; + this->peer_cfg = NULL; + this->candidates = linked_list_create(); + this->my_auth = NULL; + this->other_auth = NULL; + this->do_another_auth = TRUE; + this->expect_another_auth = TRUE; + this->authentication_failed = FALSE; + + return &this->public; +} + diff --git a/src/libcharon/sa/tasks/ike_auth.h b/src/libcharon/sa/tasks/ike_auth.h new file mode 100644 index 000000000..bba46d961 --- /dev/null +++ b/src/libcharon/sa/tasks/ike_auth.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2007 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 ike_auth ike_auth + * @{ @ingroup tasks + */ + +#ifndef IKE_AUTH_H_ +#define IKE_AUTH_H_ + +typedef struct ike_auth_t ike_auth_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/tasks/task.h> + +/** + * Task of type ike_auth, authenticates an IKE_SA using authenticators. + * + * The ike_auth task authenticates the IKE_SA using the IKE_AUTH + * exchange. It processes and build IDi and IDr payloads and also + * handles AUTH payloads. The AUTH payloads are passed to authenticator_t's, + * which do the actual authentication process. If the ike_auth task is used + * with EAP authentication, it stays alive over multiple exchanges until + * EAP has completed. + */ +struct ike_auth_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * Create a new task of type IKE_AUTHENTICATE. + * + * @param ike_sa IKE_SA this task works for + * @param initiator TRUE if thask is the initator of an exchange + * @return ike_auth task to handle by the task_manager + */ +ike_auth_t *ike_auth_create(ike_sa_t *ike_sa, bool initiator); + +#endif /** IKE_AUTH_H_ @}*/ diff --git a/src/libcharon/sa/tasks/ike_auth_lifetime.c b/src/libcharon/sa/tasks/ike_auth_lifetime.c new file mode 100644 index 000000000..75ff35168 --- /dev/null +++ b/src/libcharon/sa/tasks/ike_auth_lifetime.c @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2007 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 "ike_auth_lifetime.h" + +#include <time.h> + +#include <daemon.h> +#include <encoding/payloads/notify_payload.h> + + +typedef struct private_ike_auth_lifetime_t private_ike_auth_lifetime_t; + +/** + * Private members of a ike_auth_lifetime_t task. + */ +struct private_ike_auth_lifetime_t { + + /** + * Public methods and task_t interface. + */ + ike_auth_lifetime_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; +}; + +/** + * add the AUTH_LIFETIME notify to the message + */ +static void add_auth_lifetime(private_ike_auth_lifetime_t *this, message_t *message) +{ + chunk_t chunk; + u_int32_t lifetime; + + lifetime = this->ike_sa->get_statistic(this->ike_sa, STAT_REAUTH); + if (lifetime) + { + lifetime -= time_monotonic(NULL); + chunk = chunk_from_thing(lifetime); + *(u_int32_t*)chunk.ptr = htonl(lifetime); + message->add_notify(message, FALSE, AUTH_LIFETIME, chunk); + } +} + +/** + * read notifys from message and evaluate them + */ +static void process_payloads(private_ike_auth_lifetime_t *this, message_t *message) +{ + notify_payload_t *notify; + chunk_t data; + u_int32_t lifetime; + + notify = message->get_notify(message, AUTH_LIFETIME); + if (notify) + { + data = notify->get_notification_data(notify); + lifetime = ntohl(*(u_int32_t*)data.ptr); + this->ike_sa->set_auth_lifetime(this->ike_sa, lifetime); + } +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t build_i(private_ike_auth_lifetime_t *this, message_t *message) +{ + if (message->get_exchange_type(message) == INFORMATIONAL) + { + add_auth_lifetime(this, message); + return SUCCESS; + } + return NEED_MORE; +} + +/** + * Implementation of task_t.process for responder + */ +static status_t process_r(private_ike_auth_lifetime_t *this, message_t *message) +{ + if (message->get_exchange_type(message) == INFORMATIONAL) + { + process_payloads(this, message); + return SUCCESS; + } + return NEED_MORE; +} + +/** + * Implementation of task_t.build for responder + */ +static status_t build_r(private_ike_auth_lifetime_t *this, message_t *message) +{ + if (message->get_exchange_type(message) == IKE_AUTH && + this->ike_sa->get_state(this->ike_sa) == IKE_ESTABLISHED) + { + add_auth_lifetime(this, message); + return SUCCESS; + } + return NEED_MORE; +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t process_i(private_ike_auth_lifetime_t *this, message_t *message) +{ + if (message->get_exchange_type(message) == IKE_AUTH && + this->ike_sa->get_state(this->ike_sa) == IKE_ESTABLISHED) + { + process_payloads(this, message); + return SUCCESS; + } + return NEED_MORE; +} + +/** + * Implementation of task_t.get_type + */ +static task_type_t get_type(private_ike_auth_lifetime_t *this) +{ + return IKE_AUTH_LIFETIME; +} + +/** + * Implementation of task_t.migrate + */ +static void migrate(private_ike_auth_lifetime_t *this, ike_sa_t *ike_sa) +{ + this->ike_sa = ike_sa; +} + +/** + * Implementation of task_t.destroy + */ +static void destroy(private_ike_auth_lifetime_t *this) +{ + free(this); +} + +/* + * Described in header. + */ +ike_auth_lifetime_t *ike_auth_lifetime_create(ike_sa_t *ike_sa, bool initiator) +{ + private_ike_auth_lifetime_t *this = malloc_thing(private_ike_auth_lifetime_t); + + this->public.task.get_type = (task_type_t(*)(task_t*))get_type; + this->public.task.migrate = (void(*)(task_t*,ike_sa_t*))migrate; + this->public.task.destroy = (void(*)(task_t*))destroy; + + if (initiator) + { + this->public.task.build = (status_t(*)(task_t*,message_t*))build_i; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_i; + } + else + { + this->public.task.build = (status_t(*)(task_t*,message_t*))build_r; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_r; + } + + this->ike_sa = ike_sa; + + return &this->public; +} + diff --git a/src/libcharon/sa/tasks/ike_auth_lifetime.h b/src/libcharon/sa/tasks/ike_auth_lifetime.h new file mode 100644 index 000000000..3b129b9e3 --- /dev/null +++ b/src/libcharon/sa/tasks/ike_auth_lifetime.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2007 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 ike_auth_lifetime ike_auth_lifetime + * @{ @ingroup tasks + */ + +#ifndef IKE_AUTH_LIFETIME_H_ +#define IKE_AUTH_LIFETIME_H_ + +typedef struct ike_auth_lifetime_t ike_auth_lifetime_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/tasks/task.h> + +/** + * Task of type IKE_AUTH_LIFETIME, implements RFC4478. + * + * This task exchanges lifetimes for IKE_AUTH to force a client to + * reauthenticate before the responders lifetime reaches the limit. + */ +struct ike_auth_lifetime_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * Create a new IKE_AUTH_LIFETIME task. + * + * @param ike_sa IKE_SA this task works for + * @param initiator TRUE if taks is initiated by us + * @return ike_auth_lifetime task to handle by the task_manager + */ +ike_auth_lifetime_t *ike_auth_lifetime_create(ike_sa_t *ike_sa, bool initiator); + +#endif /** IKE_MOBIKE_H_ @}*/ diff --git a/src/libcharon/sa/tasks/ike_cert_post.c b/src/libcharon/sa/tasks/ike_cert_post.c new file mode 100644 index 000000000..c831df975 --- /dev/null +++ b/src/libcharon/sa/tasks/ike_cert_post.c @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2008 Tobias Brunner + * Copyright (C) 2006-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 "ike_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 <credentials/certificates/x509.h> + + +typedef struct private_ike_cert_post_t private_ike_cert_post_t; + +/** + * Private members of a ike_cert_post_t task. + */ +struct private_ike_cert_post_t { + + /** + * Public methods and task_t interface. + */ + ike_cert_post_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; + + /** + * Are we the initiator? + */ + bool initiator; +}; + +/** + * Generates the cert payload, if possible with "Hash and URL" + */ +static cert_payload_t *build_cert_payload(private_ike_cert_post_t *this, + certificate_t *cert) +{ + hasher_t *hasher; + identification_t *id; + chunk_t hash, encoded ; + enumerator_t *enumerator; + char *url; + cert_payload_t *payload = NULL; + + if (!this->ike_sa->supports_extension(this->ike_sa, EXT_HASH_AND_URL)) + { + return cert_payload_create_from_cert(cert); + } + + hasher = lib->crypto->create_hasher(lib->crypto, HASH_SHA1); + if (!hasher) + { + DBG1(DBG_IKE, "unable to use hash-and-url: sha1 not supported"); + return cert_payload_create_from_cert(cert); + } + + encoded = cert->get_encoding(cert); + hasher->allocate_hash(hasher, encoded, &hash); + chunk_free(&encoded); + hasher->destroy(hasher); + id = identification_create_from_encoding(ID_KEY_ID, hash); + + enumerator = charon->credentials->create_cdp_enumerator(charon->credentials, + CERT_X509, id); + if (enumerator->enumerate(enumerator, &url)) + { + payload = cert_payload_create_from_hash_and_url(hash, url); + } + else + { + payload = cert_payload_create_from_cert(cert); + } + enumerator->destroy(enumerator); + chunk_free(&hash); + id->destroy(id); + return payload; +} + +/** + * add certificates to message + */ +static void build_certs(private_ike_cert_post_t *this, message_t *message) +{ + peer_cfg_t *peer_cfg; + auth_payload_t *payload; + + payload = (auth_payload_t*)message->get_payload(message, AUTHENTICATION); + peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); + if (!peer_cfg || !payload || payload->get_auth_method(payload) == AUTH_PSK) + { /* no CERT payload for EAP/PSK */ + 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); + + /* get subject cert first, then issuing certificates */ + cert = auth->get(auth, AUTH_RULE_SUBJECT_CERT); + if (!cert) + { + break; + } + payload = build_cert_payload(this, 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(cert); + if (payload) + { + DBG1(DBG_IKE, "sending issuer cert \"%Y\"", + cert->get_subject(cert)); + message->add_payload(message, (payload_t*)payload); + } + } + } + enumerator->destroy(enumerator); + } + } +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t build_i(private_ike_cert_post_t *this, message_t *message) +{ + build_certs(this, message); + + return NEED_MORE; +} + +/** + * Implementation of task_t.process for responder + */ +static status_t process_r(private_ike_cert_post_t *this, message_t *message) +{ + return NEED_MORE; +} + +/** + * Implementation of task_t.build for responder + */ +static status_t build_r(private_ike_cert_post_t *this, message_t *message) +{ + build_certs(this, message); + + if (this->ike_sa->get_state(this->ike_sa) != IKE_ESTABLISHED) + { /* stay alive, we might have additional rounds with certs */ + return NEED_MORE; + } + return SUCCESS; +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t process_i(private_ike_cert_post_t *this, message_t *message) +{ + if (this->ike_sa->get_state(this->ike_sa) != IKE_ESTABLISHED) + { /* stay alive, we might have additional rounds with CERTS */ + return NEED_MORE; + } + return SUCCESS; +} + +/** + * Implementation of task_t.get_type + */ +static task_type_t get_type(private_ike_cert_post_t *this) +{ + return IKE_CERT_POST; +} + +/** + * Implementation of task_t.migrate + */ +static void migrate(private_ike_cert_post_t *this, ike_sa_t *ike_sa) +{ + this->ike_sa = ike_sa; +} + +/** + * Implementation of task_t.destroy + */ +static void destroy(private_ike_cert_post_t *this) +{ + free(this); +} + +/* + * Described in header. + */ +ike_cert_post_t *ike_cert_post_create(ike_sa_t *ike_sa, bool initiator) +{ + private_ike_cert_post_t *this = malloc_thing(private_ike_cert_post_t); + + this->public.task.get_type = (task_type_t(*)(task_t*))get_type; + this->public.task.migrate = (void(*)(task_t*,ike_sa_t*))migrate; + this->public.task.destroy = (void(*)(task_t*))destroy; + + if (initiator) + { + this->public.task.build = (status_t(*)(task_t*,message_t*))build_i; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_i; + } + else + { + this->public.task.build = (status_t(*)(task_t*,message_t*))build_r; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_r; + } + + this->ike_sa = ike_sa; + this->initiator = initiator; + + return &this->public; +} + diff --git a/src/libcharon/sa/tasks/ike_cert_post.h b/src/libcharon/sa/tasks/ike_cert_post.h new file mode 100644 index 000000000..a21f45927 --- /dev/null +++ b/src/libcharon/sa/tasks/ike_cert_post.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2007-2008 Martin Willi + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup ike_cert_post ike_cert_post + * @{ @ingroup tasks + */ + +#ifndef IKE_CERT_POST_H_ +#define IKE_CERT_POST_H_ + +typedef struct ike_cert_post_t ike_cert_post_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/tasks/task.h> + +/** + * Task of type ike_cert_post, certificate processing after authentication. + */ +struct ike_cert_post_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * Create a new ike_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 thask is the original initator + * @return ike_cert_post task to handle by the task_manager + */ +ike_cert_post_t *ike_cert_post_create(ike_sa_t *ike_sa, bool initiator); + +#endif /** IKE_CERT_POST_H_ @}*/ diff --git a/src/libcharon/sa/tasks/ike_cert_pre.c b/src/libcharon/sa/tasks/ike_cert_pre.c new file mode 100644 index 000000000..0805d0290 --- /dev/null +++ b/src/libcharon/sa/tasks/ike_cert_pre.c @@ -0,0 +1,524 @@ +/* + * Copyright (C) 2008 Tobias Brunner + * Copyright (C) 2006-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 "ike_cert_pre.h" + +#include <daemon.h> +#include <sa/ike_sa.h> +#include <encoding/payloads/cert_payload.h> +#include <encoding/payloads/certreq_payload.h> +#include <credentials/certificates/x509.h> + + +typedef struct private_ike_cert_pre_t private_ike_cert_pre_t; + +/** + * Private members of a ike_cert_pre_t task. + */ +struct private_ike_cert_pre_t { + + /** + * Public methods and task_t interface. + */ + ike_cert_pre_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; + + /** + * Are we the initiator? + */ + bool initiator; + + /** + * Do we accept HTTP certificate lookup requests + */ + bool do_http_lookup; + + /** + * wheter this is the final authentication round + */ + bool final; +}; + +/** + * read certificate requests + */ +static void process_certreqs(private_ike_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: + { + certreq_payload_t *certreq = (certreq_payload_t*)payload; + enumerator_t *enumerator; + chunk_t keyid; + + this->ike_sa->set_condition(this->ike_sa, COND_CERTREQ_SEEN, TRUE); + + if (certreq->get_cert_type(certreq) != CERT_X509) + { + DBG1(DBG_IKE, "cert payload %N not supported - ignored", + certificate_type_names, certreq->get_cert_type(certreq)); + break; + } + enumerator = certreq->create_keyid_enumerator(certreq); + while (enumerator->enumerate(enumerator, &keyid)) + { + identification_t *id; + certificate_t *cert; + + id = identification_create_from_encoding(ID_KEY_ID, keyid); + cert = charon->credentials->get_cert(charon->credentials, + CERT_X509, KEY_ANY, id, TRUE); + if (cert) + { + DBG1(DBG_IKE, "received cert request for \"%Y\"", + cert->get_subject(cert)); + auth->add(auth, AUTH_RULE_CA_CERT, cert); + } + else + { + DBG1(DBG_IKE, "received cert request for unknown ca " + "with keyid %Y", id); + } + id->destroy(id); + } + enumerator->destroy(enumerator); + break; + } + case NOTIFY: + { + notify_payload_t *notify = (notify_payload_t*)payload; + + /* we only handle one type of notify here */ + if (notify->get_notify_type(notify) == HTTP_CERT_LOOKUP_SUPPORTED) + { + this->ike_sa->enable_extension(this->ike_sa, EXT_HASH_AND_URL); + } + break; + } + default: + /* ignore other payloads here, these are handled elsewhere */ + break; + } + } + enumerator->destroy(enumerator); +} + +/** + * tries to extract a certificate from the cert payload or the credential + * manager (based on the hash of a "Hash and URL" encoded cert). + * Note: the returned certificate (if any) has to be destroyed + */ +static certificate_t *try_get_cert(cert_payload_t *cert_payload) +{ + certificate_t *cert = NULL; + + switch (cert_payload->get_cert_encoding(cert_payload)) + { + case ENC_X509_SIGNATURE: + { + cert = cert_payload->get_cert(cert_payload); + break; + } + case ENC_X509_HASH_AND_URL: + { + identification_t *id; + chunk_t hash = cert_payload->get_hash(cert_payload); + if (!hash.ptr) + { + /* invalid "Hash and URL" data (logged elsewhere) */ + break; + } + id = identification_create_from_encoding(ID_KEY_ID, hash); + cert = charon->credentials->get_cert(charon->credentials, + CERT_X509, KEY_ANY, id, FALSE); + id->destroy(id); + break; + } + default: + { + break; + } + } + return cert; +} + +/** + * import certificates + */ +static void process_certs(private_ike_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) + { + cert_payload_t *cert_payload; + cert_encoding_t encoding; + certificate_t *cert; + char *url; + + cert_payload = (cert_payload_t*)payload; + encoding = cert_payload->get_cert_encoding(cert_payload); + + switch (encoding) + { + case ENC_X509_HASH_AND_URL: + { + if (!this->do_http_lookup) + { + DBG1(DBG_IKE, "received hash-and-url encoded cert, but" + " we don't accept them, ignore"); + break; + } + /* FALL */ + } + case ENC_X509_SIGNATURE: + { + cert = try_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); + } + } + else if (encoding == ENC_X509_HASH_AND_URL) + { + /* we fetch the certificate not yet, but only if + * it is really needed during authentication */ + url = cert_payload->get_url(cert_payload); + if (!url) + { + DBG1(DBG_IKE, "received invalid hash-and-url " + "encoded cert, ignore"); + break; + } + url = strdup(url); + if (first) + { /* first URL is for an end entity certificate */ + DBG1(DBG_IKE, "received hash-and-url for end" + " entity cert \"%s\"", url); + auth->add(auth, AUTH_HELPER_SUBJECT_HASH_URL, url); + first = FALSE; + } + else + { + DBG1(DBG_IKE, "received hash-and-url for issuer" + " cert \"%s\"", url); + auth->add(auth, AUTH_HELPER_IM_HASH_URL, url); + } + } + break; + } + case ENC_PKCS7_WRAPPED_X509: + case ENC_PGP: + case ENC_DNS_SIGNED_KEY: + case ENC_KERBEROS_TOKEN: + case ENC_CRL: + 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 keyid of a certificate to the certificate request payload + */ +static void add_certreq(certreq_payload_t **req, certificate_t *cert) +{ + switch (cert->get_type(cert)) + { + case CERT_X509: + { + public_key_t *public; + chunk_t keyid; + x509_t *x509 = (x509_t*)cert; + + if (!(x509->get_flags(x509) & X509_CA)) + { /* no CA cert, skip */ + break; + } + public = cert->get_public_key(cert); + if (!public) + { + break; + } + if (*req == NULL) + { + *req = certreq_payload_create_type(CERT_X509); + } + if (public->get_fingerprint(public, KEY_ID_PUBKEY_INFO_SHA1, &keyid)) + { + (*req)->add_keyid(*req, keyid); + DBG1(DBG_IKE, "sending cert request for \"%Y\"", + cert->get_subject(cert)); + } + public->destroy(public); + break; + } + default: + break; + } +} + +/** + * add a auth_cfg's CA certificates to the certificate request + */ +static void add_certreqs(certreq_payload_t **req, auth_cfg_t *auth) +{ + 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(req, (certificate_t*)value); + break; + default: + break; + } + } + enumerator->destroy(enumerator); +} + +/** + * build certificate requests + */ +static void build_certreqs(private_ike_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; + certreq_payload_t *req = NULL; + + 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); + while (enumerator->enumerate(enumerator, &auth)) + { + add_certreqs(&req, auth); + } + enumerator->destroy(enumerator); + } + + if (!req) + { + /* otherwise add all trusted CA certificates */ + enumerator = charon->credentials->create_cert_enumerator( + charon->credentials, CERT_ANY, KEY_ANY, NULL, TRUE); + while (enumerator->enumerate(enumerator, &cert)) + { + add_certreq(&req, cert); + } + enumerator->destroy(enumerator); + } + + if (req) + { + message->add_payload(message, (payload_t*)req); + + if (lib->settings->get_bool(lib->settings, "charon.hash_and_url", FALSE)) + { + message->add_notify(message, FALSE, HTTP_CERT_LOOKUP_SUPPORTED, + chunk_empty); + this->do_http_lookup = TRUE; + } + } +} + +/** + * Check if this is the final authentication round + */ +static bool final_auth(message_t *message) +{ + /* we check for an AUTH payload without a ANOTHER_AUTH_FOLLOWS notify */ + if (message->get_payload(message, AUTHENTICATION) == NULL) + { + return FALSE; + } + if (message->get_notify(message, ANOTHER_AUTH_FOLLOWS)) + { + return FALSE; + } + return TRUE; +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t build_i(private_ike_cert_pre_t *this, message_t *message) +{ + if (message->get_message_id(message) == 1) + { /* initiator sends CERTREQs in first IKE_AUTH */ + build_certreqs(this, message); + } + return NEED_MORE; +} + +/** + * Implementation of task_t.process for responder + */ +static status_t process_r(private_ike_cert_pre_t *this, message_t *message) +{ + if (message->get_exchange_type(message) != IKE_SA_INIT) + { /* handle certreqs/certs in any IKE_AUTH, just in case */ + process_certreqs(this, message); + process_certs(this, message); + } + this->final = final_auth(message); + return NEED_MORE; +} + +/** + * Implementation of task_t.build for responder + */ +static status_t build_r(private_ike_cert_pre_t *this, message_t *message) +{ + if (message->get_exchange_type(message) == IKE_SA_INIT) + { + build_certreqs(this, message); + } + if (this->final) + { + return SUCCESS; + } + return NEED_MORE; +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t process_i(private_ike_cert_pre_t *this, message_t *message) +{ + if (message->get_exchange_type(message) == IKE_SA_INIT) + { + process_certreqs(this, message); + } + process_certs(this, message); + + if (final_auth(message)) + { + return SUCCESS; + } + return NEED_MORE; +} + +/** + * Implementation of task_t.get_type + */ +static task_type_t get_type(private_ike_cert_pre_t *this) +{ + return IKE_CERT_PRE; +} + +/** + * Implementation of task_t.migrate + */ +static void migrate(private_ike_cert_pre_t *this, ike_sa_t *ike_sa) +{ + this->ike_sa = ike_sa; +} + +/** + * Implementation of task_t.destroy + */ +static void destroy(private_ike_cert_pre_t *this) +{ + free(this); +} + +/* + * Described in header. + */ +ike_cert_pre_t *ike_cert_pre_create(ike_sa_t *ike_sa, bool initiator) +{ + private_ike_cert_pre_t *this = malloc_thing(private_ike_cert_pre_t); + + this->public.task.get_type = (task_type_t(*)(task_t*))get_type; + this->public.task.migrate = (void(*)(task_t*,ike_sa_t*))migrate; + this->public.task.destroy = (void(*)(task_t*))destroy; + + if (initiator) + { + this->public.task.build = (status_t(*)(task_t*,message_t*))build_i; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_i; + } + else + { + this->public.task.build = (status_t(*)(task_t*,message_t*))build_r; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_r; + } + + this->ike_sa = ike_sa; + this->initiator = initiator; + this->do_http_lookup = FALSE; + this->final = FALSE; + + return &this->public; +} diff --git a/src/libcharon/sa/tasks/ike_cert_pre.h b/src/libcharon/sa/tasks/ike_cert_pre.h new file mode 100644 index 000000000..1541b80e5 --- /dev/null +++ b/src/libcharon/sa/tasks/ike_cert_pre.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2007-2008 Martin Willi + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup ike_cert_pre ike_cert_pre + * @{ @ingroup tasks + */ + +#ifndef IKE_CERT_PRE_H_ +#define IKE_CERT_PRE_H_ + +typedef struct ike_cert_pre_t ike_cert_pre_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/tasks/task.h> + +/** + * Task of type ike_cert_post, certificate processing before authentication. + */ +struct ike_cert_pre_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * Create a new ike_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 thask is the original initator + * @return ike_cert_pre task to handle by the task_manager + */ +ike_cert_pre_t *ike_cert_pre_create(ike_sa_t *ike_sa, bool initiator); + +#endif /** IKE_CERT_PRE_H_ @}*/ diff --git a/src/libcharon/sa/tasks/ike_config.c b/src/libcharon/sa/tasks/ike_config.c new file mode 100644 index 000000000..58bcf0762 --- /dev/null +++ b/src/libcharon/sa/tasks/ike_config.c @@ -0,0 +1,448 @@ +/* + * Copyright (C) 2007 Martin Willi + * Copyright (C) 2006-2007 Fabian Hartmann, Noah Heusser + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "ike_config.h" + +#include <daemon.h> +#include <hydra.h> +#include <encoding/payloads/cp_payload.h> + +typedef struct private_ike_config_t private_ike_config_t; + +/** + * Private members of a ike_config_t task. + */ +struct private_ike_config_t { + + /** + * Public methods and task_t interface. + */ + ike_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_value(type, chunk); +} + +/** + * Handle a received attribute as initiator + */ +static void handle_attribute(private_ike_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_value(ca)); + if (handler) + { + this->ike_sa->add_configuration_attribute(this->ike_sa, + handler, ca->get_type(ca), ca->get_value(ca)); + } +} + +/** + * process a single configuration attribute + */ +static void process_attribute(private_ike_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_value(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_ike_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) + { + 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); +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t build_i(private_ike_config_t *this, message_t *message) +{ + if (message->get_message_id(message) == 1) + { /* in first IKE_AUTH only */ + 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(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; + + /* create configuration attribute */ + DBG2(DBG_IKE, "building %N attribute", + configuration_attribute_type_names, type); + ca = configuration_attribute_create_value(type, data); + if (!cp) + { + cp = cp_payload_create_type(CFG_REQUEST); + } + cp->add_attribute(cp, ca); + + /* save handler along with requested type */ + entry = malloc_thing(entry_t); + entry->type = type; + entry->handler = handler; + + this->requested->insert_last(this->requested, entry); + } + enumerator->destroy(enumerator); + + if (cp) + { + message->add_payload(message, (payload_t*)cp); + } + } + return NEED_MORE; +} + +/** + * Implementation of task_t.process for responder + */ +static status_t process_r(private_ike_config_t *this, message_t *message) +{ + if (message->get_message_id(message) == 1) + { /* in first IKE_AUTH only */ + process_payloads(this, message); + } + return NEED_MORE; +} + +/** + * Implementation of task_t.build for responder + */ +static status_t build_r(private_ike_config_t *this, message_t *message) +{ + if (this->ike_sa->get_state(this->ike_sa) == IKE_ESTABLISHED) + { /* in last IKE_AUTH exchange */ + 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 (config && 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); + } + if (vip == NULL) + { + DBG1(DBG_IKE, "no virtual IP found, sending %N", + notify_type_names, INTERNAL_ADDRESS_FAILURE); + message->add_notify(message, FALSE, INTERNAL_ADDRESS_FAILURE, + chunk_empty); + return SUCCESS; + } + DBG1(DBG_IKE, "assigning virtual IP %H to peer", vip); + this->ike_sa->set_virtual_ip(this->ike_sa, FALSE, vip); + + cp = cp_payload_create_type(CFG_REPLY); + cp->add_attribute(cp, build_vip(vip)); + } + + /* query registered providers for additional attributes to include */ + enumerator = hydra->attributes->create_responder_enumerator( + hydra->attributes, id, vip); + while (enumerator->enumerate(enumerator, &type, &value)) + { + if (!cp) + { + cp = cp_payload_create_type(CFG_REPLY); + } + DBG2(DBG_IKE, "building %N attribute", + configuration_attribute_type_names, type); + cp->add_attribute(cp, + configuration_attribute_create_value(type, value)); + } + enumerator->destroy(enumerator); + + if (cp) + { + message->add_payload(message, (payload_t*)cp); + } + DESTROY_IF(vip); + return SUCCESS; + } + return NEED_MORE; +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t process_i(private_ike_config_t *this, message_t *message) +{ + if (this->ike_sa->get_state(this->ike_sa) == IKE_ESTABLISHED) + { /* in last IKE_AUTH exchange */ + + process_payloads(this, message); + + if (this->virtual_ip) + { + this->ike_sa->set_virtual_ip(this->ike_sa, TRUE, this->virtual_ip); + } + return SUCCESS; + } + return NEED_MORE; +} + +/** + * Implementation of task_t.get_type + */ +static task_type_t get_type(private_ike_config_t *this) +{ + return IKE_CONFIG; +} + +/** + * Implementation of task_t.migrate + */ +static void migrate(private_ike_config_t *this, ike_sa_t *ike_sa) +{ + DESTROY_IF(this->virtual_ip); + + this->ike_sa = ike_sa; + this->virtual_ip = NULL; + this->requested->destroy_function(this->requested, free); + this->requested = linked_list_create(); +} + +/** + * Implementation of task_t.destroy + */ +static void destroy(private_ike_config_t *this) +{ + DESTROY_IF(this->virtual_ip); + this->requested->destroy_function(this->requested, free); + free(this); +} + +/* + * Described in header. + */ +ike_config_t *ike_config_create(ike_sa_t *ike_sa, bool initiator) +{ + private_ike_config_t *this = malloc_thing(private_ike_config_t); + + this->public.task.get_type = (task_type_t(*)(task_t*))get_type; + this->public.task.migrate = (void(*)(task_t*,ike_sa_t*))migrate; + this->public.task.destroy = (void(*)(task_t*))destroy; + + this->initiator = initiator; + this->ike_sa = ike_sa; + this->virtual_ip = NULL; + this->requested = linked_list_create(); + + if (initiator) + { + this->public.task.build = (status_t(*)(task_t*,message_t*))build_i; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_i; + } + else + { + this->public.task.build = (status_t(*)(task_t*,message_t*))build_r; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_r; + } + + return &this->public; +} + diff --git a/src/libcharon/sa/tasks/ike_config.h b/src/libcharon/sa/tasks/ike_config.h new file mode 100644 index 000000000..8cef08697 --- /dev/null +++ b/src/libcharon/sa/tasks/ike_config.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2007 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 ike_config ike_config + * @{ @ingroup tasks + */ + +#ifndef IKE_CONFIG_H_ +#define IKE_CONFIG_H_ + +typedef struct ike_config_t ike_config_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/tasks/task.h> + +/** + * Task of type IKE_CONFIG, sets up a virtual IP and other + * configurations for an IKE_SA. + */ +struct ike_config_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * Create a new ike_config task. + * + * @param ike_sa IKE_SA this task works for + * @param initiator TRUE for initiator + * @return ike_config task to handle by the task_manager + */ +ike_config_t *ike_config_create(ike_sa_t *ike_sa, bool initiator); + +#endif /** IKE_CONFIG_H_ @}*/ diff --git a/src/libcharon/sa/tasks/ike_delete.c b/src/libcharon/sa/tasks/ike_delete.c new file mode 100644 index 000000000..130948836 --- /dev/null +++ b/src/libcharon/sa/tasks/ike_delete.c @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2006-2007 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 "ike_delete.h" + +#include <daemon.h> +#include <encoding/payloads/delete_payload.h> + + +typedef struct private_ike_delete_t private_ike_delete_t; + +/** + * Private members of a ike_delete_t task. + */ +struct private_ike_delete_t { + + /** + * Public methods and task_t interface. + */ + ike_delete_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; + + /** + * Are we the initiator? + */ + bool initiator; + + /** + * are we deleting a rekeyed SA? + */ + bool rekeyed; + + /** + * are we responding to a delete, but have initated our own? + */ + bool simultaneous; +}; + +/** + * Implementation of task_t.build for initiator + */ +static status_t build_i(private_ike_delete_t *this, message_t *message) +{ + delete_payload_t *delete_payload; + + 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(PROTO_IKE); + message->add_payload(message, (payload_t*)delete_payload); + + if (this->ike_sa->get_state(this->ike_sa) == IKE_REKEYING) + { + this->rekeyed = TRUE; + } + this->ike_sa->set_state(this->ike_sa, IKE_DELETING); + + 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)); + + return NEED_MORE; +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t process_i(private_ike_delete_t *this, message_t *message) +{ + DBG0(DBG_IKE, "IKE_SA deleted"); + if (!this->rekeyed) + { /* invoke ike_down() hook if SA has not been rekeyed */ + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + } + /* completed, delete IKE_SA by returning DESTROY_ME */ + return DESTROY_ME; +} + +/** + * Implementation of task_t.process for responder + */ +static status_t process_r(private_ike_delete_t *this, message_t *message) +{ + /* we don't even scan the payloads, as the message wouldn't have + * come so far without being correct */ + 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)); + + switch (this->ike_sa->get_state(this->ike_sa)) + { + case IKE_ESTABLISHED: + this->ike_sa->set_state(this->ike_sa, IKE_DELETING); + this->ike_sa->reestablish(this->ike_sa); + return NEED_MORE; + case IKE_REKEYING: + this->rekeyed = TRUE; + break; + case IKE_DELETING: + this->simultaneous = TRUE; + break; + default: + break; + } + this->ike_sa->set_state(this->ike_sa, IKE_DELETING); + return NEED_MORE; +} + +/** + * Implementation of task_t.build for responder + */ +static status_t build_r(private_ike_delete_t *this, message_t *message) +{ + DBG0(DBG_IKE, "IKE_SA deleted"); + + if (this->simultaneous) + { + /* wait for peer's response for our delete request, but set a timeout */ + return SUCCESS; + } + if (!this->rekeyed) + { /* invoke ike_down() hook if SA has not been rekeyed */ + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + } + /* completed, delete IKE_SA by returning DESTROY_ME */ + return DESTROY_ME; +} + +/** + * Implementation of task_t.get_type + */ +static task_type_t get_type(private_ike_delete_t *this) +{ + return IKE_DELETE; +} + +/** + * Implementation of task_t.migrate + */ +static void migrate(private_ike_delete_t *this, ike_sa_t *ike_sa) +{ + this->ike_sa = ike_sa; + this->simultaneous = FALSE; +} + +/** + * Implementation of task_t.destroy + */ +static void destroy(private_ike_delete_t *this) +{ + free(this); +} + +/* + * Described in header. + */ +ike_delete_t *ike_delete_create(ike_sa_t *ike_sa, bool initiator) +{ + private_ike_delete_t *this = malloc_thing(private_ike_delete_t); + + this->public.task.get_type = (task_type_t(*)(task_t*))get_type; + this->public.task.migrate = (void(*)(task_t*,ike_sa_t*))migrate; + this->public.task.destroy = (void(*)(task_t*))destroy; + + if (initiator) + { + this->public.task.build = (status_t(*)(task_t*,message_t*))build_i; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_i; + } + else + { + this->public.task.build = (status_t(*)(task_t*,message_t*))build_r; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_r; + } + + this->ike_sa = ike_sa; + this->initiator = initiator; + this->rekeyed = FALSE; + this->simultaneous = FALSE; + + return &this->public; +} diff --git a/src/libcharon/sa/tasks/ike_delete.h b/src/libcharon/sa/tasks/ike_delete.h new file mode 100644 index 000000000..82782f393 --- /dev/null +++ b/src/libcharon/sa/tasks/ike_delete.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2007 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 ike_delete ike_delete + * @{ @ingroup tasks + */ + +#ifndef IKE_DELETE_H_ +#define IKE_DELETE_H_ + +typedef struct ike_delete_t ike_delete_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/tasks/task.h> + +/** + * Task of type ike_delete, delete an IKE_SA. + */ +struct ike_delete_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * Create a new ike_delete task. + * + * @param ike_sa IKE_SA this task works for + * @param initiator TRUE if we initiate the delete + * @return ike_delete task to handle by the task_manager + */ +ike_delete_t *ike_delete_create(ike_sa_t *ike_sa, bool initiator); + +#endif /** IKE_DELETE_H_ @}*/ diff --git a/src/libcharon/sa/tasks/ike_dpd.c b/src/libcharon/sa/tasks/ike_dpd.c new file mode 100644 index 000000000..4c6ba7662 --- /dev/null +++ b/src/libcharon/sa/tasks/ike_dpd.c @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2007 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 "ike_dpd.h" + +#include <daemon.h> + + +typedef struct private_ike_dpd_t private_ike_dpd_t; + +/** + * Private members of a ike_dpd_t task. + */ +struct private_ike_dpd_t { + + /** + * Public methods and task_t interface. + */ + ike_dpd_t public; +}; + +/** + * Implementation of task_t.build for initiator + * Implementation of task_t.process for responder + */ +static status_t return_need_more(private_ike_dpd_t *this, message_t *message) +{ + return NEED_MORE; +} + +/** + * Implementation of task_t.process for initiator + * Implementation of task_t.build for responder + */ +static status_t return_success(private_ike_dpd_t *this, message_t *message) +{ + return SUCCESS; +} + +/** + * Implementation of task_t.get_type + */ +static task_type_t get_type(private_ike_dpd_t *this) +{ + return IKE_DPD; +} + +/** + * Implementation of task_t.migrate + */ +static void migrate(private_ike_dpd_t *this, ike_sa_t *ike_sa) +{ + +} + +/** + * Implementation of task_t.destroy + */ +static void destroy(private_ike_dpd_t *this) +{ + free(this); +} + +/* + * Described in header. + */ +ike_dpd_t *ike_dpd_create(bool initiator) +{ + private_ike_dpd_t *this = malloc_thing(private_ike_dpd_t); + + this->public.task.get_type = (task_type_t(*)(task_t*))get_type; + this->public.task.migrate = (void(*)(task_t*,ike_sa_t*))migrate; + this->public.task.destroy = (void(*)(task_t*))destroy; + + if (initiator) + { + this->public.task.build = (status_t(*)(task_t*,message_t*))return_need_more; + this->public.task.process = (status_t(*)(task_t*,message_t*))return_success; + } + else + { + this->public.task.build = (status_t(*)(task_t*,message_t*))return_success; + this->public.task.process = (status_t(*)(task_t*,message_t*))return_need_more; + } + + return &this->public; +} diff --git a/src/libcharon/sa/tasks/ike_dpd.h b/src/libcharon/sa/tasks/ike_dpd.h new file mode 100644 index 000000000..36388d15b --- /dev/null +++ b/src/libcharon/sa/tasks/ike_dpd.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2007 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 ike_dpd ike_dpd + * @{ @ingroup tasks + */ + +#ifndef IKE_DPD_H_ +#define IKE_DPD_H_ + +typedef struct ike_dpd_t ike_dpd_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/tasks/task.h> + +/** + * Task of type ike_dpd, detects dead peers. + * + * The DPD task actually does nothing, as a DPD has no associated payloads. + */ +struct ike_dpd_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * Create a new ike_dpd task. + * + * @param initiator TRUE if thask is the original initator + * @return ike_dpd task to handle by the task_manager + */ +ike_dpd_t *ike_dpd_create(bool initiator); + +#endif /** IKE_DPD_H_ @}*/ diff --git a/src/libcharon/sa/tasks/ike_init.c b/src/libcharon/sa/tasks/ike_init.c new file mode 100644 index 000000000..5eb33b540 --- /dev/null +++ b/src/libcharon/sa/tasks/ike_init.c @@ -0,0 +1,600 @@ +/* + * Copyright (C) 2008-2009 Tobias Brunner + * Copyright (C) 2005-2008 Martin Willi + * Copyright (C) 2005 Jan Hutter + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "ike_init.h" + +#include <string.h> + +#include <daemon.h> +#include <crypto/diffie_hellman.h> +#include <encoding/payloads/sa_payload.h> +#include <encoding/payloads/ke_payload.h> +#include <encoding/payloads/nonce_payload.h> + +/** maximum retries to do with cookies/other dh groups */ +#define MAX_RETRIES 5 + +typedef struct private_ike_init_t private_ike_init_t; + +/** + * Private members of a ike_init_t task. + */ +struct private_ike_init_t { + + /** + * Public methods and task_t interface. + */ + ike_init_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; + + /** + * Are we the initiator? + */ + bool initiator; + + /** + * IKE config to establish + */ + ike_cfg_t *config; + + /** + * diffie hellman group to use + */ + diffie_hellman_group_t dh_group; + + /** + * diffie hellman key exchange + */ + diffie_hellman_t *dh; + + /** + * Keymat derivation (from IKE_SA) + */ + keymat_t *keymat; + + /** + * nonce chosen by us + */ + chunk_t my_nonce; + + /** + * nonce chosen by peer + */ + chunk_t other_nonce; + + /** + * Negotiated proposal used for IKE_SA + */ + proposal_t *proposal; + + /** + * Old IKE_SA which gets rekeyed + */ + ike_sa_t *old_sa; + + /** + * cookie received from responder + */ + chunk_t cookie; + + /** + * retries done so far after failure (cookie or bad dh group) + */ + u_int retry; +}; + +/** + * build the payloads for the message + */ +static void build_payloads(private_ike_init_t *this, message_t *message) +{ + sa_payload_t *sa_payload; + ke_payload_t *ke_payload; + nonce_payload_t *nonce_payload; + linked_list_t *proposal_list; + ike_sa_id_t *id; + proposal_t *proposal; + iterator_t *iterator; + + id = this->ike_sa->get_id(this->ike_sa); + + this->config = this->ike_sa->get_ike_cfg(this->ike_sa); + + if (this->initiator) + { + proposal_list = this->config->get_proposals(this->config); + if (this->old_sa) + { + /* include SPI of new IKE_SA when we are rekeying */ + iterator = proposal_list->create_iterator(proposal_list, TRUE); + while (iterator->iterate(iterator, (void**)&proposal)) + { + proposal->set_spi(proposal, id->get_initiator_spi(id)); + } + iterator->destroy(iterator); + } + + sa_payload = sa_payload_create_from_proposal_list(proposal_list); + proposal_list->destroy_offset(proposal_list, offsetof(proposal_t, destroy)); + } + else + { + if (this->old_sa) + { + /* include SPI of new IKE_SA when we are rekeying */ + this->proposal->set_spi(this->proposal, id->get_responder_spi(id)); + } + sa_payload = sa_payload_create_from_proposal(this->proposal); + } + message->add_payload(message, (payload_t*)sa_payload); + + nonce_payload = nonce_payload_create(); + nonce_payload->set_nonce(nonce_payload, this->my_nonce); + ke_payload = ke_payload_create_from_diffie_hellman(this->dh); + + if (this->old_sa) + { /* payload order differs if we are rekeying */ + message->add_payload(message, (payload_t*)nonce_payload); + message->add_payload(message, (payload_t*)ke_payload); + } + else + { + message->add_payload(message, (payload_t*)ke_payload); + message->add_payload(message, (payload_t*)nonce_payload); + } +} + +/** + * Read payloads from message + */ +static void process_payloads(private_ike_init_t *this, message_t *message) +{ + enumerator_t *enumerator; + payload_t *payload; + + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + switch (payload->get_type(payload)) + { + case SECURITY_ASSOCIATION: + { + sa_payload_t *sa_payload = (sa_payload_t*)payload; + linked_list_t *proposal_list; + bool private; + + proposal_list = sa_payload->get_proposals(sa_payload); + private = this->ike_sa->supports_extension(this->ike_sa, + EXT_STRONGSWAN); + this->proposal = this->config->select_proposal(this->config, + proposal_list, private); + proposal_list->destroy_offset(proposal_list, + offsetof(proposal_t, destroy)); + break; + } + case KEY_EXCHANGE: + { + ke_payload_t *ke_payload = (ke_payload_t*)payload; + + this->dh_group = ke_payload->get_dh_group_number(ke_payload); + if (!this->initiator) + { + this->dh = this->keymat->create_dh(this->keymat, + this->dh_group); + } + if (this->dh) + { + this->dh->set_other_public_value(this->dh, + ke_payload->get_key_exchange_data(ke_payload)); + } + break; + } + case NONCE: + { + nonce_payload_t *nonce_payload = (nonce_payload_t*)payload; + + this->other_nonce = nonce_payload->get_nonce(nonce_payload); + break; + } + default: + break; + } + } + enumerator->destroy(enumerator); +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t build_i(private_ike_init_t *this, message_t *message) +{ + rng_t *rng; + + this->config = this->ike_sa->get_ike_cfg(this->ike_sa); + DBG0(DBG_IKE, "initiating IKE_SA %s[%d] to %H", + this->ike_sa->get_name(this->ike_sa), + 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); + + if (this->retry >= MAX_RETRIES) + { + DBG1(DBG_IKE, "giving up after %d retries", MAX_RETRIES); + return FAILED; + } + + /* if the DH group is set via use_dh_group(), we already have a DH object */ + if (!this->dh) + { + this->dh_group = this->config->get_dh_group(this->config); + this->dh = this->keymat->create_dh(this->keymat, this->dh_group); + if (!this->dh) + { + DBG1(DBG_IKE, "configured DH group %N not supported", + diffie_hellman_group_names, this->dh_group); + return FAILED; + } + } + + /* generate nonce only when we are trying the first time */ + if (this->my_nonce.ptr == NULL) + { + rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK); + if (!rng) + { + DBG1(DBG_IKE, "error generating nonce"); + return FAILED; + } + rng->allocate_bytes(rng, NONCE_SIZE, &this->my_nonce); + rng->destroy(rng); + } + + if (this->cookie.ptr) + { + message->add_notify(message, FALSE, COOKIE, this->cookie); + } + + build_payloads(this, message); + +#ifdef ME + { + chunk_t connect_id = this->ike_sa->get_connect_id(this->ike_sa); + if (connect_id.ptr) + { + message->add_notify(message, FALSE, ME_CONNECTID, connect_id); + } + } +#endif /* ME */ + + return NEED_MORE; +} + +/** + * Implementation of task_t.process for responder + */ +static status_t process_r(private_ike_init_t *this, message_t *message) +{ + rng_t *rng; + + this->config = this->ike_sa->get_ike_cfg(this->ike_sa); + DBG0(DBG_IKE, "%H is initiating an IKE_SA", message->get_source(message)); + this->ike_sa->set_state(this->ike_sa, IKE_CONNECTING); + + rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK); + if (!rng) + { + DBG1(DBG_IKE, "error generating nonce"); + return FAILED; + } + rng->allocate_bytes(rng, NONCE_SIZE, &this->my_nonce); + rng->destroy(rng); + +#ifdef ME + { + notify_payload_t *notify = message->get_notify(message, ME_CONNECTID); + if (notify) + { + chunk_t connect_id = notify->get_notification_data(notify); + DBG2(DBG_IKE, "received ME_CONNECTID %#B", &connect_id); + charon->connect_manager->stop_checks(charon->connect_manager, + connect_id); + } + } +#endif /* ME */ + + process_payloads(this, message); + + return NEED_MORE; +} + +/** + * Derive the keymat for the IKE_SA + */ +static bool derive_keys(private_ike_init_t *this, + chunk_t nonce_i, chunk_t nonce_r) +{ + keymat_t *old_keymat; + pseudo_random_function_t prf_alg = PRF_UNDEFINED; + chunk_t skd = chunk_empty; + ike_sa_id_t *id; + + id = this->ike_sa->get_id(this->ike_sa); + if (this->old_sa) + { + /* rekeying: Include old SKd, use old PRF, apply SPI */ + old_keymat = this->old_sa->get_keymat(this->old_sa); + prf_alg = old_keymat->get_skd(old_keymat, &skd); + if (this->initiator) + { + id->set_responder_spi(id, this->proposal->get_spi(this->proposal)); + } + else + { + id->set_initiator_spi(id, this->proposal->get_spi(this->proposal)); + } + } + if (!this->keymat->derive_ike_keys(this->keymat, this->proposal, this->dh, + nonce_i, nonce_r, id, prf_alg, skd)) + { + return FALSE; + } + charon->bus->ike_keys(charon->bus, this->ike_sa, this->dh, + nonce_i, nonce_r, this->old_sa); + return TRUE; +} + +/** + * Implementation of task_t.build for responder + */ +static status_t build_r(private_ike_init_t *this, message_t *message) +{ + /* check if we have everything we need */ + if (this->proposal == NULL || + this->other_nonce.len == 0 || this->my_nonce.len == 0) + { + DBG1(DBG_IKE, "received proposals inacceptable"); + message->add_notify(message, TRUE, NO_PROPOSAL_CHOSEN, chunk_empty); + return FAILED; + } + this->ike_sa->set_proposal(this->ike_sa, this->proposal); + + if (this->dh == NULL || + !this->proposal->has_dh_group(this->proposal, this->dh_group)) + { + u_int16_t group; + + if (this->proposal->get_algorithm(this->proposal, DIFFIE_HELLMAN_GROUP, + &group, NULL)) + { + DBG1(DBG_IKE, "DH group %N inacceptable, requesting %N", + diffie_hellman_group_names, this->dh_group, + diffie_hellman_group_names, group); + this->dh_group = group; + group = htons(group); + message->add_notify(message, FALSE, INVALID_KE_PAYLOAD, + chunk_from_thing(group)); + } + else + { + DBG1(DBG_IKE, "no acceptable proposal found"); + } + return FAILED; + } + + if (!derive_keys(this, this->other_nonce, this->my_nonce)) + { + DBG1(DBG_IKE, "key derivation failed"); + message->add_notify(message, TRUE, NO_PROPOSAL_CHOSEN, chunk_empty); + return FAILED; + } + build_payloads(this, message); + return SUCCESS; +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t process_i(private_ike_init_t *this, message_t *message) +{ + enumerator_t *enumerator; + payload_t *payload; + + /* check for erronous notifies */ + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + if (payload->get_type(payload) == NOTIFY) + { + notify_payload_t *notify = (notify_payload_t*)payload; + notify_type_t type = notify->get_notify_type(notify); + + switch (type) + { + case INVALID_KE_PAYLOAD: + { + chunk_t data; + diffie_hellman_group_t bad_group; + + bad_group = this->dh_group; + data = notify->get_notification_data(notify); + this->dh_group = ntohs(*((u_int16_t*)data.ptr)); + DBG1(DBG_IKE, "peer didn't accept DH group %N, " + "it requested %N", diffie_hellman_group_names, + bad_group, diffie_hellman_group_names, this->dh_group); + + if (this->old_sa == NULL) + { /* reset the IKE_SA if we are not rekeying */ + this->ike_sa->reset(this->ike_sa); + } + + enumerator->destroy(enumerator); + this->retry++; + return NEED_MORE; + } + case NAT_DETECTION_SOURCE_IP: + case NAT_DETECTION_DESTINATION_IP: + /* skip, handled in ike_natd_t */ + break; + case MULTIPLE_AUTH_SUPPORTED: + /* handled in ike_auth_t */ + break; + case COOKIE: + { + chunk_free(&this->cookie); + this->cookie = chunk_clone(notify->get_notification_data(notify)); + this->ike_sa->reset(this->ike_sa); + enumerator->destroy(enumerator); + DBG2(DBG_IKE, "received %N notify", notify_type_names, type); + this->retry++; + return NEED_MORE; + } + default: + { + if (type < 16383) + { + DBG1(DBG_IKE, "received %N notify error", + notify_type_names, type); + enumerator->destroy(enumerator); + return FAILED; + } + DBG2(DBG_IKE, "received %N notify", + notify_type_names, type); + break; + } + } + } + } + enumerator->destroy(enumerator); + + process_payloads(this, message); + + /* check if we have everything */ + if (this->proposal == NULL || + this->other_nonce.len == 0 || this->my_nonce.len == 0) + { + DBG1(DBG_IKE, "peers proposal selection invalid"); + return FAILED; + } + this->ike_sa->set_proposal(this->ike_sa, this->proposal); + + if (this->dh == NULL || + !this->proposal->has_dh_group(this->proposal, this->dh_group)) + { + DBG1(DBG_IKE, "peer DH group selection invalid"); + return FAILED; + } + + if (!derive_keys(this, this->my_nonce, this->other_nonce)) + { + DBG1(DBG_IKE, "key derivation failed"); + return FAILED; + } + return SUCCESS; +} + +/** + * Implementation of task_t.get_type + */ +static task_type_t get_type(private_ike_init_t *this) +{ + return IKE_INIT; +} + +/** + * Implementation of task_t.get_type + */ +static chunk_t get_lower_nonce(private_ike_init_t *this) +{ + if (memcmp(this->my_nonce.ptr, this->other_nonce.ptr, + min(this->my_nonce.len, this->other_nonce.len)) < 0) + { + return this->my_nonce; + } + else + { + return this->other_nonce; + } +} + +/** + * Implementation of task_t.migrate + */ +static void migrate(private_ike_init_t *this, ike_sa_t *ike_sa) +{ + DESTROY_IF(this->proposal); + chunk_free(&this->other_nonce); + + this->ike_sa = ike_sa; + this->proposal = NULL; + DESTROY_IF(this->dh); + this->dh = this->keymat->create_dh(this->keymat, this->dh_group); +} + +/** + * Implementation of task_t.destroy + */ +static void destroy(private_ike_init_t *this) +{ + DESTROY_IF(this->dh); + DESTROY_IF(this->proposal); + chunk_free(&this->my_nonce); + chunk_free(&this->other_nonce); + chunk_free(&this->cookie); + free(this); +} + +/* + * Described in header. + */ +ike_init_t *ike_init_create(ike_sa_t *ike_sa, bool initiator, ike_sa_t *old_sa) +{ + private_ike_init_t *this = malloc_thing(private_ike_init_t); + + this->public.get_lower_nonce = (chunk_t(*)(ike_init_t*))get_lower_nonce; + this->public.task.get_type = (task_type_t(*)(task_t*))get_type; + this->public.task.migrate = (void(*)(task_t*,ike_sa_t*))migrate; + this->public.task.destroy = (void(*)(task_t*))destroy; + if (initiator) + { + this->public.task.build = (status_t(*)(task_t*,message_t*))build_i; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_i; + } + else + { + this->public.task.build = (status_t(*)(task_t*,message_t*))build_r; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_r; + } + + this->ike_sa = ike_sa; + this->initiator = initiator; + this->dh_group = MODP_NONE; + this->dh = NULL; + this->keymat = ike_sa->get_keymat(ike_sa); + this->my_nonce = chunk_empty; + this->other_nonce = chunk_empty; + this->cookie = chunk_empty; + this->proposal = NULL; + this->config = NULL; + this->old_sa = old_sa; + this->retry = 0; + + return &this->public; +} diff --git a/src/libcharon/sa/tasks/ike_init.h b/src/libcharon/sa/tasks/ike_init.h new file mode 100644 index 000000000..7bd784cff --- /dev/null +++ b/src/libcharon/sa/tasks/ike_init.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2007 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 ike_init ike_init + * @{ @ingroup tasks + */ + +#ifndef IKE_INIT_H_ +#define IKE_INIT_H_ + +typedef struct ike_init_t ike_init_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/tasks/task.h> + +/** + * Task of type IKE_INIT, creates an IKE_SA without authentication. + * + * The authentication of is handle in the ike_auth task. + */ +struct ike_init_t { + + /** + * Implements the task_t interface + */ + task_t task; + + /** + * Get the lower of the two nonces, used for rekey collisions. + * + * @return lower nonce + */ + chunk_t (*get_lower_nonce) (ike_init_t *this); +}; + +/** + * Create a new IKE_INIT task. + * + * @param ike_sa IKE_SA this task works for (new one when rekeying) + * @param initiator TRUE if thask is the original initator + * @param old_sa old IKE_SA when we are rekeying + * @return ike_init task to handle by the task_manager + */ +ike_init_t *ike_init_create(ike_sa_t *ike_sa, bool initiator, ike_sa_t *old_sa); + +#endif /** IKE_INIT_H_ @}*/ diff --git a/src/libcharon/sa/tasks/ike_me.c b/src/libcharon/sa/tasks/ike_me.c new file mode 100644 index 000000000..2d2847ae0 --- /dev/null +++ b/src/libcharon/sa/tasks/ike_me.c @@ -0,0 +1,856 @@ +/* + * Copyright (C) 2007-2008 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 "ike_me.h" + +#include <string.h> + +#include <daemon.h> +#include <config/peer_cfg.h> +#include <encoding/payloads/id_payload.h> +#include <encoding/payloads/notify_payload.h> +#include <encoding/payloads/endpoint_notify.h> +#include <processing/jobs/mediation_job.h> + +#define ME_CONNECTID_LEN 4 +#define ME_CONNECTKEY_LEN 16 + +typedef struct private_ike_me_t private_ike_me_t; + +/** + * Private members of a ike_me_t task. + */ +struct private_ike_me_t { + + /** + * Public methods and task_t interface. + */ + ike_me_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; + + /** + * Are we the initiator? + */ + bool initiator; + + /** + * Is this a mediation connection? + */ + bool mediation; + + /** + * Is this the response from another peer? + */ + bool response; + + /** + * Gathered endpoints + */ + linked_list_t *local_endpoints; + + /** + * Parsed endpoints + */ + linked_list_t *remote_endpoints; + + /** + * Did the peer request a callback? + */ + bool callback; + + /** + * Did the connect fail? + */ + bool failed; + + /** + * Was there anything wrong with the payloads? + */ + bool invalid_syntax; + + /** + * The requested peer + */ + identification_t *peer_id; + /** + * Received ID used for connectivity checks + */ + chunk_t connect_id; + + /** + * Received key used for connectivity checks + */ + chunk_t connect_key; + + /** + * Peer config of the mediated connection + */ + peer_cfg_t *mediated_cfg; + +}; + +/** + * Adds a list of endpoints as notifies to a given message + */ +static void add_endpoints_to_message(message_t *message, linked_list_t *endpoints) +{ + iterator_t *iterator; + endpoint_notify_t *endpoint; + + iterator = endpoints->create_iterator(endpoints, TRUE); + while (iterator->iterate(iterator, (void**)&endpoint)) + { + message->add_payload(message, (payload_t*)endpoint->build_notify(endpoint)); + } + iterator->destroy(iterator); +} + +/** + * Gathers endpoints and adds them to the current message + */ +static void gather_and_add_endpoints(private_ike_me_t *this, message_t *message) +{ + enumerator_t *enumerator; + host_t *addr, *host; + u_int16_t port; + + /* get the port that is used to communicate with the ms */ + host = this->ike_sa->get_my_host(this->ike_sa); + port = host->get_port(host); + + enumerator = charon->kernel_interface->create_address_enumerator( + charon->kernel_interface, FALSE, FALSE); + while (enumerator->enumerate(enumerator, (void**)&addr)) + { + host = addr->clone(addr); + host->set_port(host, port); + + this->local_endpoints->insert_last(this->local_endpoints, + endpoint_notify_create_from_host(HOST, host, NULL)); + + host->destroy(host); + } + enumerator->destroy(enumerator); + + host = this->ike_sa->get_server_reflexive_host(this->ike_sa); + if (host) + { + this->local_endpoints->insert_last(this->local_endpoints, + endpoint_notify_create_from_host(SERVER_REFLEXIVE, host, + this->ike_sa->get_my_host(this->ike_sa))); + } + + add_endpoints_to_message(message, this->local_endpoints); +} + +/** + * read notifys from message and evaluate them + */ +static void process_payloads(private_ike_me_t *this, message_t *message) +{ + enumerator_t *enumerator; + payload_t *payload; + + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + if (payload->get_type(payload) != NOTIFY) + { + continue; + } + + notify_payload_t *notify = (notify_payload_t*)payload; + + switch (notify->get_notify_type(notify)) + { + case ME_CONNECT_FAILED: + { + DBG2(DBG_IKE, "received ME_CONNECT_FAILED notify"); + this->failed = TRUE; + break; + } + case ME_MEDIATION: + { + DBG2(DBG_IKE, "received ME_MEDIATION notify"); + this->mediation = TRUE; + break; + } + case ME_ENDPOINT: + { + endpoint_notify_t *endpoint; + endpoint = endpoint_notify_create_from_payload(notify); + if (!endpoint) + { + DBG1(DBG_IKE, "received invalid ME_ENDPOINT notify"); + break; + } + DBG1(DBG_IKE, "received %N ME_ENDPOINT %#H", + me_endpoint_type_names, endpoint->get_type(endpoint), + endpoint->get_host(endpoint)); + + this->remote_endpoints->insert_last(this->remote_endpoints, + endpoint); + break; + } + case ME_CALLBACK: + { + DBG2(DBG_IKE, "received ME_CALLBACK notify"); + this->callback = TRUE; + break; + } + case ME_CONNECTID: + { + chunk_free(&this->connect_id); + this->connect_id = chunk_clone(notify->get_notification_data(notify)); + DBG2(DBG_IKE, "received ME_CONNECTID %#B", &this->connect_id); + break; + } + case ME_CONNECTKEY: + { + chunk_free(&this->connect_key); + this->connect_key = chunk_clone(notify->get_notification_data(notify)); + DBG4(DBG_IKE, "received ME_CONNECTKEY %#B", &this->connect_key); + break; + } + case ME_RESPONSE: + { + DBG2(DBG_IKE, "received ME_RESPONSE notify"); + this->response = TRUE; + break; + } + default: + break; + } + } + enumerator->destroy(enumerator); +} + +/** + * Implementation of task_t.build for initiator + */ +static status_t build_i(private_ike_me_t *this, message_t *message) +{ + switch(message->get_exchange_type(message)) + { + case IKE_SA_INIT: + { + peer_cfg_t *peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); + if (peer_cfg->is_mediation(peer_cfg)) + { + DBG2(DBG_IKE, "adding ME_MEDIATION"); + message->add_notify(message, FALSE, ME_MEDIATION, chunk_empty); + } + else + { + return SUCCESS; + } + break; + } + case IKE_AUTH: + { + if (this->ike_sa->has_condition(this->ike_sa, COND_NAT_HERE)) + { + endpoint_notify_t *endpoint; + endpoint = endpoint_notify_create_from_host(SERVER_REFLEXIVE, + NULL, NULL); + message->add_payload(message, (payload_t*)endpoint->build_notify(endpoint)); + endpoint->destroy(endpoint); + } + break; + } + case ME_CONNECT: + { + rng_t *rng; + id_payload_t *id_payload; + id_payload = id_payload_create_from_identification(ID_PEER, + this->peer_id); + message->add_payload(message, (payload_t*)id_payload); + + rng = lib->crypto->create_rng(lib->crypto, RNG_STRONG); + if (!rng) + { + DBG1(DBG_IKE, "unable to generate connect ID for ME_CONNECT"); + return FAILED; + } + if (!this->response) + { + /* only the initiator creates a connect ID. the responder + * returns the connect ID that it received from the initiator */ + rng->allocate_bytes(rng, ME_CONNECTID_LEN, &this->connect_id); + } + rng->allocate_bytes(rng, ME_CONNECTKEY_LEN, &this->connect_key); + rng->destroy(rng); + + message->add_notify(message, FALSE, ME_CONNECTID, this->connect_id); + message->add_notify(message, FALSE, ME_CONNECTKEY, this->connect_key); + + if (this->response) + { + message->add_notify(message, FALSE, ME_RESPONSE, chunk_empty); + } + else + { + /* FIXME: should we make this configurable? */ + message->add_notify(message, FALSE, ME_CALLBACK, chunk_empty); + } + + gather_and_add_endpoints(this, message); + + break; + } + default: + break; + } + return NEED_MORE; +} + +/** + * Implementation of task_t.process for responder + */ +static status_t process_r(private_ike_me_t *this, message_t *message) +{ + switch(message->get_exchange_type(message)) + { + case ME_CONNECT: + { + id_payload_t *id_payload; + id_payload = (id_payload_t*)message->get_payload(message, ID_PEER); + if (!id_payload) + { + DBG1(DBG_IKE, "received ME_CONNECT without ID_PEER payload" + ", aborting"); + break; + } + this->peer_id = id_payload->get_identification(id_payload); + + process_payloads(this, message); + + if (this->callback) + { + DBG1(DBG_IKE, "received ME_CALLBACK for '%Y'", this->peer_id); + break; + } + + if (!this->connect_id.ptr) + { + DBG1(DBG_IKE, "received ME_CONNECT without ME_CONNECTID notify" + ", aborting"); + this->invalid_syntax = TRUE; + break; + } + + if (!this->connect_key.ptr) + { + DBG1(DBG_IKE, "received ME_CONNECT without ME_CONNECTKEY " + "notify, aborting"); + this->invalid_syntax = TRUE; + break; + } + + if (!this->remote_endpoints->get_count(this->remote_endpoints)) + { + DBG1(DBG_IKE, "received ME_CONNECT without any ME_ENDPOINT " + "payloads, aborting"); + this->invalid_syntax = TRUE; + break; + } + + DBG1(DBG_IKE, "received ME_CONNECT"); + break; + } + default: + break; + } + return NEED_MORE; +} + +/** + * Implementation of task_t.build for responder + */ +static status_t build_r(private_ike_me_t *this, message_t *message) +{ + switch(message->get_exchange_type(message)) + { + case ME_CONNECT: + { + if (this->invalid_syntax) + { + message->add_notify(message, TRUE, INVALID_SYNTAX, chunk_empty); + break; + } + + if (this->callback) + { + /* we got a callback from the mediation server, initiate the + * queued mediated connecction */ + charon->connect_manager->check_and_initiate( + charon->connect_manager, + this->ike_sa->get_id(this->ike_sa), + this->ike_sa->get_my_id(this->ike_sa), this->peer_id); + return SUCCESS; + } + + if (this->response) + { + /* FIXME: handle result of set_responder_data + * as initiator, upon receiving a response from another peer, + * update the checklist and start sending checks */ + charon->connect_manager->set_responder_data( + charon->connect_manager, + this->connect_id, this->connect_key, + this->remote_endpoints); + } + else + { + /* FIXME: handle result of set_initiator_data + * as responder, create a checklist with the initiator's data */ + charon->connect_manager->set_initiator_data( + charon->connect_manager, + this->peer_id, this->ike_sa->get_my_id(this->ike_sa), + this->connect_id, this->connect_key, + this->remote_endpoints, FALSE); + if (this->ike_sa->respond(this->ike_sa, this->peer_id, + this->connect_id) != SUCCESS) + { + return FAILED; + } + } + break; + } + default: + break; + } + return SUCCESS; +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t process_i(private_ike_me_t *this, message_t *message) +{ + switch(message->get_exchange_type(message)) + { + case IKE_SA_INIT: + { + process_payloads(this, message); + if (!this->mediation) + { + DBG1(DBG_IKE, "server did not return a ME_MEDIATION, aborting"); + return FAILED; + } + return NEED_MORE; + } + case IKE_AUTH: + { + process_payloads(this, message); + /* FIXME: we should update the server reflexive endpoint somehow, + * if mobike notices a change */ + endpoint_notify_t *reflexive; + if (this->remote_endpoints->get_first(this->remote_endpoints, + (void**)&reflexive) == SUCCESS && + reflexive->get_type(reflexive) == SERVER_REFLEXIVE) + { /* FIXME: should we accept this endpoint even if we did not send + * a request? */ + host_t *endpoint = reflexive->get_host(reflexive); + endpoint = endpoint->clone(endpoint); + this->ike_sa->set_server_reflexive_host(this->ike_sa, endpoint); + } + break; + } + case ME_CONNECT: + { + process_payloads(this, message); + + if (this->failed) + { + DBG1(DBG_IKE, "peer '%Y' is not online", this->peer_id); + /* FIXME: notify the mediated connection (job?) */ + } + else + { + if (this->response) + { + /* FIXME: handle result of set_responder_data. */ + /* as responder, we update the checklist and start sending + * checks */ + charon->connect_manager->set_responder_data( + charon->connect_manager, this->connect_id, + this->connect_key, this->local_endpoints); + } + else + { + /* FIXME: handle result of set_initiator_data */ + /* as initiator, we create a checklist and set the + * initiator's data */ + charon->connect_manager->set_initiator_data( + charon->connect_manager, + this->ike_sa->get_my_id(this->ike_sa), + this->peer_id, this->connect_id, this->connect_key, + this->local_endpoints, TRUE); + /* FIXME: also start a timer for the whole transaction + * (maybe within the connect_manager?) */ + } + } + break; + } + default: + break; + } + return SUCCESS; +} + +/** + * Implementation of task_t.build for initiator (mediation server) + */ +static status_t build_i_ms(private_ike_me_t *this, message_t *message) +{ + switch(message->get_exchange_type(message)) + { + case ME_CONNECT: + { + id_payload_t *id_payload; + id_payload = id_payload_create_from_identification(ID_PEER, + this->peer_id); + message->add_payload(message, (payload_t*)id_payload); + + if (this->callback) + { + message->add_notify(message, FALSE, ME_CALLBACK, chunk_empty); + } + else + { + if (this->response) + { + message->add_notify(message, FALSE, ME_RESPONSE, + chunk_empty); + } + message->add_notify(message, FALSE, ME_CONNECTID, + this->connect_id); + message->add_notify(message, FALSE, ME_CONNECTKEY, + this->connect_key); + add_endpoints_to_message(message, this->remote_endpoints); + } + break; + } + default: + break; + } + return NEED_MORE; +} + +/** + * Implementation of task_t.process for responder (mediation server) + */ +static status_t process_r_ms(private_ike_me_t *this, message_t *message) +{ + switch(message->get_exchange_type(message)) + { + case IKE_SA_INIT: + { + /* FIXME: we should check for SA* and TS* payloads. if there are + * any, send NO_ADDITIONAL_SAS back and delete this SA */ + process_payloads(this, message); + return this->mediation ? NEED_MORE : SUCCESS; + } + case IKE_AUTH: + { + /* FIXME: we should check whether the current peer_config is + * configured as mediation connection */ + process_payloads(this, message); + break; + } + case CREATE_CHILD_SA: + { + /* FIXME: if this is not to rekey the IKE SA we have to return a + * NO_ADDITIONAL_SAS and then delete the SA */ + break; + } + case ME_CONNECT: + { + id_payload_t *id_payload; + id_payload = (id_payload_t*)message->get_payload(message, ID_PEER); + if (!id_payload) + { + DBG1(DBG_IKE, "received ME_CONNECT without ID_PEER payload" + ", aborting"); + this->invalid_syntax = TRUE; + break; + } + this->peer_id = id_payload->get_identification(id_payload); + + process_payloads(this, message); + + if (!this->connect_id.ptr) + { + DBG1(DBG_IKE, "received ME_CONNECT without ME_CONNECTID notify" + ", aborting"); + this->invalid_syntax = TRUE; + break; + } + + if (!this->connect_key.ptr) + { + DBG1(DBG_IKE, "received ME_CONNECT without ME_CONNECTKEY notify" + ", aborting"); + this->invalid_syntax = TRUE; + break; + } + + if (!this->remote_endpoints->get_count(this->remote_endpoints)) + { + DBG1(DBG_IKE, "received ME_CONNECT without any ME_ENDPOINT " + "payloads, aborting"); + this->invalid_syntax = TRUE; + break; + } + break; + } + default: + break; + } + return NEED_MORE; +} + +/** + * Implementation of task_t.build for responder (mediation server) + */ +static status_t build_r_ms(private_ike_me_t *this, message_t *message) +{ + switch(message->get_exchange_type(message)) + { + case IKE_SA_INIT: + { + message->add_notify(message, FALSE, ME_MEDIATION, chunk_empty); + return NEED_MORE; + } + case IKE_AUTH: + { + endpoint_notify_t *endpoint; + if (this->remote_endpoints->get_first(this->remote_endpoints, + (void**)&endpoint) == SUCCESS && + endpoint->get_type(endpoint) == SERVER_REFLEXIVE) + { + host_t *host = this->ike_sa->get_other_host(this->ike_sa); + DBG2(DBG_IKE, "received request for a server reflexive " + "endpoint sending: %#H", host); + endpoint = endpoint_notify_create_from_host(SERVER_REFLEXIVE, + host, NULL); + message->add_payload(message, (payload_t*)endpoint->build_notify(endpoint)); + endpoint->destroy(endpoint); + } + this->ike_sa->act_as_mediation_server(this->ike_sa); + break; + } + case ME_CONNECT: + { + if (this->invalid_syntax) + { + message->add_notify(message, TRUE, INVALID_SYNTAX, chunk_empty); + break; + } + + ike_sa_id_t *peer_sa; + if (this->callback) + { + peer_sa = charon->mediation_manager->check_and_register( + charon->mediation_manager, this->peer_id, + this->ike_sa->get_other_id(this->ike_sa)); + } + else + { + peer_sa = charon->mediation_manager->check( + charon->mediation_manager, this->peer_id); + } + + if (!peer_sa) + { + /* the peer is not online */ + message->add_notify(message, TRUE, ME_CONNECT_FAILED, + chunk_empty); + break; + } + + job_t *job = (job_t*)mediation_job_create(this->peer_id, + this->ike_sa->get_other_id(this->ike_sa), this->connect_id, + this->connect_key, this->remote_endpoints, this->response); + charon->processor->queue_job(charon->processor, job); + break; + } + default: + break; + } + return SUCCESS; +} + +/** + * Implementation of task_t.process for initiator (mediation server) + */ +static status_t process_i_ms(private_ike_me_t *this, message_t *message) +{ + /* FIXME: theoretically we should be prepared to receive a ME_CONNECT_FAILED + * here if the responding peer is not able to proceed. in this case we shall + * notify the initiating peer with a ME_CONNECT request containing only a + * ME_CONNECT_FAILED */ + return SUCCESS; +} + +/** + * Implementation of ike_me.connect + */ +static void me_connect(private_ike_me_t *this, identification_t *peer_id) +{ + this->peer_id = peer_id->clone(peer_id); +} + +/** + * Implementation of ike_me.respond + */ +static void me_respond(private_ike_me_t *this, identification_t *peer_id, + chunk_t connect_id) +{ + this->peer_id = peer_id->clone(peer_id); + this->connect_id = chunk_clone(connect_id); + this->response = TRUE; +} + +/** + * Implementation of ike_me.callback + */ +static void me_callback(private_ike_me_t *this, identification_t *peer_id) +{ + this->peer_id = peer_id->clone(peer_id); + this->callback = TRUE; +} + +/** + * Implementation of ike_me.relay + */ +static void relay(private_ike_me_t *this, identification_t *requester, + chunk_t connect_id, chunk_t connect_key, + linked_list_t *endpoints, bool response) +{ + this->peer_id = requester->clone(requester); + this->connect_id = chunk_clone(connect_id); + this->connect_key = chunk_clone(connect_key); + + this->remote_endpoints->destroy_offset(this->remote_endpoints, + offsetof(endpoint_notify_t, destroy)); + this->remote_endpoints = endpoints->clone_offset(endpoints, + offsetof(endpoint_notify_t, clone)); + + this->response = response; +} + +/** + * Implementation of task_t.get_type + */ +static task_type_t get_type(private_ike_me_t *this) +{ + return IKE_ME; +} + +/** + * Implementation of task_t.migrate + */ +static void migrate(private_ike_me_t *this, ike_sa_t *ike_sa) +{ + this->ike_sa = ike_sa; +} + +/** + * Implementation of task_t.destroy + */ +static void destroy(private_ike_me_t *this) +{ + DESTROY_IF(this->peer_id); + + chunk_free(&this->connect_id); + chunk_free(&this->connect_key); + + this->local_endpoints->destroy_offset(this->local_endpoints, + offsetof(endpoint_notify_t, destroy)); + this->remote_endpoints->destroy_offset(this->remote_endpoints, + offsetof(endpoint_notify_t, destroy)); + + DESTROY_IF(this->mediated_cfg); + free(this); +} + +/* + * Described in header. + */ +ike_me_t *ike_me_create(ike_sa_t *ike_sa, bool initiator) +{ + private_ike_me_t *this = malloc_thing(private_ike_me_t); + + this->public.task.get_type = (task_type_t(*)(task_t*))get_type; + this->public.task.migrate = (void(*)(task_t*,ike_sa_t*))migrate; + this->public.task.destroy = (void(*)(task_t*))destroy; + + if (ike_sa->has_condition(ike_sa, COND_ORIGINAL_INITIATOR)) + { + if (initiator) + { + this->public.task.build = (status_t(*)(task_t*,message_t*))build_i; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_i; + } + else + { + this->public.task.build = (status_t(*)(task_t*,message_t*))build_r; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_r; + } + } + else + { + /* mediation server */ + if (initiator) + { + this->public.task.build = (status_t(*)(task_t*,message_t*))build_i_ms; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_i_ms; + } + else + { + this->public.task.build = (status_t(*)(task_t*,message_t*))build_r_ms; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_r_ms; + } + } + + this->public.connect = (void(*)(ike_me_t*,identification_t*))me_connect; + this->public.respond = (void(*)(ike_me_t*,identification_t*,chunk_t))me_respond; + this->public.callback = (void(*)(ike_me_t*,identification_t*))me_callback; + this->public.relay = (void(*)(ike_me_t*,identification_t*,chunk_t,chunk_t,linked_list_t*,bool))relay; + + this->ike_sa = ike_sa; + this->initiator = initiator; + + this->peer_id = NULL; + this->connect_id = chunk_empty; + this->connect_key = chunk_empty; + this->local_endpoints = linked_list_create(); + this->remote_endpoints = linked_list_create(); + this->mediation = FALSE; + this->response = FALSE; + this->callback = FALSE; + this->failed = FALSE; + this->invalid_syntax = FALSE; + + this->mediated_cfg = NULL; + + return &this->public; +} diff --git a/src/libcharon/sa/tasks/ike_me.h b/src/libcharon/sa/tasks/ike_me.h new file mode 100644 index 000000000..31285a426 --- /dev/null +++ b/src/libcharon/sa/tasks/ike_me.h @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2007 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 ike_me ike_me + * @{ @ingroup tasks + */ + +#ifndef IKE_ME_H_ +#define IKE_ME_H_ + +typedef struct ike_me_t ike_me_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/tasks/task.h> + +/** + * Task of type IKE_ME, detects and handles IKE-ME extensions. + * + * This tasks handles the ME_MEDIATION Notify exchange to setup a mediation + * connection, allows to initiate mediated connections using ME_CONNECT + * exchanges and to request reflexive addresses from the mediation server using + * ME_ENDPOINT notifies. + * + * @note This task has to be activated before the IKE_AUTH task, because that + * task generates the IKE_SA_INIT message so that no more payloads can be added + * to it afterwards. + */ +struct ike_me_t { + /** + * Implements the task_t interface + */ + task_t task; + + /** + * Initiates a connection with another peer (i.e. sends a ME_CONNECT + * to the mediation server) + * + * @param peer_id ID of the other peer (gets cloned) + */ + void (*connect)(ike_me_t *this, identification_t *peer_id); + + /** + * Responds to a ME_CONNECT from another peer (i.e. sends a ME_CONNECT + * to the mediation server) + * + * Data gets cloned. + * + * @param peer_id ID of the other peer + * @param connect_id the connect ID as provided by the initiator + */ + void (*respond)(ike_me_t *this, identification_t *peer_id, + chunk_t connect_id); + + /** + * Sends a ME_CALLBACK to a peer that previously requested some other peer. + * + * @param peer_id ID of the other peer (gets cloned) + */ + void (*callback)(ike_me_t *this, identification_t *peer_id); + + /** + * Relays data to another peer (i.e. sends a ME_CONNECT to the peer) + * + * Data gets cloned. + * + * @param requester ID of the requesting peer + * @param connect_id content of the ME_CONNECTID notify + * @param connect_key content of the ME_CONNECTKEY notify + * @param endpoints endpoints + * @param response TRUE if this is a response + */ + void (*relay)(ike_me_t *this, identification_t *requester, + chunk_t connect_id, chunk_t connect_key, + linked_list_t *endpoints, bool response); +}; + +/** + * Create a new ike_me task. + * + * @param ike_sa IKE_SA this task works for + * @param initiator TRUE if task is initiated by us + * @return ike_me task to be handled by the task_manager + */ +ike_me_t *ike_me_create(ike_sa_t *ike_sa, bool initiator); + +#endif /** IKE_ME_H_ @}*/ diff --git a/src/libcharon/sa/tasks/ike_mobike.c b/src/libcharon/sa/tasks/ike_mobike.c new file mode 100644 index 000000000..a62886f02 --- /dev/null +++ b/src/libcharon/sa/tasks/ike_mobike.c @@ -0,0 +1,659 @@ +/* + * Copyright (C) 2007 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 "ike_mobike.h" + +#include <string.h> + +#include <daemon.h> +#include <sa/tasks/ike_natd.h> +#include <encoding/payloads/notify_payload.h> + +#define COOKIE2_SIZE 16 +#define MAX_ADDITIONAL_ADDRS 8 + +typedef struct private_ike_mobike_t private_ike_mobike_t; + +/** + * Private members of a ike_mobike_t task. + */ +struct private_ike_mobike_t { + + /** + * Public methods and task_t interface. + */ + ike_mobike_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; + + /** + * Are we the initiator? + */ + bool initiator; + + /** + * cookie2 value to verify new addresses + */ + chunk_t cookie2; + + /** + * NAT discovery reusing the IKE_NATD task + */ + ike_natd_t *natd; + + /** + * use task to update addresses + */ + bool update; + + /** + * do routability check + */ + bool check; + + /** + * include address list update + */ + bool address; +}; + +/** + * flush the IKE_SAs list of additional addresses + */ +static void flush_additional_addresses(private_ike_mobike_t *this) +{ + iterator_t *iterator; + host_t *host; + + iterator = this->ike_sa->create_additional_address_iterator(this->ike_sa); + while (iterator->iterate(iterator, (void**)&host)) + { + iterator->remove(iterator); + host->destroy(host); + } + iterator->destroy(iterator); +} + + +/** + * read notifys from message and evaluate them + */ +static void process_payloads(private_ike_mobike_t *this, message_t *message) +{ + enumerator_t *enumerator; + payload_t *payload; + bool first = TRUE; + + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + int family = AF_INET; + notify_payload_t *notify; + chunk_t data; + host_t *host; + + if (payload->get_type(payload) != NOTIFY) + { + continue; + } + notify = (notify_payload_t*)payload; + switch (notify->get_notify_type(notify)) + { + case MOBIKE_SUPPORTED: + { + peer_cfg_t *peer_cfg; + + peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); + if (!this->initiator && + peer_cfg && !peer_cfg->use_mobike(peer_cfg)) + { + DBG1(DBG_IKE, "peer supports MOBIKE, but disabled in config"); + } + else + { + DBG1(DBG_IKE, "peer supports MOBIKE"); + this->ike_sa->enable_extension(this->ike_sa, EXT_MOBIKE); + } + break; + } + case COOKIE2: + { + chunk_free(&this->cookie2); + this->cookie2 = chunk_clone(notify->get_notification_data(notify)); + break; + } + case ADDITIONAL_IP6_ADDRESS: + { + family = AF_INET6; + /* fall through */ + } + case ADDITIONAL_IP4_ADDRESS: + { + if (first) + { /* an ADDITIONAL_*_ADDRESS means replace, so flush once */ + flush_additional_addresses(this); + first = FALSE; + } + data = notify->get_notification_data(notify); + host = host_create_from_chunk(family, data, 0); + DBG2(DBG_IKE, "got additional MOBIKE peer address: %H", host); + this->ike_sa->add_additional_address(this->ike_sa, host); + break; + } + case UPDATE_SA_ADDRESSES: + { + this->update = TRUE; + break; + } + case NO_ADDITIONAL_ADDRESSES: + { + flush_additional_addresses(this); + break; + } + case NAT_DETECTION_SOURCE_IP: + case NAT_DETECTION_DESTINATION_IP: + { + /* NAT check in this MOBIKE exchange, create subtask for it */ + if (this->natd == NULL) + { + this->natd = ike_natd_create(this->ike_sa, this->initiator); + } + break; + } + default: + break; + } + } + enumerator->destroy(enumerator); +} + +/** + * Add ADDITIONAL_*_ADDRESS notifys depending on our address list + */ +static void build_address_list(private_ike_mobike_t *this, message_t *message) +{ + enumerator_t *enumerator; + host_t *host, *me; + notify_type_t type; + int added = 0; + + me = this->ike_sa->get_my_host(this->ike_sa); + enumerator = charon->kernel_interface->create_address_enumerator( + charon->kernel_interface, FALSE, FALSE); + while (enumerator->enumerate(enumerator, (void**)&host)) + { + if (me->ip_equals(me, host)) + { /* "ADDITIONAL" means do not include IKE_SAs host */ + continue; + } + switch (host->get_family(host)) + { + case AF_INET: + type = ADDITIONAL_IP4_ADDRESS; + break; + case AF_INET6: + type = ADDITIONAL_IP6_ADDRESS; + break; + default: + continue; + } + message->add_notify(message, FALSE, type, host->get_address(host)); + if (++added >= MAX_ADDITIONAL_ADDRS) + { /* limit number of notifys, some implementations do not like too + * many of them (f.e. strongSwan ;-) */ + break; + } + } + if (!added) + { + message->add_notify(message, FALSE, NO_ADDITIONAL_ADDRESSES, chunk_empty); + } + enumerator->destroy(enumerator); +} + +/** + * build a cookie and add it to the message + */ +static void build_cookie(private_ike_mobike_t *this, message_t *message) +{ + rng_t *rng; + + chunk_free(&this->cookie2); + rng = lib->crypto->create_rng(lib->crypto, RNG_STRONG); + if (rng) + { + rng->allocate_bytes(rng, COOKIE2_SIZE, &this->cookie2); + rng->destroy(rng); + message->add_notify(message, FALSE, COOKIE2, this->cookie2); + } +} + +/** + * update addresses of associated CHILD_SAs + */ +static void update_children(private_ike_mobike_t *this) +{ + iterator_t *iterator; + child_sa_t *child_sa; + + iterator = this->ike_sa->create_child_sa_iterator(this->ike_sa); + while (iterator->iterate(iterator, (void**)&child_sa)) + { + if (child_sa->update(child_sa, + this->ike_sa->get_my_host(this->ike_sa), + this->ike_sa->get_other_host(this->ike_sa), + this->ike_sa->get_virtual_ip(this->ike_sa, TRUE), + this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY)) == NOT_SUPPORTED) + { + this->ike_sa->rekey_child_sa(this->ike_sa, + child_sa->get_protocol(child_sa), + child_sa->get_spi(child_sa, TRUE)); + } + } + iterator->destroy(iterator); +} + +/** + * Apply port of old address if it equals new, port otherwise + */ +static void apply_port(private_ike_mobike_t *this, host_t *host, host_t *old, + u_int16_t port) +{ + if (host->ip_equals(host, old)) + { + host->set_port(host, old->get_port(old)); + } + else + { + if (port == IKEV2_UDP_PORT) + { + host->set_port(host, IKEV2_NATT_PORT); + } + else + { + host->set_port(host, port); + } + } +} + +/** + * Implementation of ike_mobike_t.transmit + */ +static void transmit(private_ike_mobike_t *this, packet_t *packet) +{ + host_t *me, *other, *me_old, *other_old; + iterator_t *iterator; + ike_cfg_t *ike_cfg; + packet_t *copy; + + if (!this->check) + { + return; + } + + me_old = this->ike_sa->get_my_host(this->ike_sa); + other_old = this->ike_sa->get_other_host(this->ike_sa); + ike_cfg = this->ike_sa->get_ike_cfg(this->ike_sa); + + me = charon->kernel_interface->get_source_addr( + charon->kernel_interface, other_old, NULL); + if (me) + { + apply_port(this, me, me_old, ike_cfg->get_my_port(ike_cfg)); + DBG1(DBG_IKE, "checking original path %#H - %#H", me, other_old); + copy = packet->clone(packet); + copy->set_source(copy, me); + charon->sender->send(charon->sender, copy); + } + + iterator = this->ike_sa->create_additional_address_iterator(this->ike_sa); + while (iterator->iterate(iterator, (void**)&other)) + { + me = charon->kernel_interface->get_source_addr( + charon->kernel_interface, other, NULL); + if (me) + { + if (me->get_family(me) != other->get_family(other)) + { + me->destroy(me); + continue; + } + /* reuse port for an active address, 4500 otherwise */ + apply_port(this, me, me_old, ike_cfg->get_my_port(ike_cfg)); + other = other->clone(other); + apply_port(this, other, other_old, ike_cfg->get_other_port(ike_cfg)); + DBG1(DBG_IKE, "checking path %#H - %#H", me, other); + copy = packet->clone(packet); + copy->set_source(copy, me); + copy->set_destination(copy, other); + charon->sender->send(charon->sender, copy); + } + } + iterator->destroy(iterator); +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t build_i(private_ike_mobike_t *this, message_t *message) +{ + if (message->get_message_id(message) == 1) + { /* only in first IKE_AUTH */ + message->add_notify(message, FALSE, MOBIKE_SUPPORTED, chunk_empty); + build_address_list(this, message); + } + else if (message->get_exchange_type(message) == INFORMATIONAL) + { + host_t *old, *new; + + /* we check if the existing address is still valid */ + old = message->get_source(message); + new = charon->kernel_interface->get_source_addr(charon->kernel_interface, + message->get_destination(message), old); + if (new) + { + if (!new->ip_equals(new, old)) + { + new->set_port(new, old->get_port(old)); + message->set_source(message, new); + } + else + { + new->destroy(new); + } + } + if (this->update) + { + message->add_notify(message, FALSE, UPDATE_SA_ADDRESSES, chunk_empty); + build_cookie(this, message); + update_children(this); + } + if (this->address) + { + build_address_list(this, message); + } + if (this->natd) + { + this->natd->task.build(&this->natd->task, message); + } + } + return NEED_MORE; +} + +/** + * Implementation of task_t.process for responder + */ +static status_t process_r(private_ike_mobike_t *this, message_t *message) +{ + if (message->get_message_id(message) == 1) + { /* only first IKE_AUTH */ + process_payloads(this, message); + } + else if (message->get_exchange_type(message) == INFORMATIONAL) + { + process_payloads(this, message); + if (this->update) + { + host_t *me, *other; + + me = message->get_destination(message); + other = message->get_source(message); + this->ike_sa->set_my_host(this->ike_sa, me->clone(me)); + this->ike_sa->set_other_host(this->ike_sa, other->clone(other)); + } + + if (this->natd) + { + this->natd->task.process(&this->natd->task, message); + } + } + return NEED_MORE; +} + +/** + * Implementation of task_t.build for responder + */ +static status_t build_r(private_ike_mobike_t *this, message_t *message) +{ + if (message->get_exchange_type(message) == IKE_AUTH && + this->ike_sa->get_state(this->ike_sa) == IKE_ESTABLISHED) + { + if (this->ike_sa->supports_extension(this->ike_sa, EXT_MOBIKE)) + { + message->add_notify(message, FALSE, MOBIKE_SUPPORTED, chunk_empty); + build_address_list(this, message); + } + return SUCCESS; + } + else if (message->get_exchange_type(message) == INFORMATIONAL) + { + if (this->natd) + { + this->natd->task.build(&this->natd->task, message); + } + if (this->cookie2.ptr) + { + message->add_notify(message, FALSE, COOKIE2, this->cookie2); + chunk_free(&this->cookie2); + } + if (this->update) + { + update_children(this); + } + return SUCCESS; + } + return NEED_MORE; +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t process_i(private_ike_mobike_t *this, message_t *message) +{ + if (message->get_exchange_type(message) == IKE_AUTH && + this->ike_sa->get_state(this->ike_sa) == IKE_ESTABLISHED) + { + process_payloads(this, message); + return SUCCESS; + } + else if (message->get_exchange_type(message) == INFORMATIONAL) + { + u_int32_t updates = this->ike_sa->get_pending_updates(this->ike_sa) - 1; + this->ike_sa->set_pending_updates(this->ike_sa, updates); + if (updates > 0) + { + /* newer update queued, ignore this one */ + return SUCCESS; + } + if (this->cookie2.ptr) + { /* check cookie if we included one */ + chunk_t cookie2; + + cookie2 = this->cookie2; + this->cookie2 = chunk_empty; + process_payloads(this, message); + if (!chunk_equals(cookie2, this->cookie2)) + { + chunk_free(&cookie2); + DBG1(DBG_IKE, "COOKIE2 mismatch, closing IKE_SA"); + return FAILED; + } + chunk_free(&cookie2); + } + else + { + process_payloads(this, message); + } + if (this->natd) + { + this->natd->task.process(&this->natd->task, message); + if (this->natd->has_mapping_changed(this->natd)) + { + /* force an update if mappings have changed */ + this->update = this->check = TRUE; + DBG1(DBG_IKE, "detected changes in NAT mappings, " + "initiating MOBIKE update"); + } + } + if (this->update) + { + /* update again, as NAT state may have changed */ + update_children(this); + } + if (this->check) + { + host_t *me_new, *me_old, *other_new, *other_old; + + me_new = message->get_destination(message); + other_new = message->get_source(message); + me_old = this->ike_sa->get_my_host(this->ike_sa); + other_old = this->ike_sa->get_other_host(this->ike_sa); + + if (!me_new->equals(me_new, me_old)) + { + this->update = TRUE; + this->ike_sa->set_my_host(this->ike_sa, me_new->clone(me_new)); + } + if (!other_new->equals(other_new, other_old)) + { + this->update = TRUE; + this->ike_sa->set_other_host(this->ike_sa, other_new->clone(other_new)); + } + if (this->update) + { + /* start the update with the same task */ + this->check = FALSE; + this->address = FALSE; + if (this->natd) + { + this->natd->task.destroy(&this->natd->task); + } + this->natd = ike_natd_create(this->ike_sa, this->initiator); + this->ike_sa->set_pending_updates(this->ike_sa, 1); + return NEED_MORE; + } + } + return SUCCESS; + } + return NEED_MORE; +} + +/** + * Implementation of ike_mobike_t.roam. + */ +static void roam(private_ike_mobike_t *this, bool address) +{ + this->check = TRUE; + this->address = address; + this->ike_sa->set_pending_updates(this->ike_sa, + this->ike_sa->get_pending_updates(this->ike_sa) + 1); +} + +/** + * Implementation of ike_mobike_t.dpd + */ +static void dpd(private_ike_mobike_t *this) +{ + if (!this->natd) + { + this->natd = ike_natd_create(this->ike_sa, this->initiator); + } + this->address = FALSE; + this->ike_sa->set_pending_updates(this->ike_sa, + this->ike_sa->get_pending_updates(this->ike_sa) + 1); +} + +/** + * Implementation of ike_mobike_t.is_probing. + */ +static bool is_probing(private_ike_mobike_t *this) +{ + return this->check; +} + +/** + * Implementation of task_t.get_type + */ +static task_type_t get_type(private_ike_mobike_t *this) +{ + return IKE_MOBIKE; +} + +/** + * Implementation of task_t.migrate + */ +static void migrate(private_ike_mobike_t *this, ike_sa_t *ike_sa) +{ + chunk_free(&this->cookie2); + this->ike_sa = ike_sa; + if (this->natd) + { + this->natd->task.migrate(&this->natd->task, ike_sa); + } +} + +/** + * Implementation of task_t.destroy + */ +static void destroy(private_ike_mobike_t *this) +{ + chunk_free(&this->cookie2); + if (this->natd) + { + this->natd->task.destroy(&this->natd->task); + } + free(this); +} + +/* + * Described in header. + */ +ike_mobike_t *ike_mobike_create(ike_sa_t *ike_sa, bool initiator) +{ + private_ike_mobike_t *this = malloc_thing(private_ike_mobike_t); + + this->public.roam = (void(*)(ike_mobike_t*,bool))roam; + this->public.dpd = (void(*)(ike_mobike_t*))dpd; + this->public.transmit = (void(*)(ike_mobike_t*,packet_t*))transmit; + this->public.is_probing = (bool(*)(ike_mobike_t*))is_probing; + this->public.task.get_type = (task_type_t(*)(task_t*))get_type; + this->public.task.migrate = (void(*)(task_t*,ike_sa_t*))migrate; + this->public.task.destroy = (void(*)(task_t*))destroy; + + if (initiator) + { + this->public.task.build = (status_t(*)(task_t*,message_t*))build_i; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_i; + } + else + { + this->public.task.build = (status_t(*)(task_t*,message_t*))build_r; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_r; + } + + this->ike_sa = ike_sa; + this->initiator = initiator; + this->update = FALSE; + this->check = FALSE; + this->address = TRUE; + this->cookie2 = chunk_empty; + this->natd = NULL; + + return &this->public; +} + diff --git a/src/libcharon/sa/tasks/ike_mobike.h b/src/libcharon/sa/tasks/ike_mobike.h new file mode 100644 index 000000000..05b2224d1 --- /dev/null +++ b/src/libcharon/sa/tasks/ike_mobike.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2007 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 ike_mobike ike_mobike + * @{ @ingroup tasks + */ + +#ifndef IKE_MOBIKE_H_ +#define IKE_MOBIKE_H_ + +typedef struct ike_mobike_t ike_mobike_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/tasks/task.h> +#include <network/packet.h> + +/** + * Task of type ike_mobike, detects and handles MOBIKE extension. + * + * The MOBIKE extension is defined in RFC4555. It allows to update IKE + * and IPsec tunnel addresses. + * This tasks handles the MOBIKE_SUPPORTED notify exchange to detect MOBIKE + * support, allows the exchange of ADDITIONAL_*_ADDRESS to exchange additional + * endpoints and handles the UPDATE_SA_ADDRESS notify to finally update + * endpoints. + */ +struct ike_mobike_t { + + /** + * Implements the task_t interface + */ + task_t task; + + /** + * Use the task to roam to other addresses. + * + * @param address TRUE to include address list update + */ + void (*roam)(ike_mobike_t *this, bool address); + + /** + * Use the task for a DPD check which detects changes in NAT mappings. + */ + void (*dpd)(ike_mobike_t *this); + + /** + * Transmision hook, called by task manager. + * + * The task manager calls this hook whenever it transmits a packet. It + * allows the mobike task to send the packet on multiple paths to do path + * probing. + * + * @param packet the packet to transmit + */ + void (*transmit)(ike_mobike_t *this, packet_t *packet); + + /** + * Check if this task is probing for routability. + * + * @return TRUE if task is probing + */ + bool (*is_probing)(ike_mobike_t *this); +}; + +/** + * Create a new ike_mobike task. + * + * @param ike_sa IKE_SA this task works for + * @param initiator TRUE if taks is initiated by us + * @return ike_mobike task to handle by the task_manager + */ +ike_mobike_t *ike_mobike_create(ike_sa_t *ike_sa, bool initiator); + +#endif /** IKE_MOBIKE_H_ @}*/ diff --git a/src/libcharon/sa/tasks/ike_natd.c b/src/libcharon/sa/tasks/ike_natd.c new file mode 100644 index 000000000..9ea20ba36 --- /dev/null +++ b/src/libcharon/sa/tasks/ike_natd.c @@ -0,0 +1,490 @@ +/* + * Copyright (C) 2006-2007 Martin Willi + * Copyright (C) 2006 Tobias Brunner, 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 "ike_natd.h" + +#include <string.h> + +#include <daemon.h> +#include <config/peer_cfg.h> +#include <crypto/hashers/hasher.h> +#include <encoding/payloads/notify_payload.h> + + +typedef struct private_ike_natd_t private_ike_natd_t; + +/** + * Private members of a ike_natd_t task. + */ +struct private_ike_natd_t { + + /** + * Public methods and task_t interface. + */ + ike_natd_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; + + /** + * Are we the initiator? + */ + bool initiator; + + /** + * Hasher used to build NAT detection hashes + */ + hasher_t *hasher; + + /** + * Did we process any NAT detection notifys for a source address? + */ + bool src_seen; + + /** + * Did we process any NAT detection notifys 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; + + /** + * whether NAT mappings for our NATed address has changed + */ + bool mapping_changed; +}; + + +/** + * Build NAT detection hash for a host + */ +static chunk_t generate_natd_hash(private_ike_natd_t *this, + ike_sa_id_t *ike_sa_id, host_t *host) +{ + chunk_t natd_chunk, spi_i_chunk, spi_r_chunk, addr_chunk, port_chunk; + chunk_t natd_hash; + u_int64_t spi_i, spi_r; + u_int16_t port; + + /* prepare all required chunks */ + spi_i = ike_sa_id->get_initiator_spi(ike_sa_id); + spi_r = ike_sa_id->get_responder_spi(ike_sa_id); + spi_i_chunk.ptr = (void*)&spi_i; + spi_i_chunk.len = sizeof(spi_i); + spi_r_chunk.ptr = (void*)&spi_r; + spi_r_chunk.len = sizeof(spi_r); + port = htons(host->get_port(host)); + port_chunk.ptr = (void*)&port; + port_chunk.len = sizeof(port); + addr_chunk = host->get_address(host); + + /* natd_hash = SHA1( spi_i | spi_r | address | port ) */ + natd_chunk = chunk_cat("cccc", spi_i_chunk, spi_r_chunk, addr_chunk, port_chunk); + this->hasher->allocate_hash(this->hasher, natd_chunk, &natd_hash); + DBG3(DBG_IKE, "natd_chunk %B", &natd_chunk); + DBG3(DBG_IKE, "natd_hash %B", &natd_hash); + + chunk_free(&natd_chunk); + return natd_hash; +} + +/** + * build a faked NATD payload to enforce UDP encap + */ +static chunk_t generate_natd_hash_faked(private_ike_natd_t *this) +{ + rng_t *rng; + chunk_t chunk; + + rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK); + if (!rng) + { + DBG1(DBG_IKE, "unable to get random bytes for NATD fake"); + return chunk_empty; + } + rng->allocate_bytes(rng, HASH_SIZE_SHA1, &chunk); + rng->destroy(rng); + return chunk; +} + +/** + * Build a NAT detection notify payload. + */ +static notify_payload_t *build_natd_payload(private_ike_natd_t *this, + notify_type_t type, host_t *host) +{ + chunk_t hash; + notify_payload_t *notify; + ike_sa_id_t *ike_sa_id; + ike_cfg_t *config; + + ike_sa_id = this->ike_sa->get_id(this->ike_sa); + config = this->ike_sa->get_ike_cfg(this->ike_sa); + if (config->force_encap(config) && type == NAT_DETECTION_SOURCE_IP) + { + hash = generate_natd_hash_faked(this); + } + else + { + hash = generate_natd_hash(this, ike_sa_id, host); + } + notify = notify_payload_create(); + notify->set_notify_type(notify, type); + notify->set_notification_data(notify, hash); + chunk_free(&hash); + + return notify; +} + +/** + * read notifys from message and evaluate them + */ +static void process_payloads(private_ike_natd_t *this, message_t *message) +{ + enumerator_t *enumerator; + payload_t *payload; + notify_payload_t *notify; + chunk_t hash, src_hash, dst_hash; + ike_sa_id_t *ike_sa_id; + host_t *me, *other; + ike_cfg_t *config; + + /* Precompute NAT-D hashes for incoming NAT notify 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) != NOTIFY) + { + continue; + } + notify = (notify_payload_t*)payload; + switch (notify->get_notify_type(notify)) + { + case NAT_DETECTION_DESTINATION_IP: + { + this->dst_seen = TRUE; + hash = notify->get_notification_data(notify); + if (!this->dst_matched) + { + DBG3(DBG_IKE, "received dst_hash %B", &hash); + if (chunk_equals(hash, dst_hash)) + { + this->dst_matched = TRUE; + } + } + /* RFC4555 says we should also compare against IKE_SA_INIT + * NATD payloads, but this does not work: We are running + * there at port 500, but use 4500 afterwards... */ + if (message->get_exchange_type(message) == INFORMATIONAL && + this->initiator && !this->dst_matched) + { + this->mapping_changed = this->ike_sa->has_mapping_changed( + this->ike_sa, hash); + } + break; + } + case NAT_DETECTION_SOURCE_IP: + { + this->src_seen = TRUE; + if (!this->src_matched) + { + hash = notify->get_notification_data(notify); + DBG3(DBG_IKE, "received src_hash %B", &hash); + if (chunk_equals(hash, src_hash)) + { + this->src_matched = TRUE; + } + } + break; + } + default: + break; + } + } + enumerator->destroy(enumerator); + + chunk_free(&src_hash); + chunk_free(&dst_hash); + + if (this->src_seen && this->dst_seen) + { + this->ike_sa->enable_extension(this->ike_sa, EXT_NATT); + + 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); + } + } +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t process_i(private_ike_natd_t *this, message_t *message) +{ + process_payloads(this, message); + + if (message->get_exchange_type(message) == IKE_SA_INIT) + { + peer_cfg_t *peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); + +#ifdef ME + /* if we are on a mediated connection we have already switched to + * port 4500 and the correct destination port is already configured, + * therefore we must not switch again */ + if (peer_cfg->get_mediated_by(peer_cfg)) + { + return SUCCESS; + } +#endif /* ME */ + + if (this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY) || +#ifdef ME + /* if we are on a mediation connection we switch to port 4500 even + * if no NAT is detected. */ + peer_cfg->is_mediation(peer_cfg) || +#endif /* ME */ + /* if peer supports NAT-T, we switch to port 4500 even + * if no NAT is detected. MOBIKE requires this. */ + (peer_cfg->use_mobike(peer_cfg) && + this->ike_sa->supports_extension(this->ike_sa, EXT_NATT))) + { + host_t *me, *other; + + /* do not switch if we have a custom port from mobike/NAT */ + me = this->ike_sa->get_my_host(this->ike_sa); + if (me->get_port(me) == IKEV2_UDP_PORT) + { + me->set_port(me, IKEV2_NATT_PORT); + } + other = this->ike_sa->get_other_host(this->ike_sa); + if (other->get_port(other) == IKEV2_UDP_PORT) + { + other->set_port(other, IKEV2_NATT_PORT); + } + } + } + + return SUCCESS; +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t build_i(private_ike_natd_t *this, message_t *message) +{ + notify_payload_t *notify; + enumerator_t *enumerator; + ike_cfg_t *ike_cfg; + host_t *host; + + if (this->hasher == NULL) + { + DBG1(DBG_IKE, "unable to build NATD payloads, SHA1 not supported"); + return NEED_MORE; + } + + ike_cfg = this->ike_sa->get_ike_cfg(this->ike_sa); + + /* destination is always set */ + host = message->get_destination(message); + notify = build_natd_payload(this, NAT_DETECTION_DESTINATION_IP, host); + message->add_payload(message, (payload_t*)notify); + + /* source may be any, we have 3 possibilities to get our source address: + * 1. It is defined in the config => use the one of the IKE_SA + * 2. We do a routing lookup in the kernel interface + * 3. Include all possbile addresses + */ + host = message->get_source(message); + if (!host->is_anyaddr(host)) + { /* 1. */ + notify = build_natd_payload(this, NAT_DETECTION_SOURCE_IP, host); + message->add_payload(message, (payload_t*)notify); + } + else + { + host = charon->kernel_interface->get_source_addr(charon->kernel_interface, + this->ike_sa->get_other_host(this->ike_sa), NULL); + if (host) + { /* 2. */ + host->set_port(host, ike_cfg->get_my_port(ike_cfg)); + notify = build_natd_payload(this, NAT_DETECTION_SOURCE_IP, host); + message->add_payload(message, (payload_t*)notify); + host->destroy(host); + } + else + { /* 3. */ + enumerator = charon->kernel_interface->create_address_enumerator( + charon->kernel_interface, FALSE, FALSE); + while (enumerator->enumerate(enumerator, (void**)&host)) + { + /* apply port 500 to host, but work on a copy */ + host = host->clone(host); + host->set_port(host, ike_cfg->get_my_port(ike_cfg)); + notify = build_natd_payload(this, NAT_DETECTION_SOURCE_IP, host); + host->destroy(host); + message->add_payload(message, (payload_t*)notify); + } + enumerator->destroy(enumerator); + } + } + return NEED_MORE; +} + +/** + * Implementation of task_t.build for responder + */ +static status_t build_r(private_ike_natd_t *this, message_t *message) +{ + notify_payload_t *notify; + host_t *me, *other; + + /* only add notifies on successfull responses. */ + if (message->get_exchange_type(message) == IKE_SA_INIT && + message->get_payload(message, SECURITY_ASSOCIATION) == NULL) + { + return SUCCESS; + } + + if (this->src_seen && this->dst_seen) + { + if (this->hasher == NULL) + { + DBG1(DBG_IKE, "unable to build NATD payloads, SHA1 not supported"); + return SUCCESS; + } + + /* initiator seems to support NAT detection, add response */ + me = message->get_source(message); + notify = build_natd_payload(this, NAT_DETECTION_SOURCE_IP, me); + message->add_payload(message, (payload_t*)notify); + + other = message->get_destination(message); + notify = build_natd_payload(this, NAT_DETECTION_DESTINATION_IP, other); + message->add_payload(message, (payload_t*)notify); + } + return SUCCESS; +} + +/** + * Implementation of task_t.process for responder + */ +static status_t process_r(private_ike_natd_t *this, message_t *message) +{ + process_payloads(this, message); + + return NEED_MORE; +} + +/** + * Implementation of task_t.get_type + */ +static task_type_t get_type(private_ike_natd_t *this) +{ + return IKE_NATD; +} + +/** + * Implementation of task_t.migrate + */ +static void migrate(private_ike_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; + this->mapping_changed = FALSE; +} + +/** + * Implementation of ike_natd_t.has_mapping_changed + */ +static bool has_mapping_changed(private_ike_natd_t *this) +{ + return this->mapping_changed; +} + +/** + * Implementation of task_t.destroy + */ +static void destroy(private_ike_natd_t *this) +{ + DESTROY_IF(this->hasher); + free(this); +} + +/* + * Described in header. + */ +ike_natd_t *ike_natd_create(ike_sa_t *ike_sa, bool initiator) +{ + private_ike_natd_t *this = malloc_thing(private_ike_natd_t); + + this->public.task.get_type = (task_type_t(*)(task_t*))get_type; + this->public.task.migrate = (void(*)(task_t*,ike_sa_t*))migrate; + this->public.task.destroy = (void(*)(task_t*))destroy; + + if (initiator) + { + this->public.task.build = (status_t(*)(task_t*,message_t*))build_i; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_i; + } + else + { + this->public.task.build = (status_t(*)(task_t*,message_t*))build_r; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_r; + } + + this->public.has_mapping_changed = (bool(*)(ike_natd_t*))has_mapping_changed; + + this->ike_sa = ike_sa; + this->initiator = initiator; + this->hasher = lib->crypto->create_hasher(lib->crypto, HASH_SHA1); + this->src_seen = FALSE; + this->dst_seen = FALSE; + this->src_matched = FALSE; + this->dst_matched = FALSE; + this->mapping_changed = FALSE; + + return &this->public; +} diff --git a/src/libcharon/sa/tasks/ike_natd.h b/src/libcharon/sa/tasks/ike_natd.h new file mode 100644 index 000000000..97b652ead --- /dev/null +++ b/src/libcharon/sa/tasks/ike_natd.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2007 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 ike_natd ike_natd + * @{ @ingroup tasks + */ + +#ifndef IKE_NATD_H_ +#define IKE_NATD_H_ + +typedef struct ike_natd_t ike_natd_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/tasks/task.h> + +/** + * Task of type ike_natd, detects NAT situation in IKE_SA_INIT exchange. + */ +struct ike_natd_t { + + /** + * Implements the task_t interface + */ + task_t task; + + /** + * Check if the NAT mapping has changed for our address. + * + * MOBIKE uses NAT payloads in DPD to detect changes in the NAT mappings. + * + * @return TRUE if mappings have changed + */ + bool (*has_mapping_changed)(ike_natd_t *this); +}; + +/** + * Create a new ike_natd task. + * + * @param ike_sa IKE_SA this task works for + * @param initiator TRUE if thask is the original initator + * @return ike_natd task to handle by the task_manager + */ +ike_natd_t *ike_natd_create(ike_sa_t *ike_sa, bool initiator); + +#endif /** IKE_NATD_H_ @}*/ diff --git a/src/libcharon/sa/tasks/ike_reauth.c b/src/libcharon/sa/tasks/ike_reauth.c new file mode 100644 index 000000000..ac89c358b --- /dev/null +++ b/src/libcharon/sa/tasks/ike_reauth.c @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2006-2008 Martin Willi + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "ike_reauth.h" + +#include <daemon.h> +#include <sa/tasks/ike_delete.h> + + +typedef struct private_ike_reauth_t private_ike_reauth_t; + +/** + * Private members of a ike_reauth_t task. + */ +struct private_ike_reauth_t { + + /** + * Public methods and task_t interface. + */ + ike_reauth_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; + + /** + * reused ike_delete task + */ + ike_delete_t *ike_delete; +}; + +/** + * Implementation of task_t.build for initiator + */ +static status_t build_i(private_ike_reauth_t *this, message_t *message) +{ + return this->ike_delete->task.build(&this->ike_delete->task, message); +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t process_i(private_ike_reauth_t *this, message_t *message) +{ + ike_sa_t *new; + host_t *host; + iterator_t *iterator; + child_sa_t *child_sa; + peer_cfg_t *peer_cfg; + + /* process delete response first */ + this->ike_delete->task.process(&this->ike_delete->task, message); + + peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); + + /* reauthenticate only if we have children */ + iterator = this->ike_sa->create_child_sa_iterator(this->ike_sa); + if (iterator->get_count(iterator) == 0 +#ifdef ME + /* we allow peers to reauth mediation connections (without children) */ + && !peer_cfg->is_mediation(peer_cfg) +#endif /* ME */ + ) + { + DBG1(DBG_IKE, "unable to reauthenticate IKE_SA, no CHILD_SA to recreate"); + iterator->destroy(iterator); + return FAILED; + } + + new = charon->ike_sa_manager->checkout_new(charon->ike_sa_manager, TRUE); + + new->set_peer_cfg(new, peer_cfg); + host = this->ike_sa->get_other_host(this->ike_sa); + new->set_other_host(new, host->clone(host)); + host = this->ike_sa->get_my_host(this->ike_sa); + new->set_my_host(new, host->clone(host)); + /* if we already have a virtual IP, we reuse it */ + host = this->ike_sa->get_virtual_ip(this->ike_sa, TRUE); + if (host) + { + new->set_virtual_ip(new, TRUE, host); + } + +#ifdef ME + /* we initiate the new IKE_SA of the mediation connection without CHILD_SA */ + if (peer_cfg->is_mediation(peer_cfg)) + { + if (new->initiate(new, NULL, 0, NULL, NULL) == DESTROY_ME) + { + charon->ike_sa_manager->checkin_and_destroy( + charon->ike_sa_manager, new); + /* set threads active IKE_SA after checkin */ + charon->bus->set_sa(charon->bus, this->ike_sa); + DBG1(DBG_IKE, "reauthenticating IKE_SA failed"); + return FAILED; + } + } +#endif /* ME */ + + while (iterator->iterate(iterator, (void**)&child_sa)) + { + switch (child_sa->get_state(child_sa)) + { + case CHILD_ROUTED: + { + /* move routed child directly */ + iterator->remove(iterator); + new->add_child_sa(new, child_sa); + break; + } + default: + { + /* initiate/queue all child SAs */ + child_cfg_t *child_cfg = child_sa->get_config(child_sa); + child_cfg->get_ref(child_cfg); + if (new->initiate(new, child_cfg, 0, NULL, NULL) == DESTROY_ME) + { + iterator->destroy(iterator); + charon->ike_sa_manager->checkin_and_destroy( + charon->ike_sa_manager, new); + /* set threads active IKE_SA after checkin */ + charon->bus->set_sa(charon->bus, this->ike_sa); + DBG1(DBG_IKE, "reauthenticating IKE_SA failed"); + return FAILED; + } + break; + } + } + } + iterator->destroy(iterator); + charon->ike_sa_manager->checkin(charon->ike_sa_manager, new); + /* set threads active IKE_SA after checkin */ + charon->bus->set_sa(charon->bus, this->ike_sa); + + /* we always return failed to delete the obsolete IKE_SA */ + return FAILED; +} + +/** + * Implementation of task_t.get_type + */ +static task_type_t get_type(private_ike_reauth_t *this) +{ + return IKE_REAUTH; +} + +/** + * Implementation of task_t.migrate + */ +static void migrate(private_ike_reauth_t *this, ike_sa_t *ike_sa) +{ + this->ike_delete->task.migrate(&this->ike_delete->task, ike_sa); + this->ike_sa = ike_sa; +} + +/** + * Implementation of task_t.destroy + */ +static void destroy(private_ike_reauth_t *this) +{ + this->ike_delete->task.destroy(&this->ike_delete->task); + free(this); +} + +/* + * Described in header. + */ +ike_reauth_t *ike_reauth_create(ike_sa_t *ike_sa) +{ + private_ike_reauth_t *this = malloc_thing(private_ike_reauth_t); + + this->public.task.get_type = (task_type_t(*)(task_t*))get_type; + this->public.task.migrate = (void(*)(task_t*,ike_sa_t*))migrate; + this->public.task.destroy = (void(*)(task_t*))destroy; + this->public.task.build = (status_t(*)(task_t*,message_t*))build_i; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_i; + + this->ike_sa = ike_sa; + this->ike_delete = ike_delete_create(ike_sa, TRUE); + + return &this->public; +} + diff --git a/src/libcharon/sa/tasks/ike_reauth.h b/src/libcharon/sa/tasks/ike_reauth.h new file mode 100644 index 000000000..5e97b719c --- /dev/null +++ b/src/libcharon/sa/tasks/ike_reauth.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2007 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 ike_reauth ike_reauth + * @{ @ingroup tasks + */ + +#ifndef IKE_REAUTH_H_ +#define IKE_REAUTH_H_ + +typedef struct ike_reauth_t ike_reauth_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/tasks/task.h> + +/** + * Task of type ike_reauth, reestablishes an IKE_SA. + */ +struct ike_reauth_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * Create a new ike_reauth task. + * + * This task is initiator only. + * + * @param ike_sa IKE_SA this task works for + * @return ike_reauth task to handle by the task_manager + */ +ike_reauth_t *ike_reauth_create(ike_sa_t *ike_sa); + +#endif /** IKE_REAUTH_H_ @}*/ diff --git a/src/libcharon/sa/tasks/ike_rekey.c b/src/libcharon/sa/tasks/ike_rekey.c new file mode 100644 index 000000000..a2275e796 --- /dev/null +++ b/src/libcharon/sa/tasks/ike_rekey.c @@ -0,0 +1,409 @@ +/* + * Copyright (C) 2005-2008 Martin Willi + * Copyright (C) 2005 Jan Hutter + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "ike_rekey.h" + +#include <daemon.h> +#include <encoding/payloads/notify_payload.h> +#include <sa/tasks/ike_init.h> +#include <sa/tasks/ike_delete.h> +#include <processing/jobs/delete_ike_sa_job.h> +#include <processing/jobs/rekey_ike_sa_job.h> + + +typedef struct private_ike_rekey_t private_ike_rekey_t; + +/** + * Private members of a ike_rekey_t task. + */ +struct private_ike_rekey_t { + + /** + * Public methods and task_t interface. + */ + ike_rekey_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; + + /** + * New IKE_SA which replaces the current one + */ + ike_sa_t *new_sa; + + /** + * Are we the initiator? + */ + bool initiator; + + /** + * the IKE_INIT task which is reused to simplify rekeying + */ + ike_init_t *ike_init; + + /** + * IKE_DELETE task to delete the old IKE_SA after rekeying was successful + */ + ike_delete_t *ike_delete; + + /** + * colliding task detected by the task manager + */ + task_t *collision; +}; + +/** + * Implementation of task_t.build for initiator, after rekeying + */ +static status_t build_i_delete(private_ike_rekey_t *this, message_t *message) +{ + /* update exchange type to INFORMATIONAL for the delete */ + message->set_exchange_type(message, INFORMATIONAL); + + return this->ike_delete->task.build(&this->ike_delete->task, message); +} + +/** + * Implementation of task_t.process for initiator, after rekeying + */ +static status_t process_i_delete(private_ike_rekey_t *this, message_t *message) +{ + return this->ike_delete->task.process(&this->ike_delete->task, message); +} + +/** + * Implementation of task_t.build for initiator + */ +static status_t build_i(private_ike_rekey_t *this, message_t *message) +{ + peer_cfg_t *peer_cfg; + host_t *other_host; + + /* create new SA only on first try */ + if (this->new_sa == NULL) + { + this->new_sa = charon->ike_sa_manager->checkout_new(charon->ike_sa_manager, + TRUE); + + peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); + other_host = this->ike_sa->get_other_host(this->ike_sa); + this->new_sa->set_peer_cfg(this->new_sa, peer_cfg); + this->new_sa->set_other_host(this->new_sa, other_host->clone(other_host)); + this->ike_init = ike_init_create(this->new_sa, TRUE, this->ike_sa); + this->ike_sa->set_state(this->ike_sa, IKE_REKEYING); + } + this->ike_init->task.build(&this->ike_init->task, message); + + return NEED_MORE; +} + +/** + * Implementation of task_t.process for responder + */ +static status_t process_r(private_ike_rekey_t *this, message_t *message) +{ + peer_cfg_t *peer_cfg; + iterator_t *iterator; + child_sa_t *child_sa; + + if (this->ike_sa->get_state(this->ike_sa) == IKE_DELETING) + { + DBG1(DBG_IKE, "peer initiated rekeying, but we are deleting"); + return NEED_MORE; + } + + iterator = this->ike_sa->create_child_sa_iterator(this->ike_sa); + while (iterator->iterate(iterator, (void**)&child_sa)) + { + switch (child_sa->get_state(child_sa)) + { + case CHILD_CREATED: + case CHILD_REKEYING: + case CHILD_DELETING: + /* we do not allow rekeying while we have children in-progress */ + DBG1(DBG_IKE, "peer initiated rekeying, but a child is half-open"); + iterator->destroy(iterator); + return NEED_MORE; + default: + break; + } + } + iterator->destroy(iterator); + + this->new_sa = charon->ike_sa_manager->checkout_new(charon->ike_sa_manager, + FALSE); + + peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); + this->new_sa->set_peer_cfg(this->new_sa, peer_cfg); + this->ike_init = ike_init_create(this->new_sa, FALSE, this->ike_sa); + this->ike_init->task.process(&this->ike_init->task, message); + + return NEED_MORE; +} + +/** + * Implementation of task_t.build for responder + */ +static status_t build_r(private_ike_rekey_t *this, message_t *message) +{ + if (this->new_sa == NULL) + { + /* IKE_SA/a CHILD_SA is in an inacceptable state, deny rekeying */ + message->add_notify(message, TRUE, NO_PROPOSAL_CHOSEN, chunk_empty); + return SUCCESS; + } + + if (this->ike_init->task.build(&this->ike_init->task, message) == FAILED) + { + return SUCCESS; + } + + this->ike_sa->set_state(this->ike_sa, IKE_REKEYING); + this->new_sa->set_state(this->new_sa, IKE_ESTABLISHED); + DBG0(DBG_IKE, "IKE_SA %s[%d] established between %H[%Y]...%H[%Y]", + this->new_sa->get_name(this->new_sa), + this->new_sa->get_unique_id(this->new_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)); + + return SUCCESS; +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t process_i(private_ike_rekey_t *this, message_t *message) +{ + if (message->get_notify(message, NO_ADDITIONAL_SAS)) + { + DBG1(DBG_IKE, "peer seems to not support IKE rekeying, " + "starting reauthentication"); + this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED); + charon->processor->queue_job(charon->processor, + (job_t*)rekey_ike_sa_job_create( + this->ike_sa->get_id(this->ike_sa), TRUE)); + return SUCCESS; + } + + switch (this->ike_init->task.process(&this->ike_init->task, message)) + { + case FAILED: + /* rekeying failed, fallback to old SA */ + if (!(this->collision && ( + this->collision->get_type(this->collision) == IKE_DELETE || + this->collision->get_type(this->collision) == IKE_REAUTH))) + { + job_t *job; + u_int32_t retry = RETRY_INTERVAL - (random() % RETRY_JITTER); + job = (job_t*)rekey_ike_sa_job_create( + this->ike_sa->get_id(this->ike_sa), FALSE); + DBG1(DBG_IKE, "IKE_SA rekeying failed, " + "trying again in %d seconds", retry); + this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED); + charon->scheduler->schedule_job(charon->scheduler, job, retry); + } + return SUCCESS; + case NEED_MORE: + /* bad dh group, try again */ + this->ike_init->task.migrate(&this->ike_init->task, this->new_sa); + return NEED_MORE; + default: + break; + } + + this->new_sa->set_state(this->new_sa, IKE_ESTABLISHED); + DBG0(DBG_IKE, "IKE_SA %s[%d] established between %H[%Y]...%H[%Y]", + this->new_sa->get_name(this->new_sa), + this->new_sa->get_unique_id(this->new_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)); + + /* check for collisions */ + if (this->collision && + this->collision->get_type(this->collision) == IKE_REKEY) + { + chunk_t this_nonce, other_nonce; + host_t *host; + private_ike_rekey_t *other = (private_ike_rekey_t*)this->collision; + + this_nonce = this->ike_init->get_lower_nonce(this->ike_init); + other_nonce = other->ike_init->get_lower_nonce(other->ike_init); + + /* if we have the lower nonce, delete rekeyed SA. If not, delete + * the redundant. */ + if (memcmp(this_nonce.ptr, other_nonce.ptr, + min(this_nonce.len, other_nonce.len)) < 0) + { + /* peer should delete this SA. Add a timeout just in case. */ + job_t *job = (job_t*)delete_ike_sa_job_create( + other->new_sa->get_id(other->new_sa), TRUE); + charon->scheduler->schedule_job(charon->scheduler, job, 10); + DBG1(DBG_IKE, "IKE_SA rekey collision won, deleting rekeyed IKE_SA"); + charon->ike_sa_manager->checkin(charon->ike_sa_manager, other->new_sa); + other->new_sa = NULL; + } + else + { + DBG1(DBG_IKE, "IKE_SA rekey collision lost, deleting redundant IKE_SA"); + /* apply host for a proper delete */ + host = this->ike_sa->get_my_host(this->ike_sa); + this->new_sa->set_my_host(this->new_sa, host->clone(host)); + host = this->ike_sa->get_other_host(this->ike_sa); + this->new_sa->set_other_host(this->new_sa, host->clone(host)); + this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED); + if (this->new_sa->delete(this->new_sa) == DESTROY_ME) + { + charon->ike_sa_manager->checkin_and_destroy( + charon->ike_sa_manager, this->new_sa); + } + else + { + charon->ike_sa_manager->checkin( + charon->ike_sa_manager, this->new_sa); + } + /* set threads active IKE_SA after checkin */ + charon->bus->set_sa(charon->bus, this->ike_sa); + /* inherit to other->new_sa in destroy() */ + this->new_sa = other->new_sa; + other->new_sa = NULL; + return SUCCESS; + } + /* set threads active IKE_SA after checkin */ + charon->bus->set_sa(charon->bus, this->ike_sa); + } + + /* rekeying successful, delete the IKE_SA using a subtask */ + this->ike_delete = ike_delete_create(this->ike_sa, TRUE); + this->public.task.build = (status_t(*)(task_t*,message_t*))build_i_delete; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_i_delete; + + return NEED_MORE; +} + +/** + * Implementation of task_t.get_type + */ +static task_type_t get_type(private_ike_rekey_t *this) +{ + return IKE_REKEY; +} + +static void collide(private_ike_rekey_t* this, task_t *other) +{ + DESTROY_IF(this->collision); + this->collision = other; +} + +/** + * Implementation of task_t.migrate + */ +static void migrate(private_ike_rekey_t *this, ike_sa_t *ike_sa) +{ + if (this->ike_init) + { + this->ike_init->task.destroy(&this->ike_init->task); + } + if (this->ike_delete) + { + this->ike_delete->task.destroy(&this->ike_delete->task); + } + if (this->new_sa) + { + charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, + this->new_sa); + /* set threads active IKE_SA after checkin */ + charon->bus->set_sa(charon->bus, this->ike_sa); + } + DESTROY_IF(this->collision); + + this->collision = NULL; + this->ike_sa = ike_sa; + this->new_sa = NULL; + this->ike_init = NULL; + this->ike_delete = NULL; +} + +/** + * Implementation of task_t.destroy + */ +static void destroy(private_ike_rekey_t *this) +{ + if (this->new_sa) + { + if (this->new_sa->get_state(this->new_sa) == IKE_ESTABLISHED && + this->new_sa->inherit(this->new_sa, this->ike_sa) != DESTROY_ME) + { + /* invoke hook if rekeying was successful */ + charon->bus->ike_rekey(charon->bus, this->ike_sa, this->new_sa); + charon->ike_sa_manager->checkin(charon->ike_sa_manager, this->new_sa); + } + else + { + charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, + this->new_sa); + } + /* set threads active IKE_SA after checkin */ + charon->bus->set_sa(charon->bus, this->ike_sa); + } + if (this->ike_init) + { + this->ike_init->task.destroy(&this->ike_init->task); + } + if (this->ike_delete) + { + this->ike_delete->task.destroy(&this->ike_delete->task); + } + DESTROY_IF(this->collision); + free(this); +} + +/* + * Described in header. + */ +ike_rekey_t *ike_rekey_create(ike_sa_t *ike_sa, bool initiator) +{ + private_ike_rekey_t *this = malloc_thing(private_ike_rekey_t); + + this->public.collide = (void(*)(ike_rekey_t*,task_t*))collide; + this->public.task.get_type = (task_type_t(*)(task_t*))get_type; + this->public.task.migrate = (void(*)(task_t*,ike_sa_t*))migrate; + this->public.task.destroy = (void(*)(task_t*))destroy; + if (initiator) + { + this->public.task.build = (status_t(*)(task_t*,message_t*))build_i; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_i; + } + else + { + this->public.task.build = (status_t(*)(task_t*,message_t*))build_r; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_r; + } + + this->ike_sa = ike_sa; + this->new_sa = NULL; + this->ike_init = NULL; + this->ike_delete = NULL; + this->initiator = initiator; + this->collision = NULL; + + return &this->public; +} diff --git a/src/libcharon/sa/tasks/ike_rekey.h b/src/libcharon/sa/tasks/ike_rekey.h new file mode 100644 index 000000000..1c9550768 --- /dev/null +++ b/src/libcharon/sa/tasks/ike_rekey.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2007 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 ike_rekey ike_rekey + * @{ @ingroup tasks + */ + +#ifndef IKE_REKEY_H_ +#define IKE_REKEY_H_ + +typedef struct ike_rekey_t ike_rekey_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/tasks/task.h> + +/** + * Task of type IKE_REKEY, rekey an established IKE_SA. + */ +struct ike_rekey_t { + + /** + * Implements the task_t interface + */ + task_t task; + + /** + * Register a rekeying task which collides with this one. + * + * If two peers initiate rekeying at the same time, the collision must + * be handled gracefully. The task manager is aware of what exchanges + * are going on and notifies the outgoing task by passing the incoming. + * + * @param other incoming task + */ + void (*collide)(ike_rekey_t* this, task_t *other); +}; + +/** + * Create a new IKE_REKEY task. + * + * @param ike_sa IKE_SA this task works for + * @param initiator TRUE for initiator, FALSE for responder + * @return IKE_REKEY task to handle by the task_manager + */ +ike_rekey_t *ike_rekey_create(ike_sa_t *ike_sa, bool initiator); + +#endif /** IKE_REKEY_H_ @}*/ diff --git a/src/libcharon/sa/tasks/ike_vendor.c b/src/libcharon/sa/tasks/ike_vendor.c new file mode 100644 index 000000000..7c435b6d1 --- /dev/null +++ b/src/libcharon/sa/tasks/ike_vendor.c @@ -0,0 +1,139 @@ +/* + * 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 "ike_vendor.h" + +#include <daemon.h> +#include <encoding/payloads/vendor_id_payload.h> + +typedef struct private_ike_vendor_t private_ike_vendor_t; + +/** + * Private data of an ike_vendor_t object. + */ +struct private_ike_vendor_t { + + /** + * Public ike_vendor_t interface. + */ + ike_vendor_t public; + + /** + * Associated IKE_SA + */ + ike_sa_t *ike_sa; + + /** + * Are we the inititator of this task + */ + bool initiator; +}; + +/** + * strongSwan specific vendor ID without version, MD5("strongSwan") + */ +static chunk_t strongswan_vid = chunk_from_chars( + 0x88,0x2f,0xe5,0x6d,0x6f,0xd2,0x0d,0xbc, + 0x22,0x51,0x61,0x3b,0x2e,0xbe,0x5b,0xeb +); + +METHOD(task_t, build, status_t, + private_ike_vendor_t *this, message_t *message) +{ + if (lib->settings->get_bool(lib->settings, + "charon.send_vendor_id", FALSE)) + { + vendor_id_payload_t *vid; + + vid = vendor_id_payload_create_data(chunk_clone(strongswan_vid)); + message->add_payload(message, &vid->payload_interface); + } + + return this->initiator ? NEED_MORE : SUCCESS; +} + +METHOD(task_t, process, status_t, + private_ike_vendor_t *this, message_t *message) +{ + enumerator_t *enumerator; + payload_t *payload; + + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + if (payload->get_type(payload) == VENDOR_ID) + { + vendor_id_payload_t *vid; + chunk_t data; + + vid = (vendor_id_payload_t*)payload; + data = vid->get_data(vid); + + if (chunk_equals(data, strongswan_vid)) + { + DBG1(DBG_IKE, "received strongSwan vendor id"); + this->ike_sa->enable_extension(this->ike_sa, EXT_STRONGSWAN); + } + else + { + DBG1(DBG_ENC, "received unknown vendor id: %#B", &data); + } + } + } + enumerator->destroy(enumerator); + + return this->initiator ? SUCCESS : NEED_MORE; +} + +METHOD(task_t, migrate, void, + private_ike_vendor_t *this, ike_sa_t *ike_sa) +{ + this->ike_sa = ike_sa; +} + +METHOD(task_t, get_type, task_type_t, + private_ike_vendor_t *this) +{ + return IKE_VENDOR; +} + +METHOD(task_t, destroy, void, + private_ike_vendor_t *this) +{ + free(this); +} + +/** + * See header + */ +ike_vendor_t *ike_vendor_create(ike_sa_t *ike_sa, bool initiator) +{ + private_ike_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/tasks/ike_vendor.h b/src/libcharon/sa/tasks/ike_vendor.h new file mode 100644 index 000000000..dcdd37424 --- /dev/null +++ b/src/libcharon/sa/tasks/ike_vendor.h @@ -0,0 +1,49 @@ +/* + * 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. + */ + +/** + * @defgroup ike_vendor ike_vendor + * @{ @ingroup tasks + */ + +#ifndef IKE_VENDOR_H_ +#define IKE_VENDOR_H_ + +typedef struct ike_vendor_t ike_vendor_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/tasks/task.h> + +/** + * Vendor ID processing task. + */ +struct ike_vendor_t { + + /** + * Implements task interface. + */ + task_t task; +}; + +/** + * Create a ike_vendor instance. + * + * @param ike_sa IKE_SA this task works for + * @param initiator TRUE if thask is the original initator + */ +ike_vendor_t *ike_vendor_create(ike_sa_t *ike_sa, bool initiator); + +#endif /** IKE_VENDOR_H_ @}*/ diff --git a/src/libcharon/sa/tasks/task.c b/src/libcharon/sa/tasks/task.c new file mode 100644 index 000000000..0d7383141 --- /dev/null +++ b/src/libcharon/sa/tasks/task.c @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2007 Tobias Brunner + * Copyright (C) 2007 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.h" + +#ifdef ME +ENUM(task_type_names, IKE_INIT, CHILD_REKEY, + "IKE_INIT", + "IKE_NATD", + "IKE_MOBIKE", + "IKE_AUTHENTICATE", + "IKE_AUTH_LIFETIME", + "IKE_CERT_PRE", + "IKE_CERT_POST", + "IKE_CONFIG", + "IKE_REKEY", + "IKE_REAUTH", + "IKE_DELETE", + "IKE_DPD", + "IKE_VENDOR", + "IKE_ME", + "CHILD_CREATE", + "CHILD_DELETE", + "CHILD_REKEY", +); +#else +ENUM(task_type_names, IKE_INIT, CHILD_REKEY, + "IKE_INIT", + "IKE_NATD", + "IKE_MOBIKE", + "IKE_AUTHENTICATE", + "IKE_AUTH_LIFETIME", + "IKE_CERT_PRE", + "IKE_CERT_POST", + "IKE_CONFIG", + "IKE_REKEY", + "IKE_REAUTH", + "IKE_DELETE", + "IKE_DPD", + "IKE_VENDOR", + "CHILD_CREATE", + "CHILD_DELETE", + "CHILD_REKEY", +); +#endif /* ME */ diff --git a/src/libcharon/sa/tasks/task.h b/src/libcharon/sa/tasks/task.h new file mode 100644 index 000000000..4468f2ebe --- /dev/null +++ b/src/libcharon/sa/tasks/task.h @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2007 Tobias Brunner + * 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 task task + * @{ @ingroup tasks + */ + +#ifndef TASK_H_ +#define TASK_H_ + +typedef enum task_type_t task_type_t; +typedef struct task_t task_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <encoding/message.h> + +/** + * Different kinds of tasks. + */ +enum task_type_t { + /** establish an unauthenticated IKE_SA */ + IKE_INIT, + /** detect NAT situation */ + IKE_NATD, + /** handle MOBIKE stuff */ + IKE_MOBIKE, + /** authenticate the initiated IKE_SA */ + IKE_AUTHENTICATE, + /** AUTH_LIFETIME negotiation, RFC4478 */ + IKE_AUTH_LIFETIME, + /** certificate processing before authentication (certreqs, cert parsing) */ + IKE_CERT_PRE, + /** certificate processing after authentication (certs payload generation) */ + IKE_CERT_POST, + /** Configuration payloads, virtual IP and such */ + IKE_CONFIG, + /** rekey an IKE_SA */ + IKE_REKEY, + /** reestablish a complete IKE_SA */ + IKE_REAUTH, + /** delete an IKE_SA */ + IKE_DELETE, + /** liveness check */ + IKE_DPD, + /** Vendor ID processing */ + IKE_VENDOR, +#ifdef ME + /** handle ME stuff */ + IKE_ME, +#endif /* ME */ + /** establish a CHILD_SA within an IKE_SA */ + CHILD_CREATE, + /** delete an established CHILD_SA */ + CHILD_DELETE, + /** rekey an CHILD_SA */ + CHILD_REKEY, +}; + +/** + * enum names for task_type_t. + */ +extern enum_name_t *task_type_names; + +/** + * Interface for a task, an operation handled within exchanges. + * + * A task is an elemantary operation. It may be handled by a single or by + * multiple exchanges. An exchange may even complete multiple tasks. + * A task has a build() and an process() operation. The build() operation + * creates payloads and adds it to the message. The process() operation + * inspects a message and handles its payloads. An initiator of an exchange + * first calls build() to build the request, and processes the response message + * with the process() method. + * A responder does the opposite; it calls process() first to handle an incoming + * request and secondly calls build() to build an appropriate response. + * Both methods return either SUCCESS, NEED_MORE or FAILED. A SUCCESS indicates + * that the task completed, even when the task completed unsuccesfully. The + * manager then removes the task from the list. A NEED_MORE is returned when + * the task needs further build()/process() calls to complete, the manager + * leaves the taks in the queue. A returned FAILED indicates a critical failure. + * The manager closes the IKE_SA whenever a task returns FAILED. + */ +struct task_t { + + /** + * Build a request or response message for this task. + * + * @param message message to add payloads to + * @return + * - FAILED if a critical error occured + * - DESTROY_ME if IKE_SA has been properly deleted + * - NEED_MORE if another call to build/process needed + * - SUCCESS if task completed + */ + status_t (*build) (task_t *this, message_t *message); + + /** + * Process a request or response message for this task. + * + * @param message message to read payloads from + * @return + * - FAILED if a critical error occured + * - DESTROY_ME if IKE_SA has been properly deleted + * - NEED_MORE if another call to build/process needed + * - SUCCESS if task completed + */ + status_t (*process) (task_t *this, message_t *message); + + /** + * Get the type of the task implementation. + */ + task_type_t (*get_type) (task_t *this); + + /** + * Migrate a task to a new IKE_SA. + * + * After migrating a task, it goes back to a state where it can be + * used again to initate an exchange. This is useful when a task + * has to get migrated to a new IKE_SA. + * A special usage is when a INVALID_KE_PAYLOAD is received. A call + * to reset resets the task, but uses another DH group for the next + * try. + * The ike_sa is the new IKE_SA this task belongs to and operates on. + * + * @param ike_sa new IKE_SA this task works for + */ + void (*migrate) (task_t *this, ike_sa_t *ike_sa); + + /** + * Destroys a task_t object. + */ + void (*destroy) (task_t *this); +}; + +#endif /** TASK_H_ @}*/ diff --git a/src/libcharon/sa/trap_manager.c b/src/libcharon/sa/trap_manager.c new file mode 100644 index 000000000..878170c83 --- /dev/null +++ b/src/libcharon/sa/trap_manager.c @@ -0,0 +1,403 @@ +/* + * 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 "trap_manager.h" + +#include <daemon.h> +#include <threading/rwlock.h> +#include <utils/linked_list.h> + + +typedef struct private_trap_manager_t private_trap_manager_t; +typedef struct trap_listener_t trap_listener_t; + +/** + * listener to track acquires + */ +struct trap_listener_t { + + /** + * Implements listener interface + */ + listener_t listener; + + /** + * points to trap_manager + */ + private_trap_manager_t *traps; +}; + +/** + * Private data of an trap_manager_t object. + */ +struct private_trap_manager_t { + + /** + * Public trap_manager_t interface. + */ + trap_manager_t public; + + /** + * Installed traps, as entry_t + */ + linked_list_t *traps; + + /** + * read write lock for traps list + */ + rwlock_t *lock; + + /** + * listener to track acquiring IKE_SAs + */ + trap_listener_t listener; +}; + +/** + * A installed trap entry + */ +typedef struct { + /** ref to peer_cfg to initiate */ + peer_cfg_t *peer_cfg; + /** ref to instanciated CHILD_SA */ + child_sa_t *child_sa; + /** pending IKE_SA connecting upon acquire */ + ike_sa_t *pending; +} entry_t; + +/** + * actually uninstall and destroy an installed entry + */ +static void destroy_entry(entry_t *entry) +{ + entry->child_sa->destroy(entry->child_sa); + entry->peer_cfg->destroy(entry->peer_cfg); + free(entry); +} + +/** + * Implementation of trap_manager_t.install + */ +static u_int32_t install(private_trap_manager_t *this, peer_cfg_t *peer, + child_cfg_t *child) +{ + entry_t *entry; + ike_cfg_t *ike_cfg; + child_sa_t *child_sa; + host_t *me, *other; + linked_list_t *my_ts, *other_ts; + enumerator_t *enumerator; + bool found = FALSE; + status_t status; + u_int32_t reqid; + + /* check if not already done */ + this->lock->read_lock(this->lock); + enumerator = this->traps->create_enumerator(this->traps); + while (enumerator->enumerate(enumerator, &entry)) + { + if (streq(entry->child_sa->get_name(entry->child_sa), + child->get_name(child))) + { + found = TRUE; + break; + } + } + enumerator->destroy(enumerator); + this->lock->unlock(this->lock); + if (found) + { + DBG1(DBG_CFG, "CHILD_SA named '%s' already routed", + child->get_name(child)); + return 0; + } + + /* try to resolve addresses */ + ike_cfg = peer->get_ike_cfg(peer); + other = host_create_from_dns(ike_cfg->get_other_addr(ike_cfg), + 0, ike_cfg->get_other_port(ike_cfg)); + if (!other) + { + DBG1(DBG_CFG, "installing trap failed, remote address unknown"); + return 0; + } + me = host_create_from_dns(ike_cfg->get_my_addr(ike_cfg), + other->get_family(other), ike_cfg->get_my_port(ike_cfg)); + if (!me || me->is_anyaddr(me)) + { + DESTROY_IF(me); + me = charon->kernel_interface->get_source_addr( + charon->kernel_interface, other, NULL); + if (!me) + { + DBG1(DBG_CFG, "installing trap failed, local address unknown"); + other->destroy(other); + return 0; + } + me->set_port(me, ike_cfg->get_my_port(ike_cfg)); + } + + /* create and route CHILD_SA */ + child_sa = child_sa_create(me, other, child, 0, FALSE); + my_ts = child->get_traffic_selectors(child, TRUE, NULL, me); + other_ts = child->get_traffic_selectors(child, FALSE, NULL, other); + me->destroy(me); + other->destroy(other); + + /* while we don't know the finally negotiated protocol (ESP|AH), we + * could iterate all proposals for a best guest (TODO). But as we + * support ESP only for now, we set here. */ + child_sa->set_protocol(child_sa, PROTO_ESP); + child_sa->set_mode(child_sa, child->get_mode(child)); + status = child_sa->add_policies(child_sa, my_ts, other_ts); + my_ts->destroy_offset(my_ts, offsetof(traffic_selector_t, destroy)); + other_ts->destroy_offset(other_ts, offsetof(traffic_selector_t, destroy)); + if (status != SUCCESS) + { + child_sa->destroy(child_sa); + DBG1(DBG_CFG, "installing trap failed"); + return 0; + } + + reqid = child_sa->get_reqid(child_sa); + entry = malloc_thing(entry_t); + entry->child_sa = child_sa; + entry->peer_cfg = peer->get_ref(peer); + entry->pending = NULL; + + this->lock->write_lock(this->lock); + this->traps->insert_last(this->traps, entry); + this->lock->unlock(this->lock); + + return reqid; +} + +/** + * Implementation of trap_manager_t.uninstall + */ +static bool uninstall(private_trap_manager_t *this, u_int32_t reqid) +{ + enumerator_t *enumerator; + entry_t *entry, *found = NULL; + + this->lock->write_lock(this->lock); + enumerator = this->traps->create_enumerator(this->traps); + while (enumerator->enumerate(enumerator, &entry)) + { + if (entry->child_sa->get_reqid(entry->child_sa) == reqid) + { + this->traps->remove_at(this->traps, enumerator); + found = entry; + break; + } + } + enumerator->destroy(enumerator); + this->lock->unlock(this->lock); + + if (!found) + { + DBG1(DBG_CFG, "trap %d not found to uninstall", reqid); + return FALSE; + } + + destroy_entry(found); + return TRUE; +} + +/** + * convert enumerated entries to peer_cfg, child_sa + */ +static bool trap_filter(rwlock_t *lock, entry_t **entry, peer_cfg_t **peer_cfg, + void *none, child_sa_t **child_sa) +{ + if (peer_cfg) + { + *peer_cfg = (*entry)->peer_cfg; + } + if (child_sa) + { + *child_sa = (*entry)->child_sa; + } + return TRUE; +} + +/** + * Implementation of trap_manager_t.create_enumerator + */ +static enumerator_t* create_enumerator(private_trap_manager_t *this) +{ + this->lock->read_lock(this->lock); + return enumerator_create_filter(this->traps->create_enumerator(this->traps), + (void*)trap_filter, this->lock, + (void*)this->lock->unlock); +} + +/** + * Implementation of trap_manager_t.acquire + */ +static void acquire(private_trap_manager_t *this, u_int32_t reqid, + traffic_selector_t *src, traffic_selector_t *dst) +{ + enumerator_t *enumerator; + entry_t *entry, *found = NULL; + peer_cfg_t *peer; + child_cfg_t *child; + ike_sa_t *ike_sa; + + this->lock->read_lock(this->lock); + enumerator = this->traps->create_enumerator(this->traps); + while (enumerator->enumerate(enumerator, &entry)) + { + if (entry->child_sa->get_reqid(entry->child_sa) == reqid) + { + found = entry; + break; + } + } + enumerator->destroy(enumerator); + + if (!found) + { + DBG1(DBG_CFG, "trap not found, unable to acquire reqid %d",reqid); + } + else if (found->pending) + { + DBG1(DBG_CFG, "ignoring acquire, connection attempt pending"); + } + else + { + child = found->child_sa->get_config(found->child_sa); + peer = found->peer_cfg; + ike_sa = charon->ike_sa_manager->checkout_by_config( + charon->ike_sa_manager, peer); + if (ike_sa->get_peer_cfg(ike_sa) == NULL) + { + ike_sa->set_peer_cfg(ike_sa, peer); + } + child->get_ref(child); + reqid = found->child_sa->get_reqid(found->child_sa); + if (ike_sa->initiate(ike_sa, child, reqid, src, dst) != DESTROY_ME) + { + found->pending = ike_sa; + charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa); + } + else + { + charon->ike_sa_manager->checkin_and_destroy( + charon->ike_sa_manager, ike_sa); + } + } + this->lock->unlock(this->lock); +} + +/** + * Complete the acquire, if successful or failed + */ +static void complete(private_trap_manager_t *this, ike_sa_t *ike_sa, + child_sa_t *child_sa) +{ + enumerator_t *enumerator; + entry_t *entry; + + this->lock->read_lock(this->lock); + enumerator = this->traps->create_enumerator(this->traps); + while (enumerator->enumerate(enumerator, &entry)) + { + if (entry->pending != ike_sa) + { + continue; + } + if (child_sa && child_sa->get_reqid(child_sa) != + entry->child_sa->get_reqid(entry->child_sa)) + { + continue; + } + entry->pending = NULL; + } + enumerator->destroy(enumerator); + this->lock->unlock(this->lock); +} + +/** + * Implementation of listener_t.ike_state_change + */ +static bool ike_state_change(trap_listener_t *listener, ike_sa_t *ike_sa, + ike_sa_state_t state) +{ + switch (state) + { + case IKE_DESTROYING: + complete(listener->traps, ike_sa, NULL); + return TRUE; + default: + return TRUE; + } +} + +/** + * Implementation of listener_t.child_state_change + */ +static bool child_state_change(trap_listener_t *listener, ike_sa_t *ike_sa, + child_sa_t *child_sa, child_sa_state_t state) +{ + switch (state) + { + case CHILD_INSTALLED: + case CHILD_DESTROYING: + complete(listener->traps, ike_sa, child_sa); + return TRUE; + default: + return TRUE; + } +} + +/** + * Implementation of trap_manager_t.destroy. + */ +static void destroy(private_trap_manager_t *this) +{ + charon->bus->remove_listener(charon->bus, &this->listener.listener); + this->traps->invoke_function(this->traps, (void*)destroy_entry); + this->traps->destroy(this->traps); + this->lock->destroy(this->lock); + free(this); +} + +/** + * See header + */ +trap_manager_t *trap_manager_create() +{ + private_trap_manager_t *this = malloc_thing(private_trap_manager_t); + + this->public.install = (u_int(*)(trap_manager_t*, peer_cfg_t *peer, child_cfg_t *child))install; + this->public.uninstall = (bool(*)(trap_manager_t*, u_int32_t id))uninstall; + this->public.create_enumerator = (enumerator_t*(*)(trap_manager_t*))create_enumerator; + this->public.acquire = (void(*)(trap_manager_t*, u_int32_t reqid, traffic_selector_t *src, traffic_selector_t *dst))acquire; + this->public.destroy = (void(*)(trap_manager_t*))destroy; + + this->traps = linked_list_create(); + this->lock = rwlock_create(RWLOCK_TYPE_DEFAULT); + + /* register listener for IKE state changes */ + this->listener.traps = this; + memset(&this->listener.listener, 0, sizeof(listener_t)); + this->listener.listener.ike_state_change = (void*)ike_state_change; + this->listener.listener.child_state_change = (void*)child_state_change; + charon->bus->add_listener(charon->bus, &this->listener.listener); + + return &this->public; +} + diff --git a/src/libcharon/sa/trap_manager.h b/src/libcharon/sa/trap_manager.h new file mode 100644 index 000000000..37b42e2b0 --- /dev/null +++ b/src/libcharon/sa/trap_manager.h @@ -0,0 +1,81 @@ +/* + * 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. + */ + +/** + * @defgroup trap_manager trap_manager + * @{ @ingroup sa + */ + +#ifndef TRAP_MANAGER_H_ +#define TRAP_MANAGER_H_ + +#include <library.h> +#include <utils/enumerator.h> +#include <config/peer_cfg.h> + +typedef struct trap_manager_t trap_manager_t; + +/** + * Manage policies to create SAs from traffic. + */ +struct trap_manager_t { + + /** + * Install a policy as a trap. + * + * @param peer peer configuration to initiate on trap + * @param child child configuration to install as a trap + * @return reqid of installed CHILD_SA, 0 if failed + */ + u_int32_t (*install)(trap_manager_t *this, peer_cfg_t *peer, + child_cfg_t *child); + + /** + * Uninstall a trap policy. + * + * @param id reqid of CHILD_SA to uninstall, returned by install() + * @return TRUE if uninstalled successfully + */ + bool (*uninstall)(trap_manager_t *this, u_int32_t reqid); + + /** + * Create an enumerator over all installed traps. + * + * @return enumerator over (peer_cfg_t, child_sa_t) + */ + enumerator_t* (*create_enumerator)(trap_manager_t *this); + + /** + * Acquire an SA triggered by an installed trap. + * + * @param reqid requid of the triggering CHILD_SA + * @param src source of the triggering packet + * @param dst destination of the triggering packet + */ + void (*acquire)(trap_manager_t *this, u_int32_t reqid, + traffic_selector_t *src, traffic_selector_t *dst); + + /** + * Destroy a trap_manager_t. + */ + void (*destroy)(trap_manager_t *this); +}; + +/** + * Create a trap_manager instance. + */ +trap_manager_t *trap_manager_create(); + +#endif /** TRAP_MANAGER_H_ @}*/ |