diff options
Diffstat (limited to 'src/charon/sa')
48 files changed, 14820 insertions, 0 deletions
diff --git a/src/charon/sa/authenticators/authenticator.c b/src/charon/sa/authenticators/authenticator.c new file mode 100644 index 000000000..707aae9ad --- /dev/null +++ b/src/charon/sa/authenticators/authenticator.c @@ -0,0 +1,56 @@ +/** + * @file authenticator.c + * + * @brief Generic constructor for authenticators. + * + */ + +/* + * 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 <string.h> + +#include "authenticator.h" + +#include <sa/authenticators/rsa_authenticator.h> +#include <sa/authenticators/psk_authenticator.h> +#include <sa/authenticators/eap_authenticator.h> + + +ENUM_BEGIN(auth_method_names, AUTH_RSA, AUTH_DSS, + "RSA signature", + "pre-shared key", + "DSS signature"); +ENUM_NEXT(auth_method_names, AUTH_EAP, AUTH_EAP, AUTH_DSS, + "EAP"); +ENUM_END(auth_method_names, AUTH_EAP); + +/* + * Described in header. + */ +authenticator_t *authenticator_create(ike_sa_t *ike_sa, auth_method_t auth_method) +{ + switch (auth_method) + { + case AUTH_RSA: + return (authenticator_t*)rsa_authenticator_create(ike_sa); + case AUTH_PSK: + return (authenticator_t*)psk_authenticator_create(ike_sa); + case AUTH_EAP: + return (authenticator_t*)eap_authenticator_create(ike_sa); + default: + return NULL; + } +} diff --git a/src/charon/sa/authenticators/authenticator.h b/src/charon/sa/authenticators/authenticator.h new file mode 100644 index 000000000..c7b0fc81a --- /dev/null +++ b/src/charon/sa/authenticators/authenticator.h @@ -0,0 +1,139 @@ +/** + * @file authenticator.h + * + * @brief Interface of authenticator_t. + * + */ + +/* + * 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. + */ + +#ifndef AUTHENTICATOR_H_ +#define AUTHENTICATOR_H_ + +typedef enum auth_method_t auth_method_t; +typedef struct authenticator_t authenticator_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <encoding/payloads/auth_payload.h> + +/** + * Method to use for authentication. + * + * @ingroup authenticators + */ +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, + + /** + * EAP authentication. This value is never negotiated and therefore + * a value from private use. + */ + AUTH_EAP = 201, +}; + +/** + * enum names for auth_method_t. + * + * @ingroup authenticators + */ +extern enum_name_t *auth_method_names; + +/** + * @brief Authenticator interface implemented by the various authenticators. + * + * Currently the following two AUTH methods are supported: + * - shared key message integrity code (AUTH_PSK) + * - RSA digital signature (AUTH_RSA) + * + * @b Constructors: + * - authenticator_create() + * + * @ingroup authenticators + */ +struct authenticator_t { + + /** + * @brief Verify a received authentication payload. + * + * @param this calling object + * @param ike_sa_init binary representation of received ike_sa_init + * @param my_nonce the sent nonce + * @param auth_payload authentication payload to verify + * + * @return + * - SUCCESS, + * - FAILED if verification failed + * - INVALID_ARG if auth_method does not match + * - NOT_FOUND if credentials not found + */ + status_t (*verify) (authenticator_t *this, chunk_t ike_sa_init, + chunk_t my_nonce, auth_payload_t *auth_payload); + + /** + * @brief Build an authentication payload to send to the other peer. + * + * @param this calling object + * @param ike_sa_init binary representation of sent ike_sa_init + * @param other_nonce the received nonce + * @param[out] auth_payload the resulting authentication payload + * + * @return + * - SUCCESS, + * - NOT_FOUND if the data for AUTH method could not be found + */ + status_t (*build) (authenticator_t *this, chunk_t ike_sa_init, + chunk_t other_nonce, auth_payload_t **auth_payload); + + /** + * @brief Destroys a authenticator_t object. + * + * @param this calling object + */ + void (*destroy) (authenticator_t *this); +}; + +/** + * @brief Creates an authenticator for the specified auth method. + * + * @param ike_sa associated ike_sa + * @param auth_method authentication method to use for build()/verify() + * + * @return authenticator_t object + * + * @ingroup authenticators + */ +authenticator_t *authenticator_create(ike_sa_t *ike_sa, auth_method_t auth_method); + +#endif /* AUTHENTICATOR_H_ */ diff --git a/src/charon/sa/authenticators/eap/eap_identity.c b/src/charon/sa/authenticators/eap/eap_identity.c new file mode 100644 index 000000000..12a8bf7cc --- /dev/null +++ b/src/charon/sa/authenticators/eap/eap_identity.c @@ -0,0 +1,135 @@ +/** + * @file eap_identity.c + * + * @brief Implementation of eap_identity_t. + * + */ + +/* + * 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 "eap_identity.h" + +#include <daemon.h> +#include <library.h> + +typedef struct private_eap_identity_t private_eap_identity_t; + +/** + * Private data of an eap_identity_t object. + */ +struct private_eap_identity_t { + + /** + * Public authenticator_t interface. + */ + eap_identity_t public; + + /** + * ID of the peer + */ + identification_t *peer; +}; + +/** + * Implementation of eap_method_t.process for the peer + */ +static status_t process(private_eap_identity_t *this, + eap_payload_t *in, eap_payload_t **out) +{ + chunk_t id, hdr; + + hdr = chunk_alloca(5); + id = this->peer->get_encoding(this->peer); + + *(hdr.ptr + 0) = EAP_RESPONSE; + *(hdr.ptr + 1) = in->get_identifier(in); + *(u_int16_t*)(hdr.ptr + 2) = htons(hdr.len + id.len); + *(hdr.ptr + 4) = EAP_IDENTITY; + + *out = eap_payload_create_data(chunk_cata("cc", hdr, id)); + return SUCCESS; + +} + +/** + * Implementation of eap_method_t.initiate for the peer + */ +static status_t initiate(private_eap_identity_t *this, eap_payload_t **out) +{ + /* peer never initiates */ + return FAILED; +} + +/** + * Implementation of eap_method_t.get_type. + */ +static eap_type_t get_type(private_eap_identity_t *this) +{ + return EAP_IDENTITY; +} + +/** + * Implementation of eap_method_t.get_msk. + */ +static status_t get_msk(private_eap_identity_t *this, chunk_t *msk) +{ + return FAILED; +} + +/** + * Implementation of eap_method_t.is_mutual. + */ +static bool is_mutual(private_eap_identity_t *this) +{ + return FALSE; +} + +/** + * Implementation of eap_method_t.destroy. + */ +static void destroy(private_eap_identity_t *this) +{ + free(this); +} + +/* + * Described in header. + */ +eap_identity_t *eap_create(eap_role_t role, + identification_t *server, identification_t *peer) +{ + private_eap_identity_t *this; + + if (role != EAP_PEER) + { + return NULL; + } + + this = malloc_thing(private_eap_identity_t); + + /* public functions */ + this->public.eap_method_interface.initiate = (status_t(*)(eap_method_t*,eap_payload_t**))initiate; + this->public.eap_method_interface.process = (status_t(*)(eap_method_t*,eap_payload_t*,eap_payload_t**))process; + this->public.eap_method_interface.get_type = (eap_type_t(*)(eap_method_t*))get_type; + this->public.eap_method_interface.is_mutual = (bool(*)(eap_method_t*))is_mutual; + this->public.eap_method_interface.get_msk = (status_t(*)(eap_method_t*,chunk_t*))get_msk; + this->public.eap_method_interface.destroy = (void(*)(eap_method_t*))destroy; + + /* private data */ + this->peer = peer; + + return &this->public; +} diff --git a/src/charon/sa/authenticators/eap/eap_identity.h b/src/charon/sa/authenticators/eap/eap_identity.h new file mode 100644 index 000000000..20f0f0b67 --- /dev/null +++ b/src/charon/sa/authenticators/eap/eap_identity.h @@ -0,0 +1,59 @@ +/** + * @file eap_identity.h + * + * @brief Interface of eap_identity_t. + * + */ + +/* + * 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. + */ + +#ifndef EAP_IDENTITY_H_ +#define EAP_IDENTITY_H_ + +typedef struct eap_identity_t eap_identity_t; + +#include <sa/authenticators/eap/eap_method.h> + +/** + * @brief Implementation of the eap_method_t interface using EAP Identity. + * + * @b Constructors: + * - eap_identity_create() + * - eap_client_create() using eap_method EAP_IDENTITY + * + * @ingroup eap + */ +struct eap_identity_t { + + /** + * Implemented eap_method_t interface. + */ + eap_method_t eap_method_interface; +}; + +/** + * @brief Creates the EAP method EAP Identity. + * + * @param server ID of the EAP server + * @param peer ID of the EAP client + * @return eap_identity_t object + * + * @ingroup eap + */ +eap_identity_t *eap_create(eap_role_t role, + identification_t *server, identification_t *peer); + +#endif /* EAP_IDENTITY_H_ */ diff --git a/src/charon/sa/authenticators/eap/eap_method.c b/src/charon/sa/authenticators/eap/eap_method.c new file mode 100644 index 000000000..a4d8abb58 --- /dev/null +++ b/src/charon/sa/authenticators/eap/eap_method.c @@ -0,0 +1,245 @@ +/** + * @file eap_method.c + * + * @brief Generic constructor for eap_methods. + * + */ + +/* + * 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 <string.h> +#include <sys/stat.h> +#include <dirent.h> +#include <error.h> +#include <dlfcn.h> + +#include "eap_method.h" + +#include <daemon.h> +#include <library.h> +#include <utils/linked_list.h> +#include <utils/identification.h> + + +ENUM_BEGIN(eap_type_names, EAP_IDENTITY, EAP_TOKEN_CARD, + "EAP_IDENTITY", + "EAP_NOTIFICATION", + "EAP_NAK", + "EAP_MD5", + "EAP_ONE_TIME_PASSWORD", + "EAP_TOKEN_CARD"); +ENUM_NEXT(eap_type_names, EAP_SIM, EAP_SIM, EAP_TOKEN_CARD, + "EAP_SIM"); +ENUM_NEXT(eap_type_names, EAP_AKA, EAP_AKA, EAP_SIM, + "EAP_AKA"); +ENUM_END(eap_type_names, EAP_AKA); + +ENUM(eap_code_names, EAP_REQUEST, EAP_FAILURE, + "EAP_REQUEST", + "EAP_RESPONSE", + "EAP_SUCCESS", + "EAP_FAILURE", +); + +ENUM(eap_role_names, EAP_SERVER, EAP_PEER, + "EAP_SERVER", + "EAP_PEER", +); + + +typedef struct module_entry_t module_entry_t; + +/** + * Representation of a loaded module: EAP type, library handle, constructor + */ +struct module_entry_t { + eap_type_t type; + void *handle; + eap_constructor_t constructor; +}; + +/** List of module_entry_t's */ +static linked_list_t *modules = NULL; + +/** + * unload modules at daemon shutdown + */ +void eap_method_unload() +{ + if (modules) + { + module_entry_t *entry; + + while (modules->remove_last(modules, (void**)&entry) == SUCCESS) + { + DBG2(DBG_CFG, "unloaded module for %s", eap_type_names, entry->type); + dlclose(entry->handle); + free(entry); + } + modules->destroy(modules); + modules = NULL; + } +} + +/** + * Load EAP modules at daemon startup + */ +void eap_method_load(char *directory) +{ + struct dirent* entry; + struct stat stb; + DIR* dir; + + eap_method_unload(); + modules = linked_list_create(); + + if (stat(directory, &stb) == -1 || !(stb.st_mode & S_IFDIR)) + { + DBG1(DBG_CFG, "error opening EAP modules directory %s", directory); + return; + } + if (stb.st_uid != 0) + { + DBG1(DBG_CFG, "EAP modules directory %s not owned by root, skipped", directory); + return; + } + if (stb.st_mode & S_IWOTH || stb.st_mode & S_IWGRP) + { + DBG1(DBG_CFG, "EAP modules directory %s writable by others, skipped", directory); + return; + } + + dir = opendir(directory); + if (dir == NULL) + { + DBG1(DBG_CFG, "error opening EAP modules directory %s", directory); + return; + } + + DBG1(DBG_CFG, "loading EAP modules from '%s'", directory); + + while ((entry = readdir(dir)) != NULL) + { + char file[256]; + module_entry_t module, *loaded_module; + eap_method_t *method; + identification_t *id; + char *ending; + + snprintf(file, sizeof(file), "%s/%s", directory, entry->d_name); + + if (stat(file, &stb) == -1 || !(stb.st_mode & S_IFREG)) + { + DBG2(DBG_CFG, " skipping %s, doesn't look like a file", + entry->d_name); + continue; + } + ending = entry->d_name + strlen(entry->d_name) - 3; + if (ending <= entry->d_name || !streq(ending, ".so")) + { + /* skip anything which does not look like a library */ + DBG2(DBG_CFG, " skipping %s, doesn't look like a library", + entry->d_name); + continue; + } + if (stb.st_uid != 0) + { + DBG1(DBG_CFG, " skipping %s, file is not owned by root", entry->d_name); + return; + } + if (stb.st_mode & S_IWOTH || stb.st_mode & S_IWGRP) + { + DBG1(DBG_CFG, " skipping %s, file is writeable by others", entry->d_name); + continue; + } + + /* try to load the library */ + module.handle = dlopen(file, RTLD_LAZY); + if (module.handle == NULL) + { + DBG1(DBG_CFG, " opening EAP module %s failed: %s", entry->d_name, + dlerror()); + continue; + } + module.constructor = dlsym(module.handle, "eap_create"); + if (module.constructor == NULL) + { + DBG1(DBG_CFG, " EAP module %s has no eap_create() function, skipped", + entry->d_name); + dlclose(module.handle); + continue; + } + + /* get the type implemented in the method, create an instance for it */ + id = identification_create_from_string("john@doe.xyz"); + method = module.constructor(EAP_SERVER, id, id); + if (method == NULL) + { + method = module.constructor(EAP_PEER, id, id); + } + id->destroy(id); + if (method == NULL) + { + DBG1(DBG_CFG, " unable to create instance of EAP method %s, skipped", + entry->d_name); + dlclose(module.handle); + continue; + } + module.type = method->get_type(method); + method->destroy(method); + + DBG1(DBG_CFG, " loaded EAP method %N successfully from %s", + eap_type_names, module.type, entry->d_name); + + loaded_module = malloc_thing(module_entry_t); + memcpy(loaded_module, &module, sizeof(module)); + modules->insert_last(modules, loaded_module); + } + closedir(dir); +} + +/* + * Described in header. + */ +eap_method_t *eap_method_create(eap_type_t type, eap_role_t role, + identification_t *server, + identification_t *peer) +{ + eap_method_t *method = NULL; + iterator_t *iterator; + module_entry_t *entry; + + iterator = modules->create_iterator(modules, TRUE); + while (iterator->iterate(iterator, (void**)&entry)) + { + if (entry->type == type) + { + method = entry->constructor(role, server, peer); + if (method) + { + break; + } + } + } + iterator->destroy(iterator); + + if (method == NULL) + { + DBG1(DBG_CFG, "no EAP module found for %N %N", + eap_type_names, type, eap_role_names, role); + } + return method; +} diff --git a/src/charon/sa/authenticators/eap/eap_method.h b/src/charon/sa/authenticators/eap/eap_method.h new file mode 100644 index 000000000..d43dc001f --- /dev/null +++ b/src/charon/sa/authenticators/eap/eap_method.h @@ -0,0 +1,242 @@ +/** + * @file eap_method.h + * + * @brief Interface eap_method_t. + * + */ + +/* + * 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. + */ + +#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) + * + * @ingroup eap + */ +enum eap_role_t { + EAP_SERVER, + EAP_PEER, +}; +/** + * enum names for eap_role_t. + * + * @ingroup eap + */ +extern enum_name_t *eap_role_names; + +/** + * EAP types, defines the EAP method implementation + * + * @ingroup eap + */ +enum eap_type_t { + EAP_IDENTITY = 1, + EAP_NOTIFICATION = 2, + EAP_NAK = 3, + EAP_MD5 = 4, + EAP_ONE_TIME_PASSWORD = 5, + EAP_TOKEN_CARD = 6, + EAP_SIM = 18, + EAP_AKA = 23, +}; + +/** + * enum names for eap_type_t. + * + * @ingroup eap + */ +extern enum_name_t *eap_type_names; + +/** + * EAP code, type of an EAP message + * + * @ingroup eap + */ +enum eap_code_t { + EAP_REQUEST = 1, + EAP_RESPONSE = 2, + EAP_SUCCESS = 3, + EAP_FAILURE = 4, +}; + +/** + * enum names for eap_code_t. + * + * @ingroup eap + */ +extern enum_name_t *eap_code_names; + + +/** + * @brief 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. + * + * @b Constructors: + * - eap_method_create() + * + * @ingroup eap + */ +struct eap_method_t { + + /** + * @brief 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 this calling object + * @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); + + /** + * @brief Process a received EAP message. + * + * A eap_payload is created in "out" if result is NEED_MORE. + * + * @param this calling object + * @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); + + /** + * @brief Get the EAP type implemented in this method. + * + * @param this calling object + * @return type of the EAP method + */ + eap_type_t (*get_type) (eap_method_t *this); + + /** + * @brief 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. + * + * @param this calling object + * @return TRUE if methods provides mutual authentication + */ + bool (*is_mutual) (eap_method_t *this); + + /** + * @brief Get the MSK established by this EAP method. + * + * Not all EAP methods establish a shared secret. + * + * @param this calling object + * @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); + + /** + * @brief Destroys a eap_method_t object. + * + * @param this calling object + */ + void (*destroy) (eap_method_t *this); +}; + +/** + * @brief Creates an EAP method for a specific type and role. + * + * @param eap_type EAP type to use + * @param role role of the eap_method, server or peer + * @param server ID of acting server + * @param peer ID of involved peer (client) + * @return eap_method_t object + * + * @ingroup eap + */ +eap_method_t *eap_method_create(eap_type_t eap_type, eap_role_t role, + identification_t *server, identification_t *peer); + +/** + * @brief (Re-)Load all EAP modules in the EAP modules directory. + * + * For security reasons, the directory and all it's modules must be owned + * by root and must not be writeable by someone else. + * + * @param dir directory of the EAP modules + * + * @ingroup eap + */ +void eap_method_load(char *directory); + +/** + * @brief Unload all loaded EAP modules + * + * @ingroup eap + */ +void eap_method_unload(); + +/** + * @brief Constructor definition for a pluggable EAP module. + * + * Each EAP module must define a constructor function which will return + * an initialized object with the methods defined in eap_method_t. The + * constructor must be named eap_create() and it's signature must be equal + * to that of eap_constructor_t. + * A module may implement only a single role. If it does not support the role + * requested, NULL should be returned. Multiple modules are allowed of the + * same EAP type to support seperate implementations of peer/server. + * + * @param role role the module will play, peer or server + * @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 + * + * @ingroup eap + */ +typedef eap_method_t *(*eap_constructor_t)(eap_role_t role, + identification_t *server, + identification_t *peer); + +#endif /* EAP_METHOD_H_ */ diff --git a/src/charon/sa/authenticators/eap/eap_sim.c b/src/charon/sa/authenticators/eap/eap_sim.c new file mode 100644 index 000000000..3dc59fb6b --- /dev/null +++ b/src/charon/sa/authenticators/eap/eap_sim.c @@ -0,0 +1,703 @@ +/** + * @file eap_sim.c + * + * @brief Implementation of eap_sim_t. + * + */ + +/* + * 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 "eap_sim.h" + +#include <dlfcn.h> + +#include <daemon.h> +#include <library.h> + +#define MAX_TRIES 3 + +ENUM(sim_subtype_names, SIM_START, SIM_CLIENT_ERROR, + "SIM_START", + "SIM_CHALLENGE", + "SIM_NOTIFICATION", + "SIM_13", + "SIM_CLIENT_ERROR", +); + +ENUM_BEGIN(sim_attribute_names, AT_END, AT_CLIENT_ERROR_CODE, + "AT_END", + "AT_0", + "AT_RAND", + "AT_AUTN", + "AT_RES", + "AT_AUTS", + "AT_5", + "AT_PADDING", + "AT_NONCE_MT", + "AT_8", + "AT_9", + "AT_PERMANENT_ID_REQ", + "AT_MAC", + "AT_NOTIFICATION", + "AT_ANY_ID_REQ", + "AT_IDENTITY", + "AT_VERSION_LIST", + "AT_SELECTED_VERSION", + "AT_FULLAUTH_ID_REQ", + "AT_18", + "AT_COUNTER", + "AT_COUNTER_TOO_SMALL", + "AT_NONCE_S", + "AT_CLIENT_ERROR_CODE"); +ENUM_NEXT(sim_attribute_names, AT_IV, AT_RESULT_IND, AT_CLIENT_ERROR_CODE, + "AT_IV", + "AT_ENCR_DATA", + "AT_131", + "AT_NEXT_PSEUDONYM", + "AT_NEXT_REAUTH_ID", + "AT_CHECKCODE", + "AT_RESULT_IND"); +ENUM_END(sim_attribute_names, AT_RESULT_IND); + + +typedef struct private_eap_sim_t private_eap_sim_t; + +/** + * Private data of an eap_sim_t object. + */ +struct private_eap_sim_t { + + /** + * Public authenticator_t interface. + */ + eap_sim_t public; + + /** + * ID of ourself + */ + identification_t *peer; + + /** + * SIM cardreader function loaded from library + */ + sim_algo_t alg; + + /** + * handle of the loaded library + */ + void *handle; + + /** + * how many times we try to authenticate + */ + int tries; + + /** + * version this implementation uses + */ + chunk_t version; + + /** + * version list received from server + */ + chunk_t version_list; + + /** + * Nonce value used in AT_NONCE_MT + */ + chunk_t nonce; + + /** + * k_encr key derived from MK + */ + chunk_t k_encr; + + /** + * k_auth key derived from MK, used for AT_MAC verification + */ + chunk_t k_auth; + + /** + * MSK, used for EAP-SIM based IKEv2 authentication + */ + chunk_t msk; + + /** + * EMSK, extendes MSK for further uses + */ + chunk_t emsk; +}; + +/** length of the AT_NONCE_MT nonce value */ +#define NONCE_LEN 16 +/** length of the AT_MAC value */ +#define MAC_LEN 16 +/** length of the AT_RAND value */ +#define RAND_LEN 16 +/** length of the k_encr key */ +#define KENCR_LEN 16 +/** length of the k_auth key */ +#define KAUTH_LEN 16 +/** length of the MSK */ +#define MSK_LEN 64 +/** length of the EMSK */ +#define EMSK_LEN 64 + +/* client error codes used in AT_CLIENT_ERROR_CODE */ +char client_error_general_buf[] = {0x00, 0x01}; +char client_error_unsupported_buf[] = {0x00, 0x02}; +char client_error_insufficient_buf[] = {0x00, 0x03}; +char client_error_notfresh_buf[] = {0x00, 0x04}; +chunk_t client_error_general = chunk_from_buf(client_error_general_buf); +chunk_t client_error_unsupported = chunk_from_buf(client_error_unsupported_buf); +chunk_t client_error_insufficient = chunk_from_buf(client_error_insufficient_buf); +chunk_t client_error_notfresh = chunk_from_buf(client_error_notfresh_buf); + +/** + * Read EAP and EAP-SIM header, return SIM type + */ +static sim_subtype_t read_header(chunk_t *message) +{ + sim_subtype_t type; + + if (message->len < 8) + { + *message = chunk_empty; + return 0; + } + type = *(message->ptr + 5); + *message = chunk_skip(*message, 8); + return type; +} + +/** + * read the next attribute from the chunk data + */ +static sim_attribute_t read_attribute(chunk_t *message, chunk_t *data) +{ + sim_attribute_t attribute; + size_t length; + + DBG3(DBG_IKE, "reading attribute from %B", message); + + if (message->len < 2) + { + return AT_END; + } + attribute = *message->ptr++; + length = *message->ptr++ * 4 - 2; + message->len -= 2; + DBG3(DBG_IKE, "found attribute %N with length %d", + sim_attribute_names, attribute, length); + + if (length > message->len) + { + return AT_END; + } + data->len = length; + data->ptr = message->ptr; + *message = chunk_skip(*message, length); + return attribute; +} + +/** + * Build an EAP-SIM payload using a variable length attribute list. + * The variable argument takes a sim_attribute_t followed by its data in a chunk. + */ +static eap_payload_t *build_payload(private_eap_sim_t *this, u_int8_t identifier, + sim_subtype_t type, ...) +{ + chunk_t message = chunk_alloca(512); + chunk_t pos = message; + eap_payload_t *payload; + va_list args; + sim_attribute_t attr; + u_int8_t *mac_pos = NULL; + chunk_t mac_data = chunk_empty; + + /* write EAP header, skip length bytes */ + *pos.ptr++ = EAP_RESPONSE; + *pos.ptr++ = identifier; + pos.ptr += 2; + pos.len -= 4; + /* write SIM header with type and subtype, zero reserved bytes */ + *pos.ptr++ = EAP_SIM; + *pos.ptr++ = type; + *pos.ptr++ = 0; + *pos.ptr++ = 0; + pos.len -= 4; + + va_start(args, type); + while ((attr = va_arg(args, sim_attribute_t)) != AT_END) + { + chunk_t data = va_arg(args, chunk_t); + + DBG3(DBG_IKE, "building %N %B", sim_attribute_names, attr, &data); + + /* write attribute header */ + *pos.ptr++ = attr; + pos.len--; + + switch (attr) + { + case AT_CLIENT_ERROR_CODE: + case AT_SELECTED_VERSION: + { + *pos.ptr = data.len/4 + 1; + pos = chunk_skip(pos, 1); + memcpy(pos.ptr, data.ptr, data.len); + pos = chunk_skip(pos, data.len); + break; + } + case AT_IDENTITY: + { + /* align up to four byte */ + if (data.len % 4) + { + chunk_t tmp = chunk_alloca((data.len/4)*4 + 4); + memset(tmp.ptr, 0, tmp.len); + memcpy(tmp.ptr, data.ptr, data.len); + data = tmp; + } + *pos.ptr = data.len/4 + 1; + pos = chunk_skip(pos, 1); + /* actual length in bytes */ + *(u_int16_t*)pos.ptr = htons(data.len); + pos = chunk_skip(pos, sizeof(u_int16_t)); + memcpy(pos.ptr, data.ptr, data.len); + pos = chunk_skip(pos, data.len); + break; + } + case AT_NONCE_MT: + { + *pos.ptr = data.len/4 + 1; + pos = chunk_skip(pos, 1); + memset(pos.ptr, 0, 2); + pos = chunk_skip(pos, 2); + memcpy(pos.ptr, data.ptr, data.len); + pos = chunk_skip(pos, data.len); + break; + } + case AT_MAC: + { + *pos.ptr++ = 5; pos.len--; + *pos.ptr++ = 0; pos.len--; + *pos.ptr++ = 0; pos.len--; + mac_pos = pos.ptr; + memset(mac_pos, 0, MAC_LEN); + pos = chunk_skip(pos, MAC_LEN); + mac_data = data; + break; + } + case AT_RAND: + { + *pos.ptr++ = data.len/4 + 1; pos.len--; + *pos.ptr++ = 0; pos.len--; + *pos.ptr++ = 0; pos.len--; + memcpy(pos.ptr, data.ptr, data.len); + pos = chunk_skip(pos, data.len); + break; + } + default: + DBG1(DBG_IKE, "no rule to build EAP_SIM attribute %N, skipped", + sim_attribute_names, attr); + break; + } + } + va_end(args); + + /* calculate message length, write into header */ + message.len = pos.ptr - message.ptr; + *(u_int16_t*)(message.ptr + 2) = htons(message.len); + + /* create MAC if AT_MAC attribte was included. Append supplied va_arg + * chunk mac_data to "to-sign" chunk */ + if (mac_pos) + { + signer_t *signer = signer_create(AUTH_HMAC_SHA1_128); + signer->set_key(signer, this->k_auth); + mac_data = chunk_cata("cc", message, mac_data); + signer->get_signature(signer, mac_data, mac_pos); + DBG3(DBG_IKE, "AT_MAC signature of %B\n is %b", + &mac_data, mac_pos, MAC_LEN); + signer->destroy(signer); + } + + payload = eap_payload_create_data(message); + + DBG3(DBG_IKE, "created EAP message %B", &message); + return payload; +} + +/** + * process an EAP-SIM/Request/Start message + */ +static status_t process_start(private_eap_sim_t *this, eap_payload_t *in, + eap_payload_t **out) +{ + chunk_t message, data; + sim_attribute_t attribute, include_id = AT_END; + u_int8_t identifier; + + identifier = in->get_identifier(in); + message = in->get_data(in); + read_header(&message); + + while ((attribute = read_attribute(&message, &data)) != AT_END) + { + switch (attribute) + { + case AT_VERSION_LIST: + { + /* check if server supports our implementation */ + bool found = FALSE; + if (data.len > 2) + { + /* read actual length first */ + data.len = min(data.len, ntohs(*(u_int16_t*)data.ptr) + 2); + data = chunk_skip(data, 2); + chunk_free(&this->version_list); + this->version_list = chunk_clone(data); + while (data.len >= this->version.len) + { + if (memeq(data.ptr, this->version.ptr, this->version.len)) + { + found = TRUE; + break; + } + data = chunk_skip(data, this->version.len); + } + } + if (!found) + { + DBG1(DBG_IKE, "server does not support EAP_SIM " + "version number %#B", &this->version); + *out = build_payload(this, identifier, SIM_CLIENT_ERROR, + AT_CLIENT_ERROR_CODE, client_error_unsupported, + AT_END); + return NEED_MORE; + } + break; + } + case AT_PERMANENT_ID_REQ: + case AT_FULLAUTH_ID_REQ: + case AT_ANY_ID_REQ: + /* only include AT_IDENTITY if requested */ + include_id = AT_IDENTITY; + break; + default: + DBG1(DBG_IKE, "ignoring EAP_SIM attribute %N", + sim_attribute_names, attribute); + break; + } + } + + /* build payload. If "include_id" is AT_END, AT_IDENTITY is ommited */ + *out = build_payload(this, identifier, SIM_START, + AT_SELECTED_VERSION, this->version, + AT_NONCE_MT, this->nonce, + include_id, this->peer->get_encoding(this->peer), + AT_END); + return NEED_MORE; +} + +/** + * process an EAP-SIM/Request/Challenge message + */ +static status_t process_challenge(private_eap_sim_t *this, eap_payload_t *in, + eap_payload_t **out) +{ + chunk_t message, data, tmp, kcs, kc, sreses, sres, mk; + sim_attribute_t attribute; + u_int8_t identifier, i; + chunk_t mac = chunk_empty, rands = chunk_empty; + signer_t *signer; + hasher_t *hasher; + prf_t *prf; + + if (this->tries-- <= 0) + { + /* give up without notification. This hack is required as some buggy + * server implementations won't respect our client-error. */ + return FAILED; + } + + identifier = in->get_identifier(in); + message = in->get_data(in); + read_header(&message); + + while ((attribute = read_attribute(&message, &data)) != AT_END) + { + switch (attribute) + { + case AT_RAND: + { + rands = chunk_skip(data, 2); + break; + } + case AT_MAC: + { + /* backup MAC, zero it inline for later verification */ + data = chunk_skip(data, 2); + mac = chunk_clonea(data); + memset(data.ptr, 0, data.len); + break; + } + default: + DBG1(DBG_IKE, "ignoring EAP_SIM attribute %N", + sim_attribute_names, attribute); + break; + } + } + + /* excepting two or three RAND, each 16 bytes. We require two valid + * and different RANDs */ + if ((rands.len != 2 * RAND_LEN && rands.len != 3 * RAND_LEN) || + memeq(rands.ptr, rands.ptr + RAND_LEN, RAND_LEN)) + { + DBG1(DBG_IKE, "no valid AT_RAND received"); + *out = build_payload(this, identifier, SIM_CLIENT_ERROR, + AT_CLIENT_ERROR_CODE, client_error_insufficient, + AT_END); + return FAILED; + } + if (mac.len != MAC_LEN) + { + DBG1(DBG_IKE, "no valid AT_MAC received"); + *out = build_payload(this, identifier, SIM_CLIENT_ERROR, + AT_CLIENT_ERROR_CODE, client_error_general, + AT_END); + return NEED_MORE; + } + + /* get two or three KCs/SRESes from SIM using RANDs */ + kcs = kc = chunk_alloca(rands.len / 2); + sreses = sres = chunk_alloca(rands.len / 4); + while (rands.len > 0) + { + int kc_len = kc.len, sres_len = sres.len; + + if (this->alg(rands.ptr, RAND_LEN, sres.ptr, &sres_len, kc.ptr, &kc_len)) + { + DBG1(DBG_IKE, "unable to get triplets from SIM"); + *out = build_payload(this, identifier, SIM_CLIENT_ERROR, + AT_CLIENT_ERROR_CODE, client_error_general, + AT_END); + return NEED_MORE; + } + DBG3(DBG_IKE, "got triplet for RAND %b\n Kc %b\n SRES %b", + rands.ptr, RAND_LEN, sres.ptr, sres_len, kc.ptr, kc_len); + kc = chunk_skip(kc, kc_len); + sres = chunk_skip(sres, sres_len); + rands = chunk_skip(rands, RAND_LEN); + } + + /* build MK = SHA1(Identity|n*Kc|NONCE_MT|Version List|Selected Version) */ + tmp = chunk_cata("ccccc", this->peer->get_encoding(this->peer), kcs, + this->nonce, this->version_list, this->version); + hasher = hasher_create(HASH_SHA1); + mk = chunk_alloca(hasher->get_hash_size(hasher)); + hasher->get_hash(hasher, tmp, mk.ptr); + hasher->destroy(hasher); + DBG3(DBG_IKE, "MK = SHA1(%B\n) = %B", &tmp, &mk); + + /* K_encr | K_auth | MSK | EMSK = prf() | prf() | prf() | prf() + * FIPS PRF has 320 bit block size, we need 160 byte for keys + * => run prf four times */ + prf = prf_create(PRF_FIPS_SHA1_160); + prf->set_key(prf, mk); + tmp = chunk_alloca(prf->get_block_size(prf) * 4); + for (i = 0; i < 4; i++) + { + prf->get_bytes(prf, chunk_empty, tmp.ptr + tmp.len / 4 * i); + } + prf->destroy(prf); + chunk_free(&this->k_encr); + chunk_free(&this->k_auth); + chunk_free(&this->msk); + chunk_free(&this->emsk); + chunk_split(tmp, "aaaa", KENCR_LEN, &this->k_encr, KAUTH_LEN, &this->k_auth, + MSK_LEN, &this->msk, EMSK_LEN, &this->emsk); + DBG3(DBG_IKE, "K_encr %B\nK_auth %B\nMSK %B\nEMSK %B", + &this->k_encr, &this->k_auth, &this->msk, &this->emsk); + + /* verify AT_MAC attribute, signature is over "EAP packet | NONCE_MT" */ + signer = signer_create(AUTH_HMAC_SHA1_128); + signer->set_key(signer, this->k_auth); + tmp = chunk_cata("cc", in->get_data(in), this->nonce); + if (!signer->verify_signature(signer, tmp, mac)) + { + DBG1(DBG_IKE, "AT_MAC verification failed"); + signer->destroy(signer); + *out = build_payload(this, identifier, SIM_CLIENT_ERROR, + AT_CLIENT_ERROR_CODE, client_error_general, + AT_END); + return NEED_MORE; + } + signer->destroy(signer); + + /* build response, AT_MAC is built over "EAP packet | n*SRES" */ + *out = build_payload(this, identifier, SIM_CHALLENGE, + AT_MAC, sreses, + AT_END); + return NEED_MORE; +} + +/** + * Implementation of eap_method_t.process for the peer + */ +static status_t process(private_eap_sim_t *this, + eap_payload_t *in, eap_payload_t **out) +{ + sim_subtype_t type; + chunk_t message; + + message = in->get_data(in); + type = read_header(&message); + + switch (type) + { + case SIM_START: + return process_start(this, in, out); + case SIM_CHALLENGE: + return process_challenge(this, in, out); + default: + DBG1(DBG_IKE, "unable to process EAP_SIM subtype %N", + sim_subtype_names, type); + *out = build_payload(this, in->get_identifier(in), SIM_CLIENT_ERROR, + AT_CLIENT_ERROR_CODE, client_error_general, AT_END); + return NEED_MORE; + } +} + +/** + * Implementation of eap_method_t.initiate for the peer + */ +static status_t initiate(private_eap_sim_t *this, eap_payload_t **out) +{ + /* peer never initiates */ + return FAILED; +} + +/** + * Implementation of eap_method_t.get_type. + */ +static eap_type_t get_type(private_eap_sim_t *this) +{ + return EAP_SIM; +} + +/** + * Implementation of eap_method_t.get_msk. + */ +static status_t get_msk(private_eap_sim_t *this, chunk_t *msk) +{ + if (this->msk.ptr) + { + *msk = this->msk; + return SUCCESS; + } + return FAILED; +} + +/** + * Implementation of eap_method_t.is_mutual. + */ +static bool is_mutual(private_eap_sim_t *this) +{ + return TRUE; +} + +/** + * Implementation of eap_method_t.destroy. + */ +static void destroy(private_eap_sim_t *this) +{ + dlclose(this->handle); + chunk_free(&this->nonce); + chunk_free(&this->version_list); + chunk_free(&this->k_auth); + chunk_free(&this->k_encr); + chunk_free(&this->msk); + chunk_free(&this->emsk); + free(this); +} + +/* + * Described in header. + */ +eap_sim_t *eap_create(eap_role_t role, + identification_t *server, identification_t *peer) +{ + private_eap_sim_t *this; + randomizer_t *randomizer; + static char version[] = {0x00,0x01}; + + if (role != EAP_PEER) + { + return NULL; + } + this = malloc_thing(private_eap_sim_t); + + this->handle = dlopen(SIM_READER_LIB, RTLD_LAZY); + if (this->handle == NULL) + { + DBG1(DBG_IKE, "unable to open SIM reader '%s'", SIM_READER_LIB); + free(this); + return NULL; + } + this->alg = dlsym(this->handle, SIM_READER_ALG); + if (this->alg == NULL) + { + DBG1(DBG_IKE, "unable to open SIM reader function '%s' in '%s'", + SIM_READER_ALG, SIM_READER_LIB); + dlclose(this->handle); + free(this); + return NULL; + } + + randomizer = randomizer_create(); + if (randomizer->allocate_pseudo_random_bytes(randomizer, NONCE_LEN, + &this->nonce)) + { + DBG1(DBG_IKE, "unable to generate NONCE for EAP_SIM"); + randomizer->destroy(randomizer); + free(this); + return NULL; + } + randomizer->destroy(randomizer); + + /* public functions */ + this->public.eap_method_interface.initiate = (status_t(*)(eap_method_t*,eap_payload_t**))initiate; + this->public.eap_method_interface.process = (status_t(*)(eap_method_t*,eap_payload_t*,eap_payload_t**))process; + this->public.eap_method_interface.get_type = (eap_type_t(*)(eap_method_t*))get_type; + this->public.eap_method_interface.is_mutual = (bool(*)(eap_method_t*))is_mutual; + this->public.eap_method_interface.get_msk = (status_t(*)(eap_method_t*,chunk_t*))get_msk; + this->public.eap_method_interface.destroy = (void(*)(eap_method_t*))destroy; + + /* private data */ + this->peer = peer; + this->tries = MAX_TRIES; + this->version.ptr = version; + this->version.len = sizeof(version); + this->version_list = chunk_empty; + this->k_auth = chunk_empty; + this->k_encr = chunk_empty; + this->msk = chunk_empty; + this->emsk = chunk_empty; + + return &this->public; +} diff --git a/src/charon/sa/authenticators/eap/eap_sim.h b/src/charon/sa/authenticators/eap/eap_sim.h new file mode 100644 index 000000000..10640babe --- /dev/null +++ b/src/charon/sa/authenticators/eap/eap_sim.h @@ -0,0 +1,141 @@ +/** + * @file eap_sim.h + * + * @brief Interface of eap_sim_t. + * + */ + +/* + * 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. + */ + +#ifndef EAP_SIM_H_ +#define EAP_SIM_H_ + +typedef struct eap_sim_t eap_sim_t; +typedef enum sim_subtype_t sim_subtype_t; +typedef enum sim_attribute_t sim_attribute_t; + +#include <sa/authenticators/eap/eap_method.h> + +/** + * Subtypes of SIM messages + */ +enum sim_subtype_t { + SIM_START = 10, + SIM_CHALLENGE = 11, + SIM_NOTIFICATION = 12, + SIM_CLIENT_ERROR = 14, +}; + +/** + * enum names for sim_subtype_t + */ +extern enum_name_t *sim_subtype_names; + +enum sim_attribute_t { + /** defines the end of attribute list */ + AT_END = -1, + AT_RAND = 1, + AT_AUTN = 2, + AT_RES = 3, + AT_AUTS = 4, + AT_PADDING = 6, + AT_NONCE_MT = 7, + AT_PERMANENT_ID_REQ = 10, + AT_MAC = 11, + AT_NOTIFICATION = 12, + AT_ANY_ID_REQ = 13, + AT_IDENTITY = 14, + AT_VERSION_LIST = 15, + AT_SELECTED_VERSION = 16, + AT_FULLAUTH_ID_REQ = 17, + AT_COUNTER = 19, + AT_COUNTER_TOO_SMALL = 20, + AT_NONCE_S = 21, + AT_CLIENT_ERROR_CODE = 22, + AT_IV = 129, + AT_ENCR_DATA = 130, + AT_NEXT_PSEUDONYM = 132, + AT_NEXT_REAUTH_ID = 133, + AT_CHECKCODE = 134, + AT_RESULT_IND = 135, +}; + +/** + * enum names for sim_subtype_t + */ +extern enum_name_t *sim_attribute_names; + +/** + * @brief Cardreaders SIM function. + * + * @param rand RAND to run algo with + * @param rand_length length of value in rand + * @param sres buffer to get SRES + * @param sres_length size of buffer in sres, returns bytes written to SRES + * @param kc buffer to get Kc + * @param kc_length size of buffer in Kc, returns bytes written to Kc + * @return zero on success + */ +typedef int (*sim_algo_t)(const unsigned char *rand, int rand_length, + unsigned char *sres, int *sres_length, + unsigned char *kc, int *kc_length); + +#ifndef SIM_READER_LIB +/** the library containing the cardreader with the SIM function */ +#error SIM_READER_LIB not specified, use --with-sim-reader option +#endif /* SIM_READER_LIB */ + +#ifndef SIM_READER_ALG +/** the SIM_READER_LIB's algorithm, uses sim_algo_t signature */ +#define SIM_READER_ALG "sim_run_alg" +#endif /* SIM_READER_ALG */ + + +/** + * @brief Implementation of the eap_method_t interface using EAP-SIM. + * + * This EAP-SIM client implementation uses another pluggable library to + * access the SIM card. This module is specified using the SIM_READER_LIB + * definition. The function to run the algorithm has the sim_algo_t type and + * is named as SIM_READER_ALG is defined. + * + * @b Constructors: + * - eap_create() of this module + * - eap_client_create() using eap_method EAP_SIM + * + * @ingroup eap + */ +struct eap_sim_t { + + /** + * Implemented eap_method_t interface. + */ + eap_method_t eap_method_interface; +}; + +/** + * @brief Creates the EAP method EAP-SIM. + * + * @param server ID of the EAP server + * @param peer ID of the EAP client + * @return eap_sim_t object + * + * @ingroup eap + */ +eap_sim_t *eap_create(eap_role_t role, + identification_t *server, identification_t *peer); + +#endif /* EAP_SIM_H_ */ diff --git a/src/charon/sa/authenticators/eap_authenticator.c b/src/charon/sa/authenticators/eap_authenticator.c new file mode 100644 index 000000000..6c8ca8d8f --- /dev/null +++ b/src/charon/sa/authenticators/eap_authenticator.c @@ -0,0 +1,360 @@ +/** + * @file eap_authenticator.c + * + * @brief Implementation of eap_authenticator_t. + * + */ + +/* + * 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 <string.h> + +#include "eap_authenticator.h" + +#include <daemon.h> +#include <config/policies/policy.h> +#include <sa/authenticators/eap/eap_method.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; + + /** + * Role of this authenticator, PEER or SERVER + */ + eap_role_t role; + + /** + * Current EAP method processing + */ + eap_method_t *method; + + /** + * MSK used to build and verify auth payload + */ + chunk_t msk; +}; + +extern chunk_t build_shared_key_signature(chunk_t ike_sa_init, chunk_t nonce, + chunk_t secret, identification_t *id, + prf_t *prf_skp, prf_t *prf); + +/** + * Implementation of authenticator_t.verify. + */ +static status_t verify(private_eap_authenticator_t *this, chunk_t ike_sa_init, + chunk_t my_nonce, auth_payload_t *auth_payload) +{ + chunk_t auth_data, recv_auth_data; + identification_t *other_id = this->ike_sa->get_other_id(this->ike_sa); + + auth_data = build_shared_key_signature(ike_sa_init, my_nonce, this->msk, + other_id, this->ike_sa->get_auth_verify(this->ike_sa), + this->ike_sa->get_prf(this->ike_sa)); + + recv_auth_data = auth_payload->get_data(auth_payload); + if (!chunk_equals(auth_data, recv_auth_data)) + { + DBG1(DBG_IKE, "verification of AUTH payload created from EAP MSK failed"); + chunk_free(&auth_data); + return FAILED; + } + chunk_free(&auth_data); + + DBG1(DBG_IKE, "authentication of '%D' with %N successful", + other_id, auth_method_names, AUTH_EAP); + return SUCCESS; +} + +/** + * Implementation of authenticator_t.build. + */ +static status_t build(private_eap_authenticator_t *this, chunk_t ike_sa_init, + chunk_t other_nonce, auth_payload_t **auth_payload) +{ + chunk_t auth_data; + identification_t *my_id = this->ike_sa->get_my_id(this->ike_sa); + + DBG1(DBG_IKE, "authentication of '%D' (myself) with %N", + my_id, auth_method_names, AUTH_EAP); + + auth_data = build_shared_key_signature(ike_sa_init, other_nonce, this->msk, + my_id, this->ike_sa->get_auth_build(this->ike_sa), + this->ike_sa->get_prf(this->ike_sa)); + + *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); + + return SUCCESS; +} + +/** + * Implementation of eap_authenticator_t.initiate + */ +static status_t initiate(private_eap_authenticator_t *this, eap_type_t type, + eap_payload_t **out) +{ + /* if initiate() is called, role is always server */ + this->role = EAP_SERVER; + + if (type == 0) + { + DBG1(DBG_IKE, + "client requested EAP authentication, but configuration forbids it"); + *out = eap_payload_create_code(EAP_FAILURE); + return FAILED; + } + + DBG1(DBG_IKE, "requesting %N authentication", eap_type_names, type); + this->method = eap_method_create(type, this->role, + this->ike_sa->get_my_id(this->ike_sa), + this->ike_sa->get_other_id(this->ike_sa)); + + if (this->method == NULL) + { + DBG1(DBG_IKE, "configured EAP server method %N not supported, sending %N", + eap_type_names, type, eap_code_names, EAP_FAILURE); + *out = eap_payload_create_code(EAP_FAILURE); + return FAILED; + } + if (this->method->initiate(this->method, out) != NEED_MORE) + { + DBG1(DBG_IKE, "failed to initiate %N, sending %N", + eap_type_names, type, eap_code_names, EAP_FAILURE); + *out = eap_payload_create_code(EAP_FAILURE); + return FAILED; + } + return NEED_MORE; +} + +/** + * Processing method for a peer + */ +static status_t process_peer(private_eap_authenticator_t *this, + eap_payload_t *in, eap_payload_t **out) +{ + eap_type_t type = in->get_type(in); + + if (type == EAP_IDENTITY) + { + eap_method_t *method = eap_method_create(type, EAP_PEER, + this->ike_sa->get_other_id(this->ike_sa), + this->ike_sa->get_my_id(this->ike_sa)); + + if (method == NULL || method->process(method, in, out) != SUCCESS) + { + DBG1(DBG_IKE, "EAP server requested %N, but unable to process", + eap_type_names, type); + DESTROY_IF(method); + return FAILED; + } + + DBG1(DBG_IKE, "EAP server requested %N, sending IKE identity", + eap_type_names, type); + + method->destroy(method); + return NEED_MORE; + } + + /* create an eap_method for the first call */ + if (this->method == NULL) + { + DBG1(DBG_IKE, "EAP server requested %N authentication", + eap_type_names, type); + this->method = eap_method_create(type, EAP_PEER, + this->ike_sa->get_other_id(this->ike_sa), + this->ike_sa->get_my_id(this->ike_sa)); + if (this->method == NULL) + { + DBG1(DBG_IKE, "EAP server requested unsupported " + "EAP method %N, sending EAP_NAK", eap_type_names, type); + *out = eap_payload_create_nak(); + return NEED_MORE; + } + } + + switch (this->method->process(this->method, in, out)) + { + case NEED_MORE: + return NEED_MORE; + case SUCCESS: + DBG1(DBG_IKE, "EAP method %N succeded", + eap_type_names, this->method->get_type(this->method)); + return SUCCESS; + case FAILED: + default: + DBG1(DBG_IKE, "EAP method %N failed", + eap_type_names, this->method->get_type(this->method)); + return FAILED; + } +} + +/** + * Processing method for a server + */ +static status_t process_server(private_eap_authenticator_t *this, + eap_payload_t *in, eap_payload_t **out) +{ + switch (this->method->process(this->method, in, out)) + { + case NEED_MORE: + return NEED_MORE; + case SUCCESS: + if (this->method->get_msk(this->method, &this->msk) == SUCCESS) + { + DBG1(DBG_IKE, "EAP method %N succeded, MSK established", + eap_type_names, this->method->get_type(this->method)); + this->msk = chunk_clone(this->msk); + *out = eap_payload_create_code(EAP_SUCCESS); + return SUCCESS; + } + DBG1(DBG_IKE, "EAP method %N succeded, but no MSK established", + eap_type_names, this->method->get_type(this->method)); + *out = eap_payload_create_code(EAP_FAILURE); + return FAILED; + case FAILED: + default: + DBG1(DBG_IKE, "EAP method %N failed for peer %D", + eap_type_names, this->method->get_type(this->method), + this->ike_sa->get_other_id(this->ike_sa)); + *out = eap_payload_create_code(EAP_FAILURE); + return FAILED; + } +} + +/** + * Implementation of eap_authenticator_t.process + */ +static status_t process(private_eap_authenticator_t *this, eap_payload_t *in, + eap_payload_t **out) +{ + eap_code_t code = in->get_code(in); + + switch (this->role) + { + case EAP_SERVER: + { + switch (code) + { + case EAP_RESPONSE: + { + return process_server(this, in, out); + } + default: + { + DBG1(DBG_IKE, "received %N, sending %N", + eap_code_names, code, eap_code_names, EAP_FAILURE); + *out = eap_payload_create_code(EAP_FAILURE); + return FAILED; + } + } + } + case EAP_PEER: + { + switch (code) + { + case EAP_REQUEST: + { + return process_peer(this, in, out); + } + case EAP_SUCCESS: + { + if (this->method->get_msk(this->method, &this->msk) == SUCCESS) + { + this->msk = chunk_clone(this->msk); + return SUCCESS; + } + DBG1(DBG_IKE, "EAP method %N has no MSK established", + eap_type_names, this->method->get_type(this->method)); + return FAILED; + } + case EAP_FAILURE: + default: + { + DBG1(DBG_IKE, "received %N, EAP authentication failed", + eap_code_names, code); + return FAILED; + } + } + } + default: + { + return FAILED; + } + } +} + +/** + * Implementation of authenticator_t.is_mutual. + */ +static bool is_mutual(private_eap_authenticator_t *this) +{ + if (this->method) + { + return this->method->is_mutual(this->method); + } + return FALSE; +} + +/** + * Implementation of authenticator_t.destroy. + */ +static void destroy(private_eap_authenticator_t *this) +{ + DESTROY_IF(this->method); + chunk_free(&this->msk); + free(this); +} + +/* + * Described in header. + */ +eap_authenticator_t *eap_authenticator_create(ike_sa_t *ike_sa) +{ + private_eap_authenticator_t *this = malloc_thing(private_eap_authenticator_t); + + /* public functions */ + this->public.authenticator_interface.verify = (status_t(*)(authenticator_t*,chunk_t,chunk_t,auth_payload_t*))verify; + this->public.authenticator_interface.build = (status_t(*)(authenticator_t*,chunk_t,chunk_t,auth_payload_t**))build; + this->public.authenticator_interface.destroy = (void(*)(authenticator_t*))destroy; + + this->public.is_mutual = (bool(*)(eap_authenticator_t*))is_mutual; + this->public.initiate = (status_t(*)(eap_authenticator_t*,eap_type_t,eap_payload_t**))initiate; + this->public.process = (status_t(*)(eap_authenticator_t*,eap_payload_t*,eap_payload_t**))process; + + /* private data */ + this->ike_sa = ike_sa; + this->role = EAP_PEER; + this->method = NULL; + this->msk = chunk_empty; + + return &this->public; +} diff --git a/src/charon/sa/authenticators/eap_authenticator.h b/src/charon/sa/authenticators/eap_authenticator.h new file mode 100644 index 000000000..ffa162343 --- /dev/null +++ b/src/charon/sa/authenticators/eap_authenticator.h @@ -0,0 +1,156 @@ +/** + * @file eap_authenticator.h + * + * @brief Interface of eap_authenticator_t. + * + */ + +/* + * 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. + */ + +#ifndef EAP_AUTHENTICATOR_H_ +#define EAP_AUTHENTICATOR_H_ + +typedef struct eap_authenticator_t eap_authenticator_t; + +#include <sa/authenticators/authenticator.h> +#include <encoding/payloads/eap_payload.h> + +/** + * @brief Implementation of the authenticator_t interface using AUTH_EAP. + * + * Authentication using EAP involves the most complex authenticator. It stays + * alive over multiple ike_auth transactions and handles multiple EAP + * messages. + * EAP authentication must be clearly distinguished between using + * mutual EAP methods and using methods not providing server authentication. + * If no mutual authentication is used, the server must prove it's identity + * by traditional AUTH methods (RSA, psk). Only when the EAP method is mutual, + * the client should accept an EAP-only authentication. + * RFC4306 does always use traditional authentiction, EAP only authentication + * is described in the internet draft draft-eronen-ipsec-ikev2-eap-auth-05.txt. + * + * @verbatim + ike_sa_init + -------------------------> + <------------------------- + followed by multiple ike_auth: + + +--------+ +--------+ + | EAP | ID, SA, TS, N(EAP_ONLY) | EAP | + | client | ---------------------------> | server | + | | ID, [AUTH,] EAP | | AUTH payload is + | | <--------------------------- | | only included if + | | EAP | | authentication + | | ---------------------------> | | is not mutual. + | | EAP | | + | | <--------------------------- | | + | | EAP | | + | | ---------------------------> | | + | | EAP(SUCCESS) | | + | | <--------------------------- | | + | | AUTH | | If EAP establishes + | | ---------------------------> | | a session key, AUTH + | | AUTH, SA, TS | | payloads use this + | | <--------------------------- | | key, not SK_pi/pr + +--------+ +--------+ + + @endverbatim + * @b Constructors: + * - eap_authenticator_create() + * - authenticator_create() using auth_method AUTH_EAP + * + * @ingroup authenticators + */ +struct eap_authenticator_t { + + /** + * Implemented authenticator_t interface. + */ + authenticator_t authenticator_interface; + + /** + * @brief Check if the EAP method was/is mutual and secure. + * + * RFC4306 proposes to authenticate the EAP responder (server) by standard + * IKEv2 methods (RSA, psk). Not all, but some EAP methods + * provide mutual authentication, which would result in a redundant + * authentication. If the client supports EAP_ONLY_AUTHENTICATION, and + * the the server provides mutual authentication, authentication using + * RSA/PSK may be omitted. If the server did not include a traditional + * AUTH payload, the client must verify that the server initiated mutual + * EAP authentication before it can trust the server. + * + * @param this calling object + * @return TRUE, if no AUTH payload required, FALSE otherwise + */ + bool (*is_mutual) (eap_authenticator_t* this); + + /** + * @brief Initiate the EAP exchange. + * + * The server initiates EAP exchanges, so the client never calls + * this method. If initiate() returns NEED_MORE, the EAP authentication + * process started. In any case, a payload is created in "out". + * + * @param this calling object + * @param type EAP method to use to authenticate client + * @param out created initiaal EAP message to send + * @return + * - FAILED, if initiation failed + * - NEED_MORE, if more EAP exchanges reqired + */ + status_t (*initiate) (eap_authenticator_t* this, eap_type_t type, + eap_payload_t **out); + + /** + * @brief Process an EAP message. + * + * After receiving an EAP message "in", the peer/server processes + * the payload and creates a reply/subsequent request. + * The server side always returns NEED_MORE if another EAP message + * is excepted from the client, SUCCESS if EAP exchange completed and + * "out" is EAP_SUCCES, or FAILED if the EAP exchange failed with + * a EAP_FAILURE payload in "out". Anyway, a payload in "out" is always + * created. + * The peer (client) side only creates a "out" payload if result is + * NEED_MORE, a SUCCESS/FAILED is returned whenever a + * EAP_SUCCESS/EAP_FAILURE message is received in "in". + * If a SUCCESS is returned (on any side), the EAP authentication was + * successful and the AUTH payload can be exchanged. + * + * @param this calling object + * @param in received EAP message + * @param out created EAP message to send + * @return + * - FAILED, if authentication/EAP exchange failed + * - SUCCESS, if authentication completed + * - NEED_MORE, if more EAP exchanges reqired + */ + status_t (*process) (eap_authenticator_t* this, + eap_payload_t *in, eap_payload_t **out); +}; + +/** + * @brief Creates an authenticator for AUTH_EAP. + * + * @param ike_sa associated ike_sa + * @return eap_authenticator_t object + * + * @ingroup authenticators + */ +eap_authenticator_t *eap_authenticator_create(ike_sa_t *ike_sa); + +#endif /* EAP_AUTHENTICATOR_H_ */ diff --git a/src/charon/sa/authenticators/psk_authenticator.c b/src/charon/sa/authenticators/psk_authenticator.c new file mode 100644 index 000000000..43aec0971 --- /dev/null +++ b/src/charon/sa/authenticators/psk_authenticator.c @@ -0,0 +1,204 @@ +/** + * @file psk_authenticator.c + * + * @brief Implementation of psk_authenticator_t. + * + */ + +/* + * 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 <string.h> + +#include "psk_authenticator.h" + +#include <config/policies/policy.h> +#include <daemon.h> + +/** + * 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 + + +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; +}; + +/** + * Builds the octets to be signed as described in section 2.15 of RFC 4306 + */ +chunk_t build_tbs_octets(chunk_t ike_sa_init, chunk_t nonce, + identification_t *id, prf_t *prf) +{ + u_int8_t id_header_buf[] = {0x00, 0x00, 0x00, 0x00}; + chunk_t id_header = chunk_from_buf(id_header_buf); + chunk_t id_with_header, id_prfd, id_encoding; + + id_header_buf[0] = id->get_type(id); + id_encoding = id->get_encoding(id); + + id_with_header = chunk_cat("cc", id_header, id_encoding); + prf->allocate_bytes(prf, id_with_header, &id_prfd); + chunk_free(&id_with_header); + + return chunk_cat("ccm", ike_sa_init, nonce, id_prfd); +} + +/** + * Creates the AUTH data using auth method SHARED_KEY_MESSAGE_INTEGRITY_CODE. + */ +chunk_t build_shared_key_signature(chunk_t ike_sa_init, chunk_t nonce, + chunk_t secret, identification_t *id, + prf_t *prf_skp, prf_t *prf) +{ + chunk_t key_pad, key, auth_data, octets; + + octets = build_tbs_octets(ike_sa_init, nonce, id, prf_skp); + /* AUTH = prf(prf(Shared Secret,"Key Pad for IKEv2"), <msg octets>) */ + key_pad.ptr = IKEV2_KEY_PAD; + key_pad.len = IKEV2_KEY_PAD_LENGTH; + prf->set_key(prf, secret); + prf->allocate_bytes(prf, key_pad, &key); + prf->set_key(prf, key); + prf->allocate_bytes(prf, octets, &auth_data); + DBG3(DBG_IKE, "octets = message + nonce + prf(Sk_px, IDx') %B", &octets); + DBG3(DBG_IKE, "secret %B", &secret); + DBG3(DBG_IKE, "keypad %B", &key_pad); + DBG3(DBG_IKE, "prf(secret, keypad) %B", &key); + DBG3(DBG_IKE, "AUTH = prf(prf(secret, keypad), octets) %B", &auth_data); + chunk_free(&octets); + chunk_free(&key); + + return auth_data; +} + +/** + * Implementation of authenticator_t.verify. + */ +static status_t verify(private_psk_authenticator_t *this, chunk_t ike_sa_init, + chunk_t my_nonce, auth_payload_t *auth_payload) +{ + status_t status; + chunk_t auth_data, recv_auth_data, shared_key; + identification_t *my_id, *other_id; + + my_id = this->ike_sa->get_my_id(this->ike_sa); + other_id = this->ike_sa->get_other_id(this->ike_sa); + status = charon->credentials->get_shared_key(charon->credentials, my_id, + other_id, &shared_key); + if (status != SUCCESS) + { + DBG1(DBG_IKE, "no shared key found for '%D' - '%D'", my_id, other_id); + return status; + } + + auth_data = build_shared_key_signature(ike_sa_init, my_nonce, shared_key, + other_id, this->ike_sa->get_auth_verify(this->ike_sa), + this->ike_sa->get_prf(this->ike_sa)); + chunk_free(&shared_key); + + recv_auth_data = auth_payload->get_data(auth_payload); + if (auth_data.len != recv_auth_data.len || + !memeq(auth_data.ptr, recv_auth_data.ptr, auth_data.len)) + { + DBG1(DBG_IKE, "PSK MAC verification failed"); + chunk_free(&auth_data); + return FAILED; + } + chunk_free(&auth_data); + + DBG1(DBG_IKE, "authentication of '%D' with %N successful", + other_id, auth_method_names, AUTH_PSK); + return SUCCESS; +} + +/** + * Implementation of authenticator_t.build. + */ +static status_t build(private_psk_authenticator_t *this, chunk_t ike_sa_init, + chunk_t other_nonce, auth_payload_t **auth_payload) +{ + chunk_t shared_key; + chunk_t auth_data; + status_t status; + identification_t *my_id, *other_id; + + 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 '%D' (myself) with %N", + my_id, auth_method_names, AUTH_PSK); + status = charon->credentials->get_shared_key(charon->credentials, my_id, + other_id, &shared_key); + if (status != SUCCESS) + { + DBG1(DBG_IKE, "no shared key found for '%D' - '%D'", my_id, other_id); + return status; + } + + auth_data = build_shared_key_signature(ike_sa_init, other_nonce, shared_key, + my_id, this->ike_sa->get_auth_build(this->ike_sa), + this->ike_sa->get_prf(this->ike_sa)); + DBG2(DBG_IKE, "successfully created shared key MAC"); + chunk_free(&shared_key); + *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); + return SUCCESS; +} + +/** + * Implementation of authenticator_t.destroy. + */ +static void destroy(private_psk_authenticator_t *this) +{ + free(this); +} + +/* + * Described in header. + */ +psk_authenticator_t *psk_authenticator_create(ike_sa_t *ike_sa) +{ + private_psk_authenticator_t *this = malloc_thing(private_psk_authenticator_t); + + /* public functions */ + this->public.authenticator_interface.verify = (status_t(*)(authenticator_t*,chunk_t,chunk_t,auth_payload_t*))verify; + this->public.authenticator_interface.build = (status_t(*)(authenticator_t*,chunk_t,chunk_t,auth_payload_t**))build; + this->public.authenticator_interface.destroy = (void(*)(authenticator_t*))destroy; + + /* private data */ + this->ike_sa = ike_sa; + + return &this->public; +} diff --git a/src/charon/sa/authenticators/psk_authenticator.h b/src/charon/sa/authenticators/psk_authenticator.h new file mode 100644 index 000000000..c1c5bcaac --- /dev/null +++ b/src/charon/sa/authenticators/psk_authenticator.h @@ -0,0 +1,57 @@ +/** + * @file psk_authenticator.h + * + * @brief Interface of psk_authenticator_t. + * + */ + +/* + * 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. + */ + +#ifndef PSK_AUTHENTICATOR_H_ +#define PSK_AUTHENTICATOR_H_ + +typedef struct psk_authenticator_t psk_authenticator_t; + +#include <sa/authenticators/authenticator.h> + +/** + * @brief Implementation of the authenticator_t interface using AUTH_PSK. + * + * @b Constructors: + * - psk_authenticator_create() + * - authenticator_create() using auth_method AUTH_PSK + * + * @ingroup authenticators + */ +struct psk_authenticator_t { + + /** + * Implemented authenticator_t interface. + */ + authenticator_t authenticator_interface; +}; + +/** + * @brief Creates an authenticator for AUTH_PSK. + * + * @param ike_sa associated ike_sa + * @return psk_authenticator_t object + * + * @ingroup authenticators + */ +psk_authenticator_t *psk_authenticator_create(ike_sa_t *ike_sa); + +#endif /* PSK_AUTHENTICATOR_H_ */ diff --git a/src/charon/sa/authenticators/rsa_authenticator.c b/src/charon/sa/authenticators/rsa_authenticator.c new file mode 100644 index 000000000..dfa01e332 --- /dev/null +++ b/src/charon/sa/authenticators/rsa_authenticator.c @@ -0,0 +1,180 @@ +/** + * @file rsa_authenticator.c + * + * @brief Implementation of rsa_authenticator_t. + * + */ + +/* + * 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 <string.h> + +#include "rsa_authenticator.h" + +#include <config/policies/policy.h> +#include <daemon.h> + + +typedef struct private_rsa_authenticator_t private_rsa_authenticator_t; + +/** + * Private data of an rsa_authenticator_t object. + */ +struct private_rsa_authenticator_t { + + /** + * Public authenticator_t interface. + */ + rsa_authenticator_t public; + + /** + * Assigned IKE_SA + */ + ike_sa_t *ike_sa; +}; + +/** + * Function implemented in psk_authenticator.c + */ +extern chunk_t build_tbs_octets(chunk_t ike_sa_init, chunk_t nonce, + identification_t *id, prf_t *prf); + +/** + * Implementation of authenticator_t.verify. + */ +static status_t verify(private_rsa_authenticator_t *this, chunk_t ike_sa_init, + chunk_t my_nonce, auth_payload_t *auth_payload) +{ + status_t status; + chunk_t auth_data, octets; + rsa_public_key_t *public_key; + identification_t *other_id; + + other_id = this->ike_sa->get_other_id(this->ike_sa); + + if (auth_payload->get_auth_method(auth_payload) != AUTH_RSA) + { + return INVALID_ARG; + } + auth_data = auth_payload->get_data(auth_payload); + public_key = charon->credentials->get_trusted_public_key(charon->credentials, + other_id); + if (public_key == NULL) + { + DBG1(DBG_IKE, "no RSA public key found for '%D'", other_id); + return NOT_FOUND; + } + octets = build_tbs_octets(ike_sa_init, my_nonce, other_id, + this->ike_sa->get_auth_verify(this->ike_sa)); + status = public_key->verify_emsa_pkcs1_signature(public_key, octets, auth_data); + chunk_free(&octets); + + if (status != SUCCESS) + { + DBG1(DBG_IKE, "RSA signature verification failed"); + return status; + } + + DBG1(DBG_IKE, "authentication of '%D' with %N successful", + other_id, auth_method_names, AUTH_RSA); + return SUCCESS; +} + +/** + * Implementation of authenticator_t.build. + */ +static status_t build(private_rsa_authenticator_t *this, chunk_t ike_sa_init, + chunk_t other_nonce, auth_payload_t **auth_payload) +{ + chunk_t chunk; + chunk_t octets; + chunk_t auth_data; + status_t status; + rsa_public_key_t *my_pubkey; + rsa_private_key_t *my_key; + identification_t *my_id; + + my_id = this->ike_sa->get_my_id(this->ike_sa); + DBG1(DBG_IKE, "authentication of '%D' (myself) with %N", + my_id, auth_method_names, AUTH_RSA); + DBG2(DBG_IKE, "looking for RSA public key belonging to '%D'", my_id); + + my_pubkey = charon->credentials->get_rsa_public_key(charon->credentials, my_id); + if (my_pubkey == NULL) + { + DBG1(DBG_IKE, "no RSA public key found for '%D'", my_id); + return NOT_FOUND; + } + DBG2(DBG_IKE, "matching RSA public key found"); + chunk = my_pubkey->get_keyid(my_pubkey); + DBG2(DBG_IKE, "looking for RSA private key with keyid %#B", &chunk); + my_key = charon->credentials->get_rsa_private_key(charon->credentials, my_pubkey); + if (my_key == NULL) + { + DBG1(DBG_IKE, "no RSA private key found with for %D with keyid %#B", + my_id, &chunk); + return NOT_FOUND; + } + DBG2(DBG_IKE, "matching RSA private key found"); + + octets = build_tbs_octets(ike_sa_init, other_nonce, my_id, + this->ike_sa->get_auth_build(this->ike_sa)); + status = my_key->build_emsa_pkcs1_signature(my_key, HASH_SHA1, octets, &auth_data); + chunk_free(&octets); + + if (status != SUCCESS) + { + my_key->destroy(my_key); + DBG1(DBG_IKE, "build signature of SHA1 hash failed"); + return status; + } + DBG2(DBG_IKE, "successfully signed with RSA private key"); + + *auth_payload = auth_payload_create(); + (*auth_payload)->set_auth_method(*auth_payload, AUTH_RSA); + (*auth_payload)->set_data(*auth_payload, auth_data); + + my_key->destroy(my_key); + chunk_free(&auth_data); + return SUCCESS; +} + +/** + * Implementation of authenticator_t.destroy. + */ +static void destroy(private_rsa_authenticator_t *this) +{ + free(this); +} + +/* + * Described in header. + */ +rsa_authenticator_t *rsa_authenticator_create(ike_sa_t *ike_sa) +{ + private_rsa_authenticator_t *this = malloc_thing(private_rsa_authenticator_t); + + /* public functions */ + this->public.authenticator_interface.verify = (status_t(*)(authenticator_t*,chunk_t,chunk_t,auth_payload_t*))verify; + this->public.authenticator_interface.build = (status_t(*)(authenticator_t*,chunk_t,chunk_t,auth_payload_t**))build; + this->public.authenticator_interface.destroy = (void(*)(authenticator_t*))destroy; + + /* private data */ + this->ike_sa = ike_sa; + + return &this->public; +} diff --git a/src/charon/sa/authenticators/rsa_authenticator.h b/src/charon/sa/authenticators/rsa_authenticator.h new file mode 100644 index 000000000..cc5cc0150 --- /dev/null +++ b/src/charon/sa/authenticators/rsa_authenticator.h @@ -0,0 +1,57 @@ +/** + * @file rsa_authenticator.h + * + * @brief Interface of rsa_authenticator_t. + * + */ + +/* + * 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. + */ + +#ifndef RSA_AUTHENTICATOR_H_ +#define RSA_AUTHENTICATOR_H_ + +typedef struct rsa_authenticator_t rsa_authenticator_t; + +#include <sa/authenticators/authenticator.h> + +/** + * @brief Implementation of the authenticator_t interface using AUTH_RSA. + * + * @b Constructors: + * - rsa_authenticator_create() + * - authenticator_create() using auth_method AUTH_RSA + * + * @ingroup authenticators + */ +struct rsa_authenticator_t { + + /** + * Implemented authenticator_t interface. + */ + authenticator_t authenticator_interface; +}; + +/** + * @brief Creates an authenticator for AUTH_RSA. + * + * @param ike_sa associated ike_sa + * @return rsa_authenticator_t object + * + * @ingroup authenticators + */ +rsa_authenticator_t *rsa_authenticator_create(ike_sa_t *ike_sa); + +#endif /* RSA_AUTHENTICATOR_H_ */ diff --git a/src/charon/sa/child_sa.c b/src/charon/sa/child_sa.c new file mode 100644 index 000000000..19131389d --- /dev/null +++ b/src/charon/sa/child_sa.c @@ -0,0 +1,1130 @@ +/** + * @file child_sa.c + * + * @brief Implementation of child_sa_t. + * + */ + +/* + * Copyright (C) 2005-2007 Martin Willi + * Copyright (C) 2006 Tobias Brunner, 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 <printf.h> + +#include <daemon.h> + +ENUM(child_sa_state_names, CHILD_CREATED, CHILD_DELETING, + "CREATED", + "ROUTED", + "INSTALLED", + "REKEYING", + "DELETING", +); + +typedef struct sa_policy_t sa_policy_t; + +/** + * Struct used to store information for a policy. This + * is needed since we must provide all this information + * for deleting a policy... + */ +struct sa_policy_t { + /** + * Traffic selector for us + */ + traffic_selector_t *my_ts; + + /** + * Traffic selector for other + */ + traffic_selector_t *other_ts; +}; + +typedef struct private_child_sa_t private_child_sa_t; + +/** + * Private data of a child_sa_t bject. + */ +struct private_child_sa_t { + /** + * Public interface of child_sa_t. + */ + child_sa_t public; + + struct { + /** address of peer */ + host_t *addr; + /** id of peer */ + identification_t *id; + /** actual used SPI, 0 if unused */ + u_int32_t spi; + } me, other; + + /** + * Allocated SPI for a ESP proposal candidates + */ + u_int32_t alloc_esp_spi; + + /** + * Allocated SPI for a AH proposal candidates + */ + u_int32_t alloc_ah_spi; + + /** + * Protocol used to protect this SA, ESP|AH + */ + protocol_id_t protocol; + + /** + * List containing sa_policy_t objects + */ + linked_list_t *policies; + + /** + * Seperate list for local traffic selectors + */ + linked_list_t *my_ts; + + /** + * Seperate list for remote traffic selectors + */ + linked_list_t *other_ts; + + /** + * reqid used for this child_sa + */ + u_int32_t reqid; + + /** + * encryption algorithm used for this SA + */ + algorithm_t encryption; + + /** + * integrity protection algorithm used for this SA + */ + algorithm_t integrity; + + /** + * time, on which SA was installed + */ + time_t install_time; + + /** + * absolute time when rekeying is sceduled + */ + time_t rekey_time; + + /** + * state of the CHILD_SA + */ + child_sa_state_t state; + + /** + * Specifies if NAT traversal is used + */ + bool use_natt; + + /** + * mode this SA uses, tunnel/transport + */ + mode_t mode; + + /** + * virtual IP assinged to local host + */ + host_t *virtual_ip; + + /** + * policy used to create this child + */ + policy_t *policy; +}; + +/** + * Implementation of child_sa_t.get_name. + */ +static char *get_name(private_child_sa_t *this) +{ + return this->policy->get_name(this->policy);; +} + +/** + * 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_spi + */ +u_int32_t get_spi(private_child_sa_t *this, bool inbound) +{ + if (inbound) + { + return this->me.spi; + } + return this->other.spi; +} + +/** + * Implements child_sa_t.get_protocol + */ +protocol_id_t get_protocol(private_child_sa_t *this) +{ + return this->protocol; +} + +/** + * 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_policy + */ +static policy_t* get_policy(private_child_sa_t *this) +{ + return this->policy; +} + +/** + * Run the up/down script + */ +static void updown(private_child_sa_t *this, bool up) +{ + sa_policy_t *policy; + iterator_t *iterator; + char *script; + + script = this->policy->get_updown(this->policy); + + if (script == NULL) + { + return; + } + + iterator = this->policies->create_iterator(this->policies, TRUE); + while (iterator->iterate(iterator, (void**)&policy)) + { + char command[1024]; + char *ifname = NULL; + char *my_client, *other_client, *my_client_mask, *other_client_mask; + char *pos, *virtual_ip; + FILE *shell; + + /* get subnet/bits from string */ + asprintf(&my_client, "%R", policy->my_ts); + pos = strchr(my_client, '/'); + *pos = '\0'; + my_client_mask = pos + 1; + pos = strchr(my_client_mask, '['); + if (pos) + { + *pos = '\0'; + } + asprintf(&other_client, "%R", policy->other_ts); + pos = strchr(other_client, '/'); + *pos = '\0'; + other_client_mask = pos + 1; + pos = strchr(other_client_mask, '['); + if (pos) + { + *pos = '\0'; + } + + if (this->virtual_ip) + { + asprintf(&virtual_ip, "PLUTO_MY_SOURCEIP='%H' ", + this->virtual_ip); + } + else + { + asprintf(&virtual_ip, ""); + } + + ifname = charon->kernel_interface->get_interface(charon->kernel_interface, + this->me.addr); + + /* build the command with all env variables. + * TODO: PLUTO_PEER_CA and PLUTO_NEXT_HOP are currently missing + */ + snprintf(command, sizeof(command), + "2>&1 " + "PLUTO_VERSION='1.1' " + "PLUTO_VERB='%s%s%s' " + "PLUTO_CONNECTION='%s' " + "PLUTO_INTERFACE='%s' " + "PLUTO_REQID='%u' " + "PLUTO_ME='%H' " + "PLUTO_MY_ID='%D' " + "PLUTO_MY_CLIENT='%s/%s' " + "PLUTO_MY_CLIENT_NET='%s' " + "PLUTO_MY_CLIENT_MASK='%s' " + "PLUTO_MY_PORT='%u' " + "PLUTO_MY_PROTOCOL='%u' " + "PLUTO_PEER='%H' " + "PLUTO_PEER_ID='%D' " + "PLUTO_PEER_CLIENT='%s/%s' " + "PLUTO_PEER_CLIENT_NET='%s' " + "PLUTO_PEER_CLIENT_MASK='%s' " + "PLUTO_PEER_PORT='%u' " + "PLUTO_PEER_PROTOCOL='%u' " + "%s" + "%s" + "%s", + up ? "up" : "down", + policy->my_ts->is_host(policy->my_ts, + this->me.addr) ? "-host" : "-client", + this->me.addr->get_family(this->me.addr) == AF_INET ? "" : "-ipv6", + this->policy->get_name(this->policy), + ifname ? ifname : "(unknown)", + this->reqid, + this->me.addr, + this->me.id, + my_client, my_client_mask, + my_client, my_client_mask, + policy->my_ts->get_from_port(policy->my_ts), + policy->my_ts->get_protocol(policy->my_ts), + this->other.addr, + this->other.id, + other_client, other_client_mask, + other_client, other_client_mask, + policy->other_ts->get_from_port(policy->other_ts), + policy->other_ts->get_protocol(policy->other_ts), + virtual_ip, + this->policy->get_hostaccess(this->policy) ? + "PLUTO_HOST_ACCESS='1' " : "", + script); + free(ifname); + free(my_client); + free(other_client); + free(virtual_ip); + + shell = popen(command, "r"); + + if (shell == NULL) + { + DBG1(DBG_CHD, "could not execute updown script '%s'", script); + return; + } + + while (TRUE) + { + char resp[128]; + + if (fgets(resp, sizeof(resp), shell) == NULL) + { + if (ferror(shell)) + { + DBG1(DBG_CHD, "error reading output from updown script"); + return; + } + else + { + break; + } + } + else + { + char *e = resp + strlen(resp); + if (e > resp && e[-1] == '\n') + { /* trim trailing '\n' */ + e[-1] = '\0'; + } + DBG1(DBG_CHD, "updown: %s", resp); + } + } + pclose(shell); + } + iterator->destroy(iterator); +} + +/** + * Implements child_sa_t.set_state + */ +static void set_state(private_child_sa_t *this, child_sa_state_t state) +{ + this->state = state; + if (state == CHILD_INSTALLED) + { + updown(this, TRUE); + } +} + +/** + * Allocate SPI for a single proposal + */ +static status_t alloc_proposal(private_child_sa_t *this, proposal_t *proposal) +{ + protocol_id_t protocol = proposal->get_protocol(proposal); + + if (protocol == PROTO_AH) + { + /* get a new spi for AH, if not already done */ + if (this->alloc_ah_spi == 0) + { + if (charon->kernel_interface->get_spi( + charon->kernel_interface, + this->other.addr, this->me.addr, + PROTO_AH, this->reqid, + &this->alloc_ah_spi) != SUCCESS) + { + return FAILED; + } + } + proposal->set_spi(proposal, this->alloc_ah_spi); + } + if (protocol == PROTO_ESP) + { + /* get a new spi for ESP, if not already done */ + if (this->alloc_esp_spi == 0) + { + if (charon->kernel_interface->get_spi( + charon->kernel_interface, + this->other.addr, this->me.addr, + PROTO_ESP, this->reqid, + &this->alloc_esp_spi) != SUCCESS) + { + return FAILED; + } + } + proposal->set_spi(proposal, this->alloc_esp_spi); + } + return SUCCESS; +} + + +/** + * Implements child_sa_t.alloc + */ +static status_t alloc(private_child_sa_t *this, linked_list_t *proposals) +{ + iterator_t *iterator; + proposal_t *proposal; + + /* iterator through proposals to update spis */ + iterator = proposals->create_iterator(proposals, TRUE); + while(iterator->iterate(iterator, (void**)&proposal)) + { + if (alloc_proposal(this, proposal) != SUCCESS) + { + iterator->destroy(iterator); + return FAILED; + } + } + iterator->destroy(iterator); + return SUCCESS; +} + +static status_t install(private_child_sa_t *this, proposal_t *proposal, + mode_t mode, prf_plus_t *prf_plus, bool mine) +{ + u_int32_t spi, soft, hard;; + algorithm_t *enc_algo, *int_algo; + algorithm_t enc_algo_none = {ENCR_UNDEFINED, 0}; + algorithm_t int_algo_none = {AUTH_UNDEFINED, 0}; + host_t *src; + host_t *dst; + natt_conf_t *natt; + status_t status; + + this->protocol = proposal->get_protocol(proposal); + + /* now we have to decide which spi to use. Use self allocated, if "mine", + * or the one in the proposal, if not "mine" (others). Additionally, + * source and dest host switch depending on the role */ + if (mine) + { + /* if we have allocated SPIs for AH and ESP, we must delete the unused + * one. */ + if (this->protocol == PROTO_ESP) + { + this->me.spi = this->alloc_esp_spi; + if (this->alloc_ah_spi) + { + charon->kernel_interface->del_sa(charon->kernel_interface, this->me.addr, + this->alloc_ah_spi, PROTO_AH); + } + } + else + { + this->me.spi = this->alloc_ah_spi; + if (this->alloc_esp_spi) + { + charon->kernel_interface->del_sa(charon->kernel_interface, this->me.addr, + this->alloc_esp_spi, PROTO_ESP); + } + } + spi = this->me.spi; + dst = this->me.addr; + src = this->other.addr; + } + else + { + this->other.spi = proposal->get_spi(proposal); + spi = this->other.spi; + src = this->me.addr; + dst = this->other.addr; + } + + DBG2(DBG_CHD, "adding %s %N SA", mine ? "inbound" : "outbound", + protocol_id_names, this->protocol); + + /* select encryption algo */ + if (proposal->get_algorithm(proposal, ENCRYPTION_ALGORITHM, &enc_algo)) + { + DBG2(DBG_CHD, " using %N for encryption", + encryption_algorithm_names, enc_algo->algorithm); + } + else + { + enc_algo = &enc_algo_none; + } + + /* select integrity algo */ + if (proposal->get_algorithm(proposal, INTEGRITY_ALGORITHM, &int_algo)) + { + DBG2(DBG_CHD, " using %N for integrity", + integrity_algorithm_names, int_algo->algorithm); + } + else + { + int_algo = &int_algo_none; + } + + /* setup nat-t */ + if (this->use_natt) + { + natt = alloca(sizeof(natt_conf_t)); + natt->sport = src->get_port(src); + natt->dport = dst->get_port(dst); + } + else + { + natt = NULL; + } + + soft = this->policy->get_soft_lifetime(this->policy); + hard = this->policy->get_hard_lifetime(this->policy); + + /* send SA down to the kernel */ + DBG2(DBG_CHD, " SPI 0x%.8x, src %H dst %H", ntohl(spi), src, dst); + status = charon->kernel_interface->add_sa(charon->kernel_interface, + src, dst, spi, this->protocol, + this->reqid, mine ? soft : 0, + hard, enc_algo, int_algo, + prf_plus, natt, mode, mine); + + this->encryption = *enc_algo; + this->integrity = *int_algo; + this->install_time = time(NULL); + this->rekey_time = soft; + + return status; +} + +static status_t add(private_child_sa_t *this, proposal_t *proposal, + mode_t mode, prf_plus_t *prf_plus) +{ + u_int32_t outbound_spi, inbound_spi; + + /* backup outbound spi, as alloc overwrites it */ + outbound_spi = proposal->get_spi(proposal); + + /* get SPIs inbound SAs */ + if (alloc_proposal(this, proposal) != SUCCESS) + { + return FAILED; + } + inbound_spi = proposal->get_spi(proposal); + + /* install inbound SAs */ + if (install(this, proposal, mode, prf_plus, TRUE) != SUCCESS) + { + return FAILED; + } + + /* install outbound SAs, restore spi*/ + proposal->set_spi(proposal, outbound_spi); + if (install(this, proposal, mode, prf_plus, FALSE) != SUCCESS) + { + return FAILED; + } + proposal->set_spi(proposal, inbound_spi); + + return SUCCESS; +} + +static status_t update(private_child_sa_t *this, proposal_t *proposal, + mode_t mode, prf_plus_t *prf_plus) +{ + u_int32_t inbound_spi; + + /* backup received spi, as install() overwrites it */ + inbound_spi = proposal->get_spi(proposal); + + /* install outbound SAs */ + if (install(this, proposal, mode, prf_plus, FALSE) != SUCCESS) + { + return FAILED; + } + + /* restore spi */ + proposal->set_spi(proposal, inbound_spi); + /* install inbound SAs */ + if (install(this, proposal, mode, prf_plus, TRUE) != SUCCESS) + { + return FAILED; + } + + return SUCCESS; +} + +static status_t add_policies(private_child_sa_t *this, + linked_list_t *my_ts_list, + linked_list_t *other_ts_list, mode_t mode) +{ + iterator_t *my_iter, *other_iter; + traffic_selector_t *my_ts, *other_ts; + /* use low prio for ROUTED policies */ + bool high_prio = (this->state != CHILD_CREATED); + + /* iterate over both lists */ + my_iter = my_ts_list->create_iterator(my_ts_list, TRUE); + other_iter = other_ts_list->create_iterator(other_ts_list, TRUE); + while (my_iter->iterate(my_iter, (void**)&my_ts)) + { + other_iter->reset(other_iter); + while (other_iter->iterate(other_iter, (void**)&other_ts)) + { + /* set up policies for every entry in my_ts_list to every entry in other_ts_list */ + status_t status; + sa_policy_t *policy; + + if (my_ts->get_type(my_ts) != other_ts->get_type(other_ts)) + { + DBG2(DBG_CHD, + "CHILD_SA policy uses two different IP families, ignored"); + continue; + } + + /* only set up policies if protocol matches, or if one is zero (any) */ + if (my_ts->get_protocol(my_ts) != other_ts->get_protocol(other_ts) && + my_ts->get_protocol(my_ts) && other_ts->get_protocol(other_ts)) + { + DBG2(DBG_CHD, + "CHILD_SA policy uses two different protocols, ignored"); + continue; + } + + /* install 3 policies: out, in and forward */ + status = charon->kernel_interface->add_policy(charon->kernel_interface, + this->me.addr, this->other.addr, my_ts, other_ts, POLICY_OUT, + this->protocol, this->reqid, high_prio, mode, FALSE); + + status |= charon->kernel_interface->add_policy(charon->kernel_interface, + this->other.addr, this->me.addr, other_ts, my_ts, POLICY_IN, + this->protocol, this->reqid, high_prio, mode, FALSE); + + status |= charon->kernel_interface->add_policy(charon->kernel_interface, + this->other.addr, this->me.addr, other_ts, my_ts, POLICY_FWD, + this->protocol, this->reqid, high_prio, mode, FALSE); + + if (status != SUCCESS) + { + my_iter->destroy(my_iter); + other_iter->destroy(other_iter); + return status; + } + + /* store policy to delete/update them later */ + policy = malloc_thing(sa_policy_t); + policy->my_ts = my_ts->clone(my_ts); + policy->other_ts = other_ts->clone(other_ts); + this->policies->insert_last(this->policies, (void*)policy); + /* add to separate list to query them via get_*_traffic_selectors() */ + this->my_ts->insert_last(this->my_ts, (void*)policy->my_ts); + this->other_ts->insert_last(this->other_ts, (void*)policy->other_ts); + } + } + my_iter->destroy(my_iter); + other_iter->destroy(other_iter); + + /* switch to routed state if no SAD entry set up */ + if (this->state == CHILD_CREATED) + { + this->state = CHILD_ROUTED; + } + /* needed to update hosts */ + this->mode = mode; + return SUCCESS; +} + +/** + * Implementation of child_sa_t.get_my_traffic_selectors. + */ +static linked_list_t *get_my_traffic_selectors(private_child_sa_t *this) +{ + return this->my_ts; +} + +/** + * Implementation of child_sa_t.get_my_traffic_selectors. + */ +static linked_list_t *get_other_traffic_selectors(private_child_sa_t *this) +{ + return this->other_ts; +} + +/** + * Implementation of child_sa_t.get_use_time + */ +static status_t get_use_time(private_child_sa_t *this, bool inbound, time_t *use_time) +{ + iterator_t *iterator; + sa_policy_t *policy; + status_t status = FAILED; + + *use_time = UNDEFINED_TIME; + + iterator = this->policies->create_iterator(this->policies, TRUE); + while (iterator->iterate(iterator, (void**)&policy)) + { + if (inbound) + { + time_t in = UNDEFINED_TIME, fwd = UNDEFINED_TIME; + + status = charon->kernel_interface->query_policy( + charon->kernel_interface, + policy->other_ts, policy->my_ts, + POLICY_IN, (u_int32_t*)&in); + status |= charon->kernel_interface->query_policy( + charon->kernel_interface, + policy->other_ts, policy->my_ts, + POLICY_FWD, (u_int32_t*)&fwd); + *use_time = max(in, fwd); + } + else + { + status = charon->kernel_interface->query_policy( + charon->kernel_interface, + policy->my_ts, policy->other_ts, + POLICY_OUT, (u_int32_t*)use_time); + } + } + iterator->destroy(iterator); + return status; +} + +/** + * output handler in printf() + */ +static int print(FILE *stream, const struct printf_info *info, + const void *const *args) +{ + private_child_sa_t *this = *((private_child_sa_t**)(args[0])); + iterator_t *iterator; + sa_policy_t *policy; + u_int32_t now, rekeying; + u_int32_t use, use_in, use_fwd; + status_t status; + size_t written = 0; + + if (this == NULL) + { + return fprintf(stream, "(null)"); + } + + now = time(NULL); + + written += fprintf(stream, "%12s{%d}: %N, %N", + this->policy->get_name(this->policy), this->reqid, + child_sa_state_names, this->state, + mode_names, this->mode); + + if (this->state == CHILD_INSTALLED) + { + written += fprintf(stream, ", %N SPIs: 0x%0x_i 0x%0x_o", + protocol_id_names, this->protocol, + htonl(this->me.spi), htonl(this->other.spi)); + + if (info->alt) + { + written += fprintf(stream, "\n%12s{%d}: ", + this->policy->get_name(this->policy), + this->reqid); + + if (this->protocol == PROTO_ESP) + { + written += fprintf(stream, "%N", encryption_algorithm_names, + this->encryption.algorithm); + + if (this->encryption.key_size) + { + written += fprintf(stream, "-%d", this->encryption.key_size); + } + written += fprintf(stream, "/"); + } + + written += fprintf(stream, "%N", integrity_algorithm_names, + this->integrity.algorithm); + if (this->integrity.key_size) + { + written += fprintf(stream, "-%d", this->integrity.key_size); + } + written += fprintf(stream, ", rekeying "); + + /* calculate rekey times */ + if (this->rekey_time) + { + rekeying = this->install_time + this->rekey_time - now; + written += fprintf(stream, "in %ds", rekeying); + } + else + { + written += fprintf(stream, "disabled"); + } + } + } + iterator = this->policies->create_iterator(this->policies, TRUE); + while (iterator->iterate(iterator, (void**)&policy)) + { + written += fprintf(stream, "\n%12s{%d}: %R===%R, last use: ", + this->policy->get_name(this->policy), this->reqid, + policy->my_ts, policy->other_ts); + + /* query time of last policy use */ + + /* inbound: POLICY_IN or POLICY_FWD */ + status = charon->kernel_interface->query_policy(charon->kernel_interface, + policy->other_ts, policy->my_ts, POLICY_IN, &use_in); + use_in = (status == SUCCESS)? use_in : 0; + status = charon->kernel_interface->query_policy(charon->kernel_interface, + policy->other_ts, policy->my_ts, POLICY_FWD, &use_fwd); + use_fwd = (status == SUCCESS)? use_fwd : 0; + use = max(use_in, use_fwd); + if (use) + { + written += fprintf(stream, "%ds_i ", now - use); + } + else + { + written += fprintf(stream, "no_i "); + } + + /* outbound: POLICY_OUT */ + status = charon->kernel_interface->query_policy(charon->kernel_interface, + policy->my_ts, policy->other_ts, POLICY_OUT, &use); + if (status == SUCCESS && use) + { + written += fprintf(stream, "%ds_o ", now - use); + } + else + { + written += fprintf(stream, "no_o "); + } + } + iterator->destroy(iterator); + return written; +} + +/** + * register printf() handlers + */ +static void __attribute__ ((constructor))print_register() +{ + register_printf_function(PRINTF_CHILD_SA, print, arginfo_ptr); +} + +/** + * Update the host adress/port of a SA + */ +static status_t update_sa_hosts(private_child_sa_t *this, host_t *new_me, host_t *new_other, + int my_changes, int other_changes, bool mine) +{ + host_t *src, *dst, *new_src, *new_dst; + int src_changes, dst_changes; + status_t status; + u_int32_t spi; + + if (mine) + { + src = this->other.addr; + dst = this->me.addr; + new_src = new_other; + new_dst = new_me; + src_changes = other_changes; + dst_changes = my_changes; + spi = this->other.spi; + } + else + { + src = this->me.addr; + dst = this->other.addr; + new_src = new_me; + new_dst = new_other; + src_changes = my_changes; + dst_changes = other_changes; + spi = this->me.spi; + } + + DBG2(DBG_CHD, "updating %N SA 0x%x, from %#H..#H to %#H..%#H", + protocol_id_names, this->protocol, ntohl(spi), src, dst, new_src, new_dst); + + status = charon->kernel_interface->update_sa(charon->kernel_interface, + dst, spi, this->protocol, + new_src, new_dst, + src_changes, dst_changes); + + if (status != SUCCESS) + { + return FAILED; + } + return SUCCESS; +} + +/** + * Update the host adress/port of a policy + */ +static status_t update_policy_hosts(private_child_sa_t *this, host_t *new_me, host_t *new_other) +{ + iterator_t *iterator; + sa_policy_t *policy; + status_t status; + /* we always use high priorities, as hosts getting updated are INSTALLED */ + + iterator = this->policies->create_iterator(this->policies, TRUE); + while (iterator->iterate(iterator, (void**)&policy)) + { + status = charon->kernel_interface->add_policy( + charon->kernel_interface, + new_me, new_other, + policy->my_ts, policy->other_ts, + POLICY_OUT, this->protocol, this->reqid, TRUE, this->mode, TRUE); + + status |= charon->kernel_interface->add_policy( + charon->kernel_interface, + new_other, new_me, + policy->other_ts, policy->my_ts, + POLICY_IN, this->protocol, this->reqid, TRUE, this->mode, TRUE); + + status |= charon->kernel_interface->add_policy( + charon->kernel_interface, + new_other, new_me, + policy->other_ts, policy->my_ts, + POLICY_FWD, this->protocol, this->reqid, TRUE, this->mode, TRUE); + + if (status != SUCCESS) + { + iterator->destroy(iterator); + return FAILED; + } + } + iterator->destroy(iterator); + + return SUCCESS; +} + +/** + * Implementation of child_sa_t.update_hosts. + */ +static status_t update_hosts(private_child_sa_t *this, host_t *new_me, host_t *new_other, + host_diff_t my_changes, host_diff_t other_changes) +{ + if (!my_changes && !other_changes) + { + return SUCCESS; + } + + /* update our (initator) SAs */ + if (update_sa_hosts(this, new_me, new_other, my_changes, other_changes, TRUE) != SUCCESS) + { + return FAILED; + } + + /* update his (responder) SAs */ + if (update_sa_hosts(this, new_me, new_other, my_changes, other_changes, FALSE) != SUCCESS) + { + return FAILED; + } + + /* update policies */ + if (my_changes & HOST_DIFF_ADDR || other_changes & HOST_DIFF_ADDR) + { + if (update_policy_hosts(this, new_me, new_other) != SUCCESS) + { + return FAILED; + } + } + + /* update hosts */ + if (my_changes) + { + this->me.addr->destroy(this->me.addr); + this->me.addr = new_me->clone(new_me); + } + + if (other_changes) + { + this->other.addr->destroy(this->other.addr); + this->other.addr = new_other->clone(new_other); + } + + return SUCCESS; +} + +/** + * Implementation of child_sa_t.set_virtual_ip. + */ +static void set_virtual_ip(private_child_sa_t *this, host_t *ip) +{ + this->virtual_ip = ip->clone(ip); +} + +/** + * Implementation of child_sa_t.destroy. + */ +static void destroy(private_child_sa_t *this) +{ + sa_policy_t *policy; + + if (this->state == CHILD_DELETING || this->state == CHILD_INSTALLED) + { + updown(this, FALSE); + } + + /* delete SAs in the kernel, if they are set up */ + if (this->me.spi) + { + charon->kernel_interface->del_sa(charon->kernel_interface, + this->me.addr, this->me.spi, this->protocol); + } + if (this->alloc_esp_spi && this->alloc_esp_spi != this->me.spi) + { + charon->kernel_interface->del_sa(charon->kernel_interface, + this->me.addr, this->alloc_esp_spi, PROTO_ESP); + } + if (this->alloc_ah_spi && this->alloc_ah_spi != this->me.spi) + { + charon->kernel_interface->del_sa(charon->kernel_interface, + this->me.addr, this->alloc_ah_spi, PROTO_AH); + } + if (this->other.spi) + { + charon->kernel_interface->del_sa(charon->kernel_interface, + this->other.addr, this->other.spi, this->protocol); + } + + /* delete all policies in the kernel */ + while (this->policies->remove_last(this->policies, (void**)&policy) == SUCCESS) + { + /* let rekeyed policies, as they are used by another child_sa */ + charon->kernel_interface->del_policy(charon->kernel_interface, + policy->my_ts, policy->other_ts, + POLICY_OUT); + + charon->kernel_interface->del_policy(charon->kernel_interface, + policy->other_ts, policy->my_ts, + POLICY_IN); + + charon->kernel_interface->del_policy(charon->kernel_interface, + policy->other_ts, policy->my_ts, + POLICY_FWD); + policy->my_ts->destroy(policy->my_ts); + policy->other_ts->destroy(policy->other_ts); + free(policy); + } + this->policies->destroy(this->policies); + + this->my_ts->destroy(this->my_ts); + this->other_ts->destroy(this->other_ts); + this->me.addr->destroy(this->me.addr); + this->other.addr->destroy(this->other.addr); + this->me.id->destroy(this->me.id); + this->other.id->destroy(this->other.id); + this->policy->destroy(this->policy); + DESTROY_IF(this->virtual_ip); + free(this); +} + +/* + * Described in header. + */ +child_sa_t * child_sa_create(host_t *me, host_t* other, + identification_t *my_id, identification_t *other_id, + policy_t *policy, u_int32_t rekey, bool use_natt) +{ + 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_spi = (u_int32_t(*)(child_sa_t*, bool))get_spi; + this->public.get_protocol = (protocol_id_t(*)(child_sa_t*))get_protocol; + this->public.alloc = (status_t(*)(child_sa_t*,linked_list_t*))alloc; + this->public.add = (status_t(*)(child_sa_t*,proposal_t*,mode_t,prf_plus_t*))add; + this->public.update = (status_t(*)(child_sa_t*,proposal_t*,mode_t,prf_plus_t*))update; + this->public.update_hosts = (status_t (*)(child_sa_t*,host_t*,host_t*,host_diff_t,host_diff_t))update_hosts; + this->public.add_policies = (status_t (*)(child_sa_t*, linked_list_t*,linked_list_t*,mode_t))add_policies; + this->public.get_my_traffic_selectors = (linked_list_t*(*)(child_sa_t*))get_my_traffic_selectors; + this->public.get_other_traffic_selectors = (linked_list_t*(*)(child_sa_t*))get_other_traffic_selectors; + this->public.get_use_time = (status_t (*)(child_sa_t*,bool,time_t*))get_use_time; + this->public.set_state = (void(*)(child_sa_t*,child_sa_state_t))set_state; + this->public.get_state = (child_sa_state_t(*)(child_sa_t*))get_state; + this->public.get_policy = (policy_t*(*)(child_sa_t*))get_policy; + this->public.set_virtual_ip = (void(*)(child_sa_t*,host_t*))set_virtual_ip; + this->public.destroy = (void(*)(child_sa_t*))destroy; + + /* private data */ + this->me.addr = me->clone(me); + this->other.addr = other->clone(other); + this->me.id = my_id->clone(my_id); + this->other.id = other_id->clone(other_id); + this->me.spi = 0; + this->other.spi = 0; + this->alloc_ah_spi = 0; + this->alloc_esp_spi = 0; + this->use_natt = use_natt; + this->state = CHILD_CREATED; + /* reuse old reqid if we are rekeying an existing CHILD_SA */ + this->reqid = rekey ? rekey : ++reqid; + this->encryption.algorithm = ENCR_UNDEFINED; + this->encryption.key_size = 0; + this->integrity.algorithm = AUTH_UNDEFINED; + this->encryption.key_size = 0; + this->policies = linked_list_create(); + this->my_ts = linked_list_create(); + this->other_ts = linked_list_create(); + this->protocol = PROTO_NONE; + this->mode = MODE_TUNNEL; + this->virtual_ip = NULL; + this->policy = policy; + policy->get_ref(policy); + + return &this->public; +} diff --git a/src/charon/sa/child_sa.h b/src/charon/sa/child_sa.h new file mode 100644 index 000000000..216e56659 --- /dev/null +++ b/src/charon/sa/child_sa.h @@ -0,0 +1,298 @@ +/** + * @file child_sa.h + * + * @brief Interface of child_sa_t. + * + */ + +/* + * 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. + */ + + +#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/policies/policy.h> + +/** + * Where we should start with reqid enumeration + */ +#define REQID_START 2000000000 + +/** + * @brief 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, + + /** + * Installed an in-use CHILD_SA + */ + CHILD_INSTALLED, + + /** + * CHILD_SA which is rekeying + */ + CHILD_REKEYING, + + /** + * CHILD_SA in progress of delete + */ + CHILD_DELETING, +}; + +/** + * enum strings for child_sa_state_t. + */ +extern enum_name_t *child_sa_state_names; + +/** + * @brief 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 proposal via child_sa_t.alloc + * - A send the updated proposal to B + * - B selects a suitable proposal + * - B calls child_sa_t.add to add and update the selected proposal + * - B sends the updated proposal to A + * - A calls child_sa_t.update to update the already allocated SPIs with the chosen proposal + * + * Once SAs are set up, policies can be added using add_policies. + * + * + * @b Constructors: + * - child_sa_create() + * + * @ingroup sa + */ +struct child_sa_t { + + /** + * @brief Get the name of the policy this CHILD_SA uses. + * + * @param this calling object + * @return name + */ + char* (*get_name) (child_sa_t *this); + + /** + * @brief Get the reqid of the CHILD SA. + * + * Every CHILD_SA has a reqid. The kernel uses this ID to + * identify it. + * + * @param this calling object + * @return reqid of the CHILD SA + */ + u_int32_t (*get_reqid)(child_sa_t *this); + + /** + * @brief 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 this calling object + * @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); + + /** + * @brief Get the protocol which this CHILD_SA uses to protect traffic. + * + * @param this calling object + * @return AH | ESP + */ + protocol_id_t (*get_protocol) (child_sa_t *this); + + /** + * @brief Allocate SPIs for given proposals. + * + * Since the kernel manages SPIs for us, we need + * to allocate them. If a proposal contains more + * than one protocol, for each protocol an SPI is + * allocated. SPIs are stored internally and written + * back to the proposal. + * + * @param this calling object + * @param proposals list of proposals for which SPIs are allocated + */ + status_t (*alloc)(child_sa_t *this, linked_list_t* proposals); + + /** + * @brief Install the kernel SAs for a proposal, without previous SPI allocation. + * + * @param this calling object + * @param proposal proposal for which SPIs are allocated + * @param mode mode for the CHILD_SA + * @param prf_plus key material to use for key derivation + * @return SUCCESS or FAILED + */ + status_t (*add)(child_sa_t *this, proposal_t *proposal, mode_t mode, + prf_plus_t *prf_plus); + + /** + * @brief Install the kernel SAs for a proposal, after SPIs have been allocated. + * + * Updates an SA, for which SPIs are already allocated via alloc(). + * + * @param this calling object + * @param proposal proposal for which SPIs are allocated + * @param mode mode for the CHILD_SA + * @param prf_plus key material to use for key derivation + * @return SUCCESS or FAILED + */ + status_t (*update)(child_sa_t *this, proposal_t *proposal, mode_t mode, + prf_plus_t *prf_plus); + + /** + * @brief Update the hosts in the kernel SAs and policies + * + * @warning only call this after update() has been called. + * + * @param this calling object + * @param new_me the new local host + * @param new_other the new remote host + * @param my_diff differences to apply for me + * @param other_diff differences to apply for other + * @return SUCCESS or FAILED + */ + status_t (*update_hosts)(child_sa_t *this, host_t *new_me, host_t *new_other, + host_diff_t my_diff, host_diff_t other_diff); + + /** + * @brief Install the policies using some traffic selectors. + * + * Supplied lists of traffic_selector_t's specify the policies + * to use for this child sa. + * + * @param this calling object + * @param my_ts traffic selectors for local site + * @param other_ts traffic selectors for remote site + * @param mode mode for the SA: tunnel/transport + * @return SUCCESS or FAILED + */ + status_t (*add_policies)(child_sa_t *this, linked_list_t *my_ts_list, + linked_list_t *other_ts_list, mode_t mode); + + /** + * @brief Get the traffic selectors of added policies of local host. + * + * @param this calling object + * @return list of traffic selectors + */ + linked_list_t* (*get_my_traffic_selectors) (child_sa_t *this); + + /** + * @brief Get the traffic selectors of added policies of remote host. + * + * @param this calling object + * @return list of traffic selectors + */ + linked_list_t* (*get_other_traffic_selectors) (child_sa_t *this); + + /** + * @brief Get the time of this child_sa_t's last use (i.e. last use of any of its policies) + * + * @param this calling object + * @param inbound query for in- or outbound usage + * @param use_time the time + * @return SUCCESS or FAILED + */ + status_t (*get_use_time) (child_sa_t *this, bool inbound, time_t *use_time); + + /** + * @brief Get the state of the CHILD_SA. + * + * @param this calling object + */ + child_sa_state_t (*get_state) (child_sa_t *this); + + /** + * @brief Set the state of the CHILD_SA. + * + * @param this calling object + */ + void (*set_state) (child_sa_t *this, child_sa_state_t state); + + /** + * @brief Get the policy used to set up this child sa. + * + * @param this calling object + * @return policy + */ + policy_t* (*get_policy) (child_sa_t *this); + + /** + * @brief Set the virtual IP used received from IRAS. + * + * To allow proper setup of firewall rules, the virtual IP is required + * for filtering. + * + * @param this calling object + * @param ip own virtual IP + */ + void (*set_virtual_ip) (child_sa_t *this, host_t *ip); + + /** + * @brief Destroys a child_sa. + * + * @param this calling object + */ + void (*destroy) (child_sa_t *this); +}; + +/** + * @brief Constructor to create a new child_sa_t. + * + * @param me own address + * @param other remote address + * @param my_id id of own peer + * @param other_id id of remote peer + * @param policy policy this CHILD_SA instantiates + * @param reqid reqid of old CHILD_SA when rekeying, 0 otherwise + * @param use_natt TRUE if NAT traversal is used + * @return child_sa_t object + * + * @ingroup sa + */ +child_sa_t * child_sa_create(host_t *me, host_t *other, + identification_t *my_id, identification_t* other_id, + policy_t *policy, u_int32_t reqid, bool use_natt); + +#endif /*CHILD_SA_H_*/ diff --git a/src/charon/sa/ike_sa.c b/src/charon/sa/ike_sa.c new file mode 100644 index 000000000..68aba3064 --- /dev/null +++ b/src/charon/sa/ike_sa.c @@ -0,0 +1,2032 @@ +/** + * @file ike_sa.c + * + * @brief Implementation of ike_sa_t. + * + */ + +/* + * Copyright (C) 2006 Tobias Brunner, Daniel Roethlisberger + * 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 <sys/time.h> +#include <string.h> +#include <printf.h> +#include <sys/stat.h> + +#include "ike_sa.h" + +#include <library.h> +#include <daemon.h> +#include <utils/linked_list.h> +#include <utils/lexparser.h> +#include <crypto/diffie_hellman.h> +#include <crypto/prf_plus.h> +#include <crypto/crypters/crypter.h> +#include <crypto/hashers/hasher.h> +#include <encoding/payloads/sa_payload.h> +#include <encoding/payloads/nonce_payload.h> +#include <encoding/payloads/ke_payload.h> +#include <encoding/payloads/delete_payload.h> +#include <encoding/payloads/transform_substructure.h> +#include <encoding/payloads/transform_attribute.h> +#include <encoding/payloads/ts_payload.h> +#include <sa/task_manager.h> +#include <sa/tasks/ike_init.h> +#include <sa/tasks/ike_natd.h> +#include <sa/tasks/ike_auth.h> +#include <sa/tasks/ike_config.h> +#include <sa/tasks/ike_cert.h> +#include <sa/tasks/ike_rekey.h> +#include <sa/tasks/ike_delete.h> +#include <sa/tasks/ike_dpd.h> +#include <sa/tasks/child_create.h> +#include <sa/tasks/child_delete.h> +#include <sa/tasks/child_rekey.h> +#include <queues/jobs/retransmit_job.h> +#include <queues/jobs/delete_ike_sa_job.h> +#include <queues/jobs/send_dpd_job.h> +#include <queues/jobs/send_keepalive_job.h> +#include <queues/jobs/rekey_ike_sa_job.h> +#include <queues/jobs/route_job.h> +#include <queues/jobs/initiate_job.h> + + +#ifndef RESOLV_CONF +#define RESOLV_CONF "/etc/resolv.conf" +#endif + +ENUM(ike_sa_state_names, IKE_CREATED, IKE_DELETING, + "CREATED", + "CONNECTING", + "ESTABLISHED", + "REKEYING", + "DELETING", +); + +typedef struct private_ike_sa_t private_ike_sa_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; + + /** + * connection used to establish this IKE_SA. + */ + connection_t *connection; + + /** + * Peer and authentication information to establish IKE_SA. + */ + policy_t *policy; + + /** + * 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; + + /** + * Identification used for us + */ + identification_t *my_id; + + /** + * Identification used for other + */ + identification_t *other_id; + + /** + * Linked List containing the child sa's of the current IKE_SA. + */ + linked_list_t *child_sas; + + /** + * crypter for inbound traffic + */ + crypter_t *crypter_in; + + /** + * crypter for outbound traffic + */ + crypter_t *crypter_out; + + /** + * Signer for inbound traffic + */ + signer_t *signer_in; + + /** + * Signer for outbound traffic + */ + signer_t *signer_out; + + /** + * Multi purpose prf, set key, use it, forget it + */ + prf_t *prf; + + /** + * Prf function for derivating keymat child SAs + */ + prf_t *child_prf; + + /** + * PRF to build outging authentication data + */ + prf_t *auth_build; + + /** + * PRF to verify incoming authentication data + */ + prf_t *auth_verify; + + /** + * NAT status of local host. + */ + bool nat_here; + + /** + * NAT status of remote host. + */ + bool nat_there; + + /** + * 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 DNS servers installed by us + */ + linked_list_t *dns_servers; + + /** + * Timestamps for this IKE_SA + */ + struct { + /** last IKE message received */ + u_int32_t inbound; + /** last IKE message sent */ + u_int32_t outbound; + /** when IKE_SA became established */ + u_int32_t established; + /** when IKE_SA gets rekeyed */ + u_int32_t rekey; + /** when IKE_SA gets deleted */ + u_int32_t delete; + } time; + + /** + * how many times we have retried so far (keyingtries) + */ + u_int32_t keyingtry; +}; + +/** + * get the time of the latest traffic processed by the kernel + */ +static time_t get_use_time(private_ike_sa_t* this, bool inbound) +{ + iterator_t *iterator; + child_sa_t *child_sa; + time_t latest = 0, use_time; + + iterator = this->child_sas->create_iterator(this->child_sas, TRUE); + while (iterator->iterate(iterator, (void**)&child_sa)) + { + if (child_sa->get_use_time(child_sa, inbound, &use_time) == SUCCESS) + { + latest = max(latest, use_time); + } + } + iterator->destroy(iterator); + + if (inbound) + { + return max(this->time.inbound, latest); + } + else + { + return max(this->time.outbound, latest); + } +} + +/** + * 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->connection) + { + return this->connection->get_name(this->connection); + } + return "(unnamed)"; +} + +/** + * Implementation of ike_sa_t.get_connection + */ +static connection_t* get_connection(private_ike_sa_t *this) +{ + return this->connection; +} + +/** + * Implementation of ike_sa_t.set_connection + */ +static void set_connection(private_ike_sa_t *this, connection_t *connection) +{ + this->connection = connection; + connection->get_ref(connection); +} + +/** + * Implementation of ike_sa_t.get_policy + */ +static policy_t *get_policy(private_ike_sa_t *this) +{ + return this->policy; +} + +/** + * Implementation of ike_sa_t.set_policy + */ +static void set_policy(private_ike_sa_t *this, policy_t *policy) +{ + policy->get_ref(policy); + this->policy = policy; +} + +/** + * 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.send_dpd + */ +static status_t send_dpd(private_ike_sa_t *this) +{ + send_dpd_job_t *job; + time_t diff, delay; + + delay = this->connection->get_dpd_delay(this->connection); + + 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(NULL); + diff = now - last_in; + if (diff >= delay) + { + /* to long ago, initiate dead peer detection */ + task_t *task; + + 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 = send_dpd_job_create(this->ike_sa_id); + charon->event_queue->add_relative(charon->event_queue, (job_t*)job, + (delay - diff) * 1000); + return SUCCESS; +} + +/** + * 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, interval; + + last_out = get_use_time(this, FALSE); + now = time(NULL); + + diff = now - last_out; + interval = charon->configuration->get_keepalive_interval(charon->configuration); + + if (diff >= 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); + charon->sender->send(charon->sender, packet); + DBG1(DBG_IKE, "sending keep alive"); + diff = 0; + } + job = send_keepalive_job_create(this->ike_sa_id); + charon->event_queue->add_relative(charon->event_queue, (job_t*)job, + (interval - diff) * 1000); +} + +/** + * 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) +{ + DBG1(DBG_IKE, "IKE_SA state change: %N => %N", + ike_sa_state_names, this->state, + ike_sa_state_names, state); + + switch (state) + { + case IKE_ESTABLISHED: + { + if (this->state == IKE_CONNECTING) + { + job_t *job; + u_int32_t now = time(NULL); + u_int32_t soft, hard; + bool reauth; + + this->time.established = now; + /* start DPD checks */ + send_dpd(this); + + /* schedule rekeying/reauthentication */ + soft = this->connection->get_soft_lifetime(this->connection); + hard = this->connection->get_hard_lifetime(this->connection); + reauth = this->connection->get_reauth(this->connection); + DBG1(DBG_IKE, "scheduling %s in %ds, maximum lifetime %ds", + reauth ? "reauthentication": "rekeying", soft, hard); + + if (soft) + { + this->time.rekey = now + soft; + job = (job_t*)rekey_ike_sa_job_create(this->ike_sa_id, reauth); + charon->event_queue->add_relative(charon->event_queue, job, + soft * 1000); + } + + if (hard) + { + this->time.delete = now + hard; + job = (job_t*)delete_ike_sa_job_create(this->ike_sa_id, TRUE); + charon->event_queue->add_relative(charon->event_queue, job, + hard * 1000); + } + } + 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->event_queue->add_relative(charon->event_queue, job, + charon->configuration->get_half_open_ike_sa_timeout( + charon->configuration)); + break; + } + default: + break; + } + + 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); +} + +/** + * Update connection host, as addresses may change (NAT) + */ +static void update_hosts(private_ike_sa_t *this, host_t *me, host_t *other) +{ + iterator_t *iterator = NULL; + child_sa_t *child_sa = NULL; + host_diff_t my_diff, other_diff; + + if (this->my_host->is_anyaddr(this->my_host) || + this->other_host->is_anyaddr(this->other_host)) + { + /* on first received message */ + this->my_host->destroy(this->my_host); + this->my_host = me->clone(me); + this->other_host->destroy(this->other_host); + this->other_host = other->clone(other); + return; + } + + my_diff = me->get_differences(me, this->my_host); + other_diff = other->get_differences(other, this->other_host); + + if (!my_diff && !other_diff) + { + return; + } + + if (my_diff) + { + this->my_host->destroy(this->my_host); + this->my_host = me->clone(me); + } + + if (!this->nat_here) + { + /* update without restrictions if we are not NATted */ + if (other_diff) + { + this->other_host->destroy(this->other_host); + this->other_host = other->clone(other); + } + } + else + { + /* if we are natted, only port may change */ + if (other_diff & HOST_DIFF_ADDR) + { + return; + } + else if (other_diff & HOST_DIFF_PORT) + { + this->other_host->set_port(this->other_host, other->get_port(other)); + } + } + iterator = this->child_sas->create_iterator(this->child_sas, TRUE); + while (iterator->iterate(iterator, (void**)&child_sa)) + { + child_sa->update_hosts(child_sa, this->my_host, this->other_host, + my_diff, other_diff); + } + 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->time.outbound = time(NULL); + message->set_ike_sa_id(message, this->ike_sa_id); + message->set_destination(message, this->other_host->clone(this->other_host)); + message->set_source(message, this->my_host->clone(this->my_host)); + return message->generate(message, this->crypter_out, this->signer_out, 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); + } + if (generate_message(this, response, &packet) == SUCCESS) + { + charon->sender->send(charon->sender, packet); + } + response->destroy(response); +} + +/** + * 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; + + is_request = message->get_request(message); + + status = message->parse_body(message, this->crypter_in, this->signer_in); + if (status != SUCCESS) + { + + if (is_request) + { + switch (status) + { + case NOT_SUPPORTED: + DBG1(DBG_IKE, "ciritcal 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)); + return status; + } + 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 connection */ + if (this->connection == NULL) + { + job_t *job; + this->connection = charon->connections->get_connection_by_hosts( + charon->connections, me, other); + if (this->connection == NULL) + { + /* no connection found for these hosts, destroy */ + DBG1(DBG_IKE, "no connection 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->event_queue->add_relative(charon->event_queue, job, + charon->configuration->get_half_open_ike_sa_timeout( + charon->configuration)); + } + + /* check if message is trustworthy, and update connection information */ + if (this->state == IKE_CREATED || + message->get_exchange_type(message) != IKE_SA_INIT) + { + update_hosts(this, me, other); + this->time.inbound = time(NULL); + } + return this->task_manager->process_message(this->task_manager, message); + } +} + +/** + * apply the connection/policy information to this IKE_SA + */ +static void apply_config(private_ike_sa_t *this, + connection_t *connection, policy_t *policy) +{ + host_t *me, *other; + identification_t *my_id, *other_id; + + if (this->connection == NULL && this->policy == NULL) + { + this->connection = connection; + connection->get_ref(connection); + this->policy = policy; + policy->get_ref(policy); + + me = connection->get_my_host(connection); + other = connection->get_other_host(connection); + my_id = policy->get_my_id(policy); + other_id = policy->get_other_id(policy); + set_my_host(this, me->clone(me)); + set_other_host(this, other->clone(other)); + DESTROY_IF(this->my_id); + DESTROY_IF(this->other_id); + this->my_id = my_id->clone(my_id); + this->other_id = other_id->clone(other_id); + } +} + +/** + * Implementation of ike_sa_t.initiate. + */ +static status_t initiate(private_ike_sa_t *this, + connection_t *connection, policy_t *policy) +{ + task_t *task; + + if (this->state == IKE_CREATED) + { + /* if we aren't established/establishing, do so */ + apply_config(this, connection, policy); + + if (this->other_host->is_anyaddr(this->other_host)) + { + SIG(IKE_UP_START, "initiating IKE_SA"); + SIG(IKE_UP_FAILED, "unable to initiate to %%any"); + return DESTROY_ME; + } + + 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_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_config_create(&this->public, policy); + this->task_manager->queue_task(this->task_manager, task); + } + + task = (task_t*)child_create_create(&this->public, policy); + this->task_manager->queue_task(this->task_manager, task); + + return this->task_manager->initiate(this->task_manager); +} + +/** + * Implementation of ike_sa_t.acquire. + */ +static status_t acquire(private_ike_sa_t *this, u_int32_t reqid) +{ + policy_t *policy; + iterator_t *iterator; + child_sa_t *current, *child_sa = NULL; + task_t *task; + child_create_t *child_create; + + if (this->state == IKE_DELETING) + { + SIG(CHILD_UP_START, "acquiring CHILD_SA on kernel request"); + SIG(CHILD_UP_FAILED, "acquiring CHILD_SA (reqid %d) failed: " + "IKE_SA is deleting", reqid); + return FAILED; + } + + /* find CHILD_SA */ + iterator = this->child_sas->create_iterator(this->child_sas, TRUE); + while (iterator->iterate(iterator, (void**)¤t)) + { + if (current->get_reqid(current) == reqid) + { + child_sa = current; + break; + } + } + iterator->destroy(iterator); + if (!child_sa) + { + SIG(CHILD_UP_START, "acquiring CHILD_SA on kernel request"); + SIG(CHILD_UP_FAILED, "acquiring CHILD_SA (reqid %d) failed: " + "CHILD_SA not found", reqid); + return FAILED; + } + + policy = child_sa->get_policy(child_sa); + + if (this->state == IKE_CREATED) + { + 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_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_config_create(&this->public, policy); + this->task_manager->queue_task(this->task_manager, task); + } + + child_create = child_create_create(&this->public, policy); + child_create->use_reqid(child_create, reqid); + this->task_manager->queue_task(this->task_manager, (task_t*)child_create); + + return this->task_manager->initiate(this->task_manager); +} + +/** + * compare two lists of traffic selectors for equality + */ +static bool ts_list_equals(linked_list_t *l1, linked_list_t *l2) +{ + bool equals = TRUE; + iterator_t *i1, *i2; + traffic_selector_t *t1, *t2; + + if (l1->get_count(l1) != l2->get_count(l2)) + { + return FALSE; + } + + i1 = l1->create_iterator(l1, TRUE); + i2 = l2->create_iterator(l2, TRUE); + while (i1->iterate(i1, (void**)&t1) && i2->iterate(i2, (void**)&t2)) + { + if (!t1->equals(t1, t2)) + { + equals = FALSE; + break; + } + } + i1->destroy(i1); + i2->destroy(i2); + return equals; +} + +/** + * Implementation of ike_sa_t.route. + */ +static status_t route(private_ike_sa_t *this, connection_t *connection, policy_t *policy) +{ + child_sa_t *child_sa = NULL; + iterator_t *iterator; + linked_list_t *my_ts, *other_ts; + status_t status; + + SIG(CHILD_ROUTE_START, "routing CHILD_SA"); + + /* check if not already routed*/ + iterator = this->child_sas->create_iterator(this->child_sas, TRUE); + while (iterator->iterate(iterator, (void**)&child_sa)) + { + if (child_sa->get_state(child_sa) == CHILD_ROUTED) + { + linked_list_t *my_ts_conf, *other_ts_conf; + + my_ts = child_sa->get_my_traffic_selectors(child_sa); + other_ts = child_sa->get_other_traffic_selectors(child_sa); + + my_ts_conf = policy->get_my_traffic_selectors(policy, this->my_host); + other_ts_conf = policy->get_other_traffic_selectors(policy, this->other_host); + + if (ts_list_equals(my_ts, my_ts_conf) && + ts_list_equals(other_ts, other_ts_conf)) + { + iterator->destroy(iterator); + my_ts_conf->destroy_offset(my_ts_conf, offsetof(traffic_selector_t, destroy)); + other_ts_conf->destroy_offset(other_ts_conf, offsetof(traffic_selector_t, destroy)); + SIG(CHILD_ROUTE_FAILED, "CHILD_SA with such a policy already routed"); + return FAILED; + } + my_ts_conf->destroy_offset(my_ts_conf, offsetof(traffic_selector_t, destroy)); + other_ts_conf->destroy_offset(other_ts_conf, offsetof(traffic_selector_t, destroy)); + } + } + iterator->destroy(iterator); + + switch (this->state) + { + case IKE_DELETING: + case IKE_REKEYING: + SIG(CHILD_ROUTE_FAILED, + "unable to route CHILD_SA, as its IKE_SA gets deleted"); + return FAILED; + case IKE_CREATED: + /* apply connection information, we need it to acquire */ + apply_config(this, connection, policy); + break; + case IKE_CONNECTING: + case IKE_ESTABLISHED: + default: + break; + } + + /* install kernel policies */ + child_sa = child_sa_create(this->my_host, this->other_host, + this->my_id, this->other_id, policy, FALSE, 0); + + my_ts = policy->get_my_traffic_selectors(policy, this->my_host); + other_ts = policy->get_other_traffic_selectors(policy, this->other_host); + status = child_sa->add_policies(child_sa, my_ts, other_ts, + policy->get_mode(policy)); + my_ts->destroy_offset(my_ts, offsetof(traffic_selector_t, destroy)); + other_ts->destroy_offset(other_ts, offsetof(traffic_selector_t, destroy)); + this->child_sas->insert_last(this->child_sas, child_sa); + SIG(CHILD_ROUTE_SUCCESS, "CHILD_SA routed"); + return status; +} + +/** + * Implementation of ike_sa_t.unroute. + */ +static status_t unroute(private_ike_sa_t *this, policy_t *policy) +{ + iterator_t *iterator; + child_sa_t *child_sa = NULL; + bool found = FALSE; + linked_list_t *my_ts, *other_ts, *my_ts_conf, *other_ts_conf; + + SIG(CHILD_UNROUTE_START, "unrouting CHILD_SA"); + + /* find CHILD_SA in ROUTED state */ + iterator = this->child_sas->create_iterator(this->child_sas, TRUE); + while (iterator->iterate(iterator, (void**)&child_sa)) + { + if (child_sa->get_state(child_sa) == CHILD_ROUTED) + { + my_ts = child_sa->get_my_traffic_selectors(child_sa); + other_ts = child_sa->get_other_traffic_selectors(child_sa); + + my_ts_conf = policy->get_my_traffic_selectors(policy, this->my_host); + other_ts_conf = policy->get_other_traffic_selectors(policy, this->other_host); + + if (ts_list_equals(my_ts, my_ts_conf) && + ts_list_equals(other_ts, other_ts_conf)) + { + iterator->remove(iterator); + SIG(CHILD_UNROUTE_SUCCESS, "CHILD_SA unrouted"); + child_sa->destroy(child_sa); + my_ts_conf->destroy_offset(my_ts_conf, offsetof(traffic_selector_t, destroy)); + other_ts_conf->destroy_offset(other_ts_conf, offsetof(traffic_selector_t, destroy)); + found = TRUE; + break; + } + my_ts_conf->destroy_offset(my_ts_conf, offsetof(traffic_selector_t, destroy)); + other_ts_conf->destroy_offset(other_ts_conf, offsetof(traffic_selector_t, destroy)); + } + } + iterator->destroy(iterator); + + if (!found) + { + SIG(CHILD_UNROUTE_FAILED, "CHILD_SA to unroute not found"); + return FAILED; + } + /* if we are not established, and we have no more routed childs, remove whole SA */ + if (this->state == IKE_CREATED && + this->child_sas->get_count(this->child_sas) == 0) + { + return DESTROY_ME; + } + return SUCCESS; +} + +/** + * Implementation of ike_sa_t.retransmit. + */ +static status_t retransmit(private_ike_sa_t *this, u_int32_t message_id) +{ + this->time.outbound = time(NULL); + if (this->task_manager->retransmit(this->task_manager, message_id) != SUCCESS) + { + policy_t *policy; + child_sa_t* child_sa; + linked_list_t *to_route, *to_restart; + iterator_t *iterator; + + /* 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->connection->get_keyingtries(this->connection); + this->keyingtry++; + if (tries == 0 || tries > this->keyingtry) + { + SIG(IKE_UP_FAILED, "peer not responding, trying again " + "(%d/%d) in background ", this->keyingtry + 1, tries); + reset(this); + return this->task_manager->initiate(this->task_manager); + } + SIG(IKE_UP_FAILED, "establishing IKE_SA failed, peer not responding"); + break; + } + case IKE_REKEYING: + SIG(IKE_REKEY_FAILED, "rekeying IKE_SA failed, peer not responding"); + break; + case IKE_DELETING: + SIG(IKE_DOWN_FAILED, "proper IKE_SA delete failed, peer not responding"); + break; + default: + break; + } + + /* summarize how we have to handle each child */ + to_route = linked_list_create(); + to_restart = linked_list_create(); + iterator = this->child_sas->create_iterator(this->child_sas, TRUE); + while (iterator->iterate(iterator, (void**)&child_sa)) + { + policy = child_sa->get_policy(child_sa); + + if (child_sa->get_state(child_sa) == CHILD_ROUTED) + { + /* reroute routed CHILD_SAs */ + to_route->insert_last(to_route, policy); + } + else + { + /* use DPD action for established CHILD_SAs */ + switch (policy->get_dpd_action(policy)) + { + case DPD_ROUTE: + to_route->insert_last(to_route, policy); + break; + case DPD_RESTART: + to_restart->insert_last(to_restart, policy); + break; + default: + break; + } + } + } + iterator->destroy(iterator); + + /* create a new IKE_SA if we have to route or to restart */ + if (to_route->get_count(to_route) || to_restart->get_count(to_restart)) + { + private_ike_sa_t *new; + task_t *task; + + new = (private_ike_sa_t*)charon->ike_sa_manager->checkout_new( + charon->ike_sa_manager, TRUE); + + apply_config(new, this->connection, this->policy); + /* use actual used host, not the wildcarded one in connection */ + new->other_host->destroy(new->other_host); + new->other_host = this->other_host->clone(this->other_host); + + /* install routes */ + while (to_route->remove_last(to_route, (void**)&policy) == SUCCESS) + { + route(new, new->connection, policy); + } + + /* restart children */ + if (to_restart->get_count(to_restart)) + { + task = (task_t*)ike_init_create(&new->public, TRUE, NULL); + new->task_manager->queue_task(new->task_manager, task); + task = (task_t*)ike_natd_create(&new->public, TRUE); + new->task_manager->queue_task(new->task_manager, task); + task = (task_t*)ike_cert_create(&new->public, TRUE); + new->task_manager->queue_task(new->task_manager, task); + task = (task_t*)ike_config_create(&new->public, new->policy); + new->task_manager->queue_task(new->task_manager, task); + task = (task_t*)ike_auth_create(&new->public, TRUE); + new->task_manager->queue_task(new->task_manager, task); + + while (to_restart->remove_last(to_restart, (void**)&policy) == SUCCESS) + { + task = (task_t*)child_create_create(&new->public, policy); + new->task_manager->queue_task(new->task_manager, task); + } + new->task_manager->initiate(new->task_manager); + } + charon->ike_sa_manager->checkin(charon->ike_sa_manager, &new->public); + } + to_route->destroy(to_route); + to_restart->destroy(to_restart); + return DESTROY_ME; + } + return SUCCESS; +} + +/** + * Implementation of ike_sa_t.get_prf. + */ +static prf_t *get_prf(private_ike_sa_t *this) +{ + return this->prf; +} + +/** + * Implementation of ike_sa_t.get_prf. + */ +static prf_t *get_child_prf(private_ike_sa_t *this) +{ + return this->child_prf; +} + +/** + * Implementation of ike_sa_t.get_auth_bild + */ +static prf_t *get_auth_build(private_ike_sa_t *this) +{ + return this->auth_build; +} + +/** + * Implementation of ike_sa_t.get_auth_verify + */ +static prf_t *get_auth_verify(private_ike_sa_t *this) +{ + return this->auth_verify; +} + +/** + * 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.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.derive_keys. + */ +static status_t derive_keys(private_ike_sa_t *this, + proposal_t *proposal, chunk_t secret, + chunk_t nonce_i, chunk_t nonce_r, + bool initiator, prf_t *child_prf, prf_t *old_prf) +{ + prf_plus_t *prf_plus; + chunk_t skeyseed, key, nonces, prf_plus_seed; + algorithm_t *algo; + size_t key_size; + crypter_t *crypter_i, *crypter_r; + signer_t *signer_i, *signer_r; + prf_t *prf_i, *prf_r; + u_int8_t spi_i_buf[sizeof(u_int64_t)], spi_r_buf[sizeof(u_int64_t)]; + chunk_t spi_i = chunk_from_buf(spi_i_buf); + chunk_t spi_r = chunk_from_buf(spi_r_buf); + + /* Create SAs general purpose PRF first, we may use it here */ + if (!proposal->get_algorithm(proposal, PSEUDO_RANDOM_FUNCTION, &algo)) + { + DBG1(DBG_IKE, "key derivation failed: no PSEUDO_RANDOM_FUNCTION");; + return FAILED; + } + this->prf = prf_create(algo->algorithm); + if (this->prf == NULL) + { + DBG1(DBG_IKE, "key derivation failed: PSEUDO_RANDOM_FUNCTION " + "%N not supported!", pseudo_random_function_names, algo->algorithm); + return FAILED; + } + + DBG4(DBG_IKE, "shared Diffie Hellman secret %B", &secret); + nonces = chunk_cat("cc", nonce_i, nonce_r); + *((u_int64_t*)spi_i.ptr) = this->ike_sa_id->get_initiator_spi(this->ike_sa_id); + *((u_int64_t*)spi_r.ptr) = this->ike_sa_id->get_responder_spi(this->ike_sa_id); + prf_plus_seed = chunk_cat("ccc", nonces, spi_i, spi_r); + + /* KEYMAT = prf+ (SKEYSEED, Ni | Nr | SPIi | SPIr) + * + * if we are rekeying, SKEYSEED is built on another way + */ + if (child_prf == NULL) /* not rekeying */ + { + /* SKEYSEED = prf(Ni | Nr, g^ir) */ + this->prf->set_key(this->prf, nonces); + this->prf->allocate_bytes(this->prf, secret, &skeyseed); + DBG4(DBG_IKE, "SKEYSEED %B", &skeyseed); + this->prf->set_key(this->prf, skeyseed); + chunk_free(&skeyseed); + chunk_free(&secret); + 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 */ + secret = chunk_cat("mc", secret, nonces); + child_prf->allocate_bytes(child_prf, secret, &skeyseed); + DBG4(DBG_IKE, "SKEYSEED %B", &skeyseed); + old_prf->set_key(old_prf, skeyseed); + chunk_free(&skeyseed); + chunk_free(&secret); + prf_plus = prf_plus_create(old_prf, prf_plus_seed); + } + chunk_free(&nonces); + chunk_free(&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 => child_prf */ + proposal->get_algorithm(proposal, PSEUDO_RANDOM_FUNCTION, &algo); + this->child_prf = prf_create(algo->algorithm); + key_size = this->child_prf->get_key_size(this->child_prf); + prf_plus->allocate_bytes(prf_plus, key_size, &key); + DBG4(DBG_IKE, "Sk_d secret %B", &key); + this->child_prf->set_key(this->child_prf, key); + chunk_free(&key); + + /* SK_ai/SK_ar used for integrity protection => signer_in/signer_out */ + if (!proposal->get_algorithm(proposal, INTEGRITY_ALGORITHM, &algo)) + { + DBG1(DBG_IKE, "key derivation failed: no INTEGRITY_ALGORITHM"); + return FAILED; + } + signer_i = signer_create(algo->algorithm); + signer_r = signer_create(algo->algorithm); + if (signer_i == NULL || signer_r == NULL) + { + DBG1(DBG_IKE, "key derivation failed: INTEGRITY_ALGORITHM " + "%N not supported!", integrity_algorithm_names ,algo->algorithm); + return FAILED; + } + 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_free(&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_free(&key); + + if (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, &algo)) + { + DBG1(DBG_IKE, "key derivation failed: no ENCRYPTION_ALGORITHM"); + return FAILED; + } + crypter_i = crypter_create(algo->algorithm, algo->key_size / 8); + crypter_r = crypter_create(algo->algorithm, algo->key_size / 8); + if (crypter_i == NULL || crypter_r == NULL) + { + DBG1(DBG_IKE, "key derivation failed: ENCRYPTION_ALGORITHM " + "%N (key size %d) not supported!", + encryption_algorithm_names, algo->algorithm, algo->key_size); + return FAILED; + } + 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_free(&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_free(&key); + + if (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 => prf_auth_i, prf_auth_r */ + proposal->get_algorithm(proposal, PSEUDO_RANDOM_FUNCTION, &algo); + prf_i = prf_create(algo->algorithm); + prf_r = prf_create(algo->algorithm); + + key_size = prf_i->get_key_size(prf_i); + prf_plus->allocate_bytes(prf_plus, key_size, &key); + DBG4(DBG_IKE, "Sk_pi secret %B", &key); + prf_i->set_key(prf_i, key); + chunk_free(&key); + + prf_plus->allocate_bytes(prf_plus, key_size, &key); + DBG4(DBG_IKE, "Sk_pr secret %B", &key); + prf_r->set_key(prf_r, key); + chunk_free(&key); + + if (initiator) + { + this->auth_verify = prf_r; + this->auth_build = prf_i; + } + else + { + this->auth_verify = prf_i; + this->auth_build = prf_r; + } + + /* all done, prf_plus not needed anymore */ + prf_plus->destroy(prf_plus); + + return SUCCESS; +} + +/** + * 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_sa_t *child_sa; + child_rekey_t *child_rekey; + + child_sa = get_child_sa(this, protocol, spi, TRUE); + if (child_sa) + { + child_rekey = child_rekey_create(&this->public, child_sa); + this->task_manager->queue_task(this->task_manager, &child_rekey->task); + return this->task_manager->initiate(this->task_manager); + } + return FAILED; +} + +/** + * 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_sa_t *child_sa; + child_delete_t *child_delete; + + child_sa = get_child_sa(this, protocol, spi, TRUE); + if (child_sa) + { + child_delete = child_delete_create(&this->public, child_sa); + this->task_manager->queue_task(this->task_manager, &child_delete->task); + return this->task_manager->initiate(this->task_manager); + } + return FAILED; +} + +/** + * 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: + DBG1(DBG_IKE, "deleting IKE_SA"); + /* do not log when rekeyed */ + 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); + default: + DBG1(DBG_IKE, "destroying IKE_SA in state %N without notification", + ike_sa_state_names, this->state); + 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.reestablish + */ +static void reestablish(private_ike_sa_t *this) +{ + private_ike_sa_t *other; + iterator_t *iterator; + child_sa_t *child_sa; + policy_t *policy; + task_t *task; + job_t *job; + + other = (private_ike_sa_t*)charon->ike_sa_manager->checkout_new( + charon->ike_sa_manager, TRUE); + + apply_config(other, this->connection, this->policy); + other->other_host->destroy(other->other_host); + other->other_host = this->other_host->clone(this->other_host); + + if (this->state == IKE_ESTABLISHED) + { + task = (task_t*)ike_init_create(&other->public, TRUE, NULL); + other->task_manager->queue_task(other->task_manager, task); + task = (task_t*)ike_natd_create(&other->public, TRUE); + other->task_manager->queue_task(other->task_manager, task); + task = (task_t*)ike_cert_create(&other->public, TRUE); + other->task_manager->queue_task(other->task_manager, task); + task = (task_t*)ike_config_create(&other->public, other->policy); + other->task_manager->queue_task(other->task_manager, task); + task = (task_t*)ike_auth_create(&other->public, TRUE); + other->task_manager->queue_task(other->task_manager, task); + } + + other->task_manager->adopt_tasks(other->task_manager, this->task_manager); + + /* Create task for established children, adopt routed children directly */ + iterator = this->child_sas->create_iterator(this->child_sas, TRUE); + while(iterator->iterate(iterator, (void**)&child_sa)) + { + switch (child_sa->get_state(child_sa)) + { + case CHILD_ROUTED: + { + iterator->remove(iterator); + other->child_sas->insert_first(other->child_sas, child_sa); + break; + } + default: + { + policy = child_sa->get_policy(child_sa); + task = (task_t*)child_create_create(&other->public, policy); + other->task_manager->queue_task(other->task_manager, task); + break; + } + } + } + iterator->destroy(iterator); + + other->task_manager->initiate(other->task_manager); + + charon->ike_sa_manager->checkin(charon->ike_sa_manager, &other->public); + + job = (job_t*)delete_ike_sa_job_create(this->ike_sa_id, TRUE); + charon->job_queue->add(charon->job_queue, job); +} + +/** + * 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; + host_t *ip; + + /* 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 DNS servers */ + while (other->dns_servers->remove_last(other->dns_servers, + (void**)&ip) == SUCCESS) + { + this->dns_servers->insert_first(this->dns_servers, ip); + } + + /* 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); + + /* 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.is_natt_enabled. + */ +static bool is_natt_enabled(private_ike_sa_t *this) +{ + return this->nat_here || this->nat_there; +} + +/** + * Implementation of ike_sa_t.enable_natt. + */ +static void enable_natt(private_ike_sa_t *this, bool local) +{ + if (local) + { + DBG1(DBG_IKE, "local host is behind NAT, scheduling keep alives"); + this->nat_here = TRUE; + send_keepalive(this); + } + else + { + DBG1(DBG_IKE, "remote host is behind NAT"); + this->nat_there = TRUE; + } +} + +/** + * 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 (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, + this->my_host); + this->my_virtual_ip->destroy(this->my_virtual_ip); + } + if (charon->kernel_interface->add_ip(charon->kernel_interface, ip, + this->my_host) == SUCCESS) + { + 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.remove_dns_server + */ +static void remove_dns_servers(private_ike_sa_t *this) +{ + FILE *file; + struct stat stats; + chunk_t contents, line, orig_line, token; + char string[INET6_ADDRSTRLEN]; + host_t *ip; + iterator_t *iterator; + + if (this->dns_servers->get_count(this->dns_servers) == 0) + { + /* don't touch anything if we have no nameservers installed */ + return; + } + + file = fopen(RESOLV_CONF, "r"); + if (file == NULL || stat(RESOLV_CONF, &stats) != 0) + { + DBG1(DBG_IKE, "unable to open DNS configuration file %s: %m", RESOLV_CONF); + return; + } + + contents = chunk_alloca((size_t)stats.st_size); + + if (fread(contents.ptr, 1, contents.len, file) != contents.len) + { + DBG1(DBG_IKE, "unable to read DNS configuration file: %m"); + fclose(file); + return; + } + + fclose(file); + file = fopen(RESOLV_CONF, "w"); + if (file == NULL) + { + DBG1(DBG_IKE, "unable to open DNS configuration file %s: %m", RESOLV_CONF); + return; + } + + iterator = this->dns_servers->create_iterator(this->dns_servers, TRUE); + while (fetchline(&contents, &line)) + { + bool found = FALSE; + orig_line = line; + if (extract_token(&token, ' ', &line) && + strncasecmp(token.ptr, "nameserver", token.len) == 0) + { + if (!extract_token(&token, ' ', &line)) + { + token = line; + } + iterator->reset(iterator); + while (iterator->iterate(iterator, (void**)&ip)) + { + snprintf(string, sizeof(string), "%H", ip); + if (strlen(string) == token.len && + strncmp(token.ptr, string, token.len) == 0) + { + iterator->remove(iterator); + ip->destroy(ip); + found = TRUE; + break; + } + } + } + + if (!found) + { + /* write line untouched back to file */ + fwrite(orig_line.ptr, orig_line.len, 1, file); + fprintf(file, "\n"); + } + } + iterator->destroy(iterator); + fclose(file); +} + +/** + * Implementation of ike_sa_t.add_dns_server + */ +static void add_dns_server(private_ike_sa_t *this, host_t *dns) +{ + FILE *file; + struct stat stats; + chunk_t contents; + + DBG1(DBG_IKE, "installing DNS server %H", dns); + + file = fopen(RESOLV_CONF, "a+"); + if (file == NULL || stat(RESOLV_CONF, &stats) != 0) + { + DBG1(DBG_IKE, "unable to open DNS configuration file %s: %m", RESOLV_CONF); + return; + } + + contents = chunk_alloca(stats.st_size); + + if (fread(contents.ptr, 1, contents.len, file) != contents.len) + { + DBG1(DBG_IKE, "unable to read DNS configuration file: %m"); + fclose(file); + return; + } + + fclose(file); + file = fopen(RESOLV_CONF, "w"); + if (file == NULL) + { + DBG1(DBG_IKE, "unable to open DNS configuration file %s: %m", RESOLV_CONF); + return; + } + + if (fprintf(file, "nameserver %H # added by strongSwan, assigned by %D\n", + dns, this->other_id) < 0) + { + DBG1(DBG_IKE, "unable to write DNS configuration: %m"); + } + else + { + this->dns_servers->insert_last(this->dns_servers, dns->clone(dns)); + } + fwrite(contents.ptr, contents.len, 1, file); + + fclose(file); +} + +/** + * output handler in printf() + */ +static int print(FILE *stream, const struct printf_info *info, + const void *const *args) +{ + int written = 0; + bool reauth = FALSE; + private_ike_sa_t *this = *((private_ike_sa_t**)(args[0])); + + if (this->connection) + { + reauth = this->connection->get_reauth(this->connection); + } + + if (this == NULL) + { + return fprintf(stream, "(null)"); + } + + written = fprintf(stream, "%12s[%d]: %N, %H[%D]...%H[%D]", get_name(this), + this->unique_id, ike_sa_state_names, this->state, + this->my_host, this->my_id, this->other_host, + this->other_id); + written += fprintf(stream, "\n%12s[%d]: IKE SPIs: %J, %s in %ds", + get_name(this), this->unique_id, this->ike_sa_id, + this->connection && reauth? "reauthentication":"rekeying", + this->time.rekey - time(NULL)); + + if (info->alt) + { + + } + return written; +} + +/** + * register printf() handlers + */ +static void __attribute__ ((constructor))print_register() +{ + register_printf_function(PRINTF_IKE_SA, print, arginfo_ptr); +} + +/** + * Implementation of ike_sa_t.destroy. + */ +static void destroy(private_ike_sa_t *this) +{ + this->child_sas->destroy_offset(this->child_sas, offsetof(child_sa_t, destroy)); + + DESTROY_IF(this->crypter_in); + DESTROY_IF(this->crypter_out); + DESTROY_IF(this->signer_in); + DESTROY_IF(this->signer_out); + DESTROY_IF(this->prf); + DESTROY_IF(this->child_prf); + DESTROY_IF(this->auth_verify); + DESTROY_IF(this->auth_build); + + if (this->my_virtual_ip) + { + charon->kernel_interface->del_ip(charon->kernel_interface, + this->my_virtual_ip, this->my_host); + this->my_virtual_ip->destroy(this->my_virtual_ip); + } + DESTROY_IF(this->other_virtual_ip); + + remove_dns_servers(this); + this->dns_servers->destroy_offset(this->dns_servers, offsetof(host_t, destroy)); + + DESTROY_IF(this->my_host); + DESTROY_IF(this->other_host); + DESTROY_IF(this->my_id); + DESTROY_IF(this->other_id); + + DESTROY_IF(this->connection); + DESTROY_IF(this->policy); + + this->ike_sa_id->destroy(this->ike_sa_id); + this->task_manager->destroy(this->task_manager); + 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.process_message = (status_t(*)(ike_sa_t*, message_t*)) process_message; + this->public.initiate = (status_t(*)(ike_sa_t*,connection_t*,policy_t*)) initiate; + this->public.route = (status_t(*)(ike_sa_t*,connection_t*,policy_t*)) route; + this->public.unroute = (status_t(*)(ike_sa_t*,policy_t*)) unroute; + this->public.acquire = (status_t(*)(ike_sa_t*,u_int32_t)) acquire; + this->public.get_connection = (connection_t*(*)(ike_sa_t*))get_connection; + this->public.set_connection = (void(*)(ike_sa_t*,connection_t*))set_connection; + this->public.get_policy = (policy_t*(*)(ike_sa_t*))get_policy; + this->public.set_policy = (void(*)(ike_sa_t*,policy_t*))set_policy; + 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.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.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_prf = (prf_t *(*) (ike_sa_t *)) get_prf; + this->public.get_child_prf = (prf_t *(*) (ike_sa_t *)) get_child_prf; + this->public.get_auth_verify = (prf_t *(*) (ike_sa_t *)) get_auth_verify; + this->public.get_auth_build = (prf_t *(*) (ike_sa_t *)) get_auth_build; + this->public.derive_keys = (status_t (*) (ike_sa_t *,proposal_t*,chunk_t,chunk_t,chunk_t,bool,prf_t*,prf_t*)) derive_keys; + 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.enable_natt = (void(*)(ike_sa_t*, bool)) enable_natt; + this->public.is_natt_enabled = (bool(*)(ike_sa_t*)) is_natt_enabled; + this->public.rekey = (status_t(*)(ike_sa_t*))rekey; + this->public.reestablish = (void(*)(ike_sa_t*))reestablish; + 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_dns_server = (void(*)(ike_sa_t*,host_t*))add_dns_server; + + /* 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->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->crypter_in = NULL; + this->crypter_out = NULL; + this->signer_in = NULL; + this->signer_out = NULL; + this->prf = NULL; + this->auth_verify = NULL; + this->auth_build = NULL; + this->child_prf = NULL; + this->nat_here = FALSE; + this->nat_there = FALSE; + this->state = IKE_CREATED; + this->time.inbound = this->time.outbound = time(NULL); + this->time.established = 0; + this->time.rekey = 0; + this->time.delete = 0; + this->connection = NULL; + this->policy = 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->dns_servers = linked_list_create(); + this->keyingtry = 0; + + return &this->public; +} diff --git a/src/charon/sa/ike_sa.h b/src/charon/sa/ike_sa.h new file mode 100644 index 000000000..604ec94a9 --- /dev/null +++ b/src/charon/sa/ike_sa.h @@ -0,0 +1,649 @@ +/** + * @file ike_sa.h + * + * @brief Interface of ike_sa_t. + * + */ + +/* + * Copyright (C) 2006 Tobias Brunner, Daniel Roethlisberger + * 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. + */ + +#ifndef IKE_SA_H_ +#define IKE_SA_H_ + +typedef enum ike_sa_state_t ike_sa_state_t; +typedef struct ike_sa_t ike_sa_t; + +#include <library.h> +#include <encoding/message.h> +#include <encoding/payloads/proposal_substructure.h> +#include <sa/ike_sa_id.h> +#include <sa/child_sa.h> +#include <sa/tasks/task.h> +#include <config/configuration.h> +#include <utils/randomizer.h> +#include <crypto/prfs/prf.h> +#include <crypto/crypters/crypter.h> +#include <crypto/signers/signer.h> +#include <config/connections/connection.h> +#include <config/policies/policy.h> +#include <config/proposal.h> + +/** + * @brief 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 + * + * @ingroup sa + */ +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 rekeying in progress + */ + IKE_REKEYING, + + /** + * IKE_SA is in progress of deletion + */ + IKE_DELETING, +}; + +/** + * enum names for ike_sa_state_t. + */ +extern enum_name_t *ike_sa_state_names; + +/** + * @brief 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. + * + * @b Constructors: + * - ike_sa_create() + * + * @ingroup sa + */ +struct ike_sa_t { + + /** + * @brief Get the id of the SA. + * + * Returned ike_sa_id_t object is not getting cloned! + * + * @param this calling object + * @return ike_sa's ike_sa_id_t + */ + ike_sa_id_t* (*get_id) (ike_sa_t *this); + + /** + * @brief Get the numerical ID uniquely defining this IKE_SA. + * + * @param this calling object + * @return unique ID + */ + u_int32_t (*get_unique_id) (ike_sa_t *this); + + /** + * @brief Get the state of the IKE_SA. + * + * @param this calling object + * @return state of the IKE_SA + */ + ike_sa_state_t (*get_state) (ike_sa_t *this); + + /** + * @brief Set the state of the IKE_SA. + * + * @param this calling object + * @param state state to set for the IKE_SA + */ + void (*set_state) (ike_sa_t *this, ike_sa_state_t ike_sa); + + /** + * @brief Get the name of the connection this IKE_SA uses. + * + * @param this calling object + * @return name + */ + char* (*get_name) (ike_sa_t *this); + + /** + * @brief Get the own host address. + * + * @param this calling object + * @return host address + */ + host_t* (*get_my_host) (ike_sa_t *this); + + /** + * @brief Set the own host address. + * + * @param this calling object + * @param me host address + */ + void (*set_my_host) (ike_sa_t *this, host_t *me); + + /** + * @brief Get the other peers host address. + * + * @param this calling object + * @return host address + */ + host_t* (*get_other_host) (ike_sa_t *this); + + /** + * @brief Set the others host address. + * + * @param this calling object + * @param other host address + */ + void (*set_other_host) (ike_sa_t *this, host_t *other); + + /** + * @brief Get the own identification. + * + * @param this calling object + * @return identification + */ + identification_t* (*get_my_id) (ike_sa_t *this); + + /** + * @brief Set the own identification. + * + * @param this calling object + * @param me identification + */ + void (*set_my_id) (ike_sa_t *this, identification_t *me); + + /** + * @brief Get the other peers identification. + * + * @param this calling object + * @return identification + */ + identification_t* (*get_other_id) (ike_sa_t *this); + + /** + * @brief Set the other peers identification. + * + * @param this calling object + * @param other identification + */ + void (*set_other_id) (ike_sa_t *this, identification_t *other); + + /** + * @brief Get the connection used by this IKE_SA. + * + * @param this calling object + * @return connection + */ + connection_t* (*get_connection) (ike_sa_t *this); + + /** + * @brief Set the connection to use with this IKE_SA. + * + * @param this calling object + * @param connection connection to use + */ + void (*set_connection) (ike_sa_t *this, connection_t* connection); + + /** + * @brief Get the policy used by this IKE_SA. + * + * @param this calling object + * @return policy + */ + policy_t* (*get_policy) (ike_sa_t *this); + + /** + * @brief Set the policy to use with this IKE_SA. + * + * @param this calling object + * @param policy policy to use + */ + void (*set_policy) (ike_sa_t *this, policy_t *policy); + + /** + * @brief Initiate a new connection. + * + * The policy/connection is owned by the IKE_SA after the call, so + * do not modify or destroy it. + * + * @param this calling object + * @param connection connection to initiate + * @param policy policy to set up + * @return + * - SUCCESS if initialization started + * - DESTROY_ME if initialization failed and IKE_SA MUST be deleted + */ + status_t (*initiate) (ike_sa_t *this, connection_t *connection, policy_t *policy); + + /** + * @brief Route a policy in the kernel. + * + * Installs the policies in the kernel. If traffic matches, + * the kernel requests connection setup from the IKE_SA via acquire(). + * + * @param this calling object + * @param connection connection definition used for routing + * @param policy policy to route + * @return + * - SUCCESS if routed successfully + * - FAILED if routing failed + */ + status_t (*route) (ike_sa_t *this, connection_t *connection, policy_t *policy); + + /** + * @brief Unroute a policy in the kernel previously routed. + * + * @param this calling object + * @param policy policy to route + * @return + * - SUCCESS if route removed + * - DESTROY_ME if last route was removed from + * an IKE_SA which was not established + */ + status_t (*unroute) (ike_sa_t *this, policy_t *policy); + + /** + * @brief Acquire connection setup for a policy. + * + * If an installed policy raises an acquire, the kernel calls + * this function to establish the CHILD_SA (and maybe the IKE_SA). + * + * @param this calling object + * @param reqid reqid of the CHILD_SA the policy belongs to. + * @return + * - SUCCESS if initialization started + * - DESTROY_ME if initialization failed and IKE_SA MUST be deleted + */ + status_t (*acquire) (ike_sa_t *this, u_int32_t reqid); + + /** + * @brief 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. + * + * @param this calling object + * @return + * - SUCCESS if deletion is initialized + * - INVALID_STATE, if the IKE_SA is not in + * an established state and can not be + * delete (but destroyed). + */ + status_t (*delete) (ike_sa_t *this); + + /** + * @brief 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 this calling object + * @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); + + /** + * @brief Generate a IKE message to send it to the peer. + * + * This method generates all payloads in the message and encrypts/signs + * the packet. + * + * @param this calling object + * @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); + + /** + * @brief Retransmits a request. + * + * @param this calling object + * @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); + + /** + * @brief 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. + * + * @param this calling object + * @return + * - SUCCESS + * - DESTROY_ME, if peer did not respond + */ + status_t (*send_dpd) (ike_sa_t *this); + + /** + * @brief 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. + * + * @param this calling object + */ + void (*send_keepalive) (ike_sa_t *this); + + /** + * @brief Check if NAT traversal is enabled for this IKE_SA. + * + * @param this calling object + * @return TRUE if NAT traversal enabled + */ + bool (*is_natt_enabled) (ike_sa_t *this); + + /** + * @brief Enable NAT detection for this IKE_SA. + * + * If a Network address translation is detected with + * NAT_DETECTION notifys, a SA must switch to ports + * 4500. To enable this behavior, call enable_natt(). + * It is relevant which peer is NATted, this is specified + * with the "local" parameter. Call it twice when both + * are NATted. + * + * @param this calling object + * @param local TRUE, if we are NATted, FALSE if other + */ + void (*enable_natt) (ike_sa_t *this, bool local); + + /** + * @brief Derive all keys and create the transforms for IKE communication. + * + * Keys are derived using the diffie hellman secret, nonces and internal + * stored SPIs. + * Key derivation differs when an IKE_SA is set up to replace an + * existing IKE_SA (rekeying). The SK_d key from the old IKE_SA + * is included in the derivation process. + * + * @param this calling object + * @param proposal proposal which contains algorithms to use + * @param secret secret derived from DH exchange, gets freed + * @param nonce_i initiators nonce + * @param nonce_r responders nonce + * @param initiator TRUE if initiator, FALSE otherwise + * @param child_prf PRF with SK_d key when rekeying, NULL otherwise + * @param old_prf general purpose PRF of old SA when rekeying + */ + status_t (*derive_keys)(ike_sa_t *this, proposal_t* proposal, chunk_t secret, + chunk_t nonce_i, chunk_t nonce_r, + bool initiator, prf_t *child_prf, prf_t *old_prf); + + /** + * @brief Get the multi purpose prf. + * + * @param this calling object + * @return pointer to prf_t object + */ + prf_t *(*get_prf) (ike_sa_t *this); + + /** + * @brief Get the prf-object, which is used to derive keys for child SAs. + * + * @param this calling object + * @return pointer to prf_t object + */ + prf_t *(*get_child_prf) (ike_sa_t *this); + + /** + * @brief Get the prf to build outgoing authentication data. + * + * @param this calling object + * @return pointer to prf_t object + */ + prf_t *(*get_auth_build) (ike_sa_t *this); + + /** + * @brief Get the prf to verify incoming authentication data. + * + * @param this calling object + * @return pointer to prf_t object + */ + prf_t *(*get_auth_verify) (ike_sa_t *this); + + /** + * @brief Associates a child SA to this IKE SA + * + * @param this calling object + * @param child_sa child_sa to add + */ + void (*add_child_sa) (ike_sa_t *this, child_sa_t *child_sa); + + /** + * @brief Get a CHILD_SA identified by protocol and SPI. + * + * @param this calling object + * @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); + + /** + * @brief Create an iterator over all CHILD_SAs. + * + * @param this calling object + * @return iterator + */ + iterator_t* (*create_child_sa_iterator) (ike_sa_t *this); + + /** + * @brief Rekey the CHILD SA with the specified reqid. + * + * Looks for a CHILD SA owned by this IKE_SA, and start the rekeing. + * + * @param this calling object + * @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); + + /** + * @brief 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 this calling object + * @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); + + /** + * @brief Destroy a CHILD SA with the specified protocol/SPI. + * + * Looks for a CHILD SA owned by this IKE_SA and destroys it. + * + * @param this calling object + * @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); + + /** + * @brief Rekey the IKE_SA. + * + * Sets up a new IKE_SA, moves all CHILDs to it and deletes this IKE_SA. + * + * @param this calling object + * @return - SUCCESS, if IKE_SA rekeying initiated + */ + status_t (*rekey) (ike_sa_t *this); + + /** + * @brief Restablish the IKE_SA. + * + * Create a completely new IKE_SA with authentication, recreates all children + * within the IKE_SA, but lets the old IKE_SA untouched. + * + * @param this calling object + */ + void (*reestablish) (ike_sa_t *this); + + /** + * @brief 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 this calling object + */ + void (*set_virtual_ip) (ike_sa_t *this, bool local, host_t *ip); + + /** + * @brief Get the virtual IP configured. + * + * @param this calling object + * @param local TRUE to get local virtual IP, FALSE for remote + */ + host_t* (*get_virtual_ip) (ike_sa_t *this, bool local); + + /** + * @brief Add a DNS server to the system. + * + * An IRAS may send a DNS server. To use it, it is installed on the + * system. The DNS entry has a lifetime until the IKE_SA gets closed. + * + * @param this calling object + * @param dns DNS server to install on the system + */ + void (*add_dns_server) (ike_sa_t *this, host_t *dns); + + /** + * @brief 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 this calling object + * @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); + + /** + * @brief Reset the IKE_SA, useable when initiating fails + * + * @param this calling object + */ + void (*reset) (ike_sa_t *this); + + /** + * @brief Destroys a ike_sa_t object. + * + * @param this calling object + */ + void (*destroy) (ike_sa_t *this); +}; + +/** + * @brief 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 + * + * @ingroup sa + */ +ike_sa_t *ike_sa_create(ike_sa_id_t *ike_sa_id); + +#endif /* IKE_SA_H_ */ diff --git a/src/charon/sa/ike_sa_id.c b/src/charon/sa/ike_sa_id.c new file mode 100644 index 000000000..c143fc0ba --- /dev/null +++ b/src/charon/sa/ike_sa_id.c @@ -0,0 +1,215 @@ +/** + * @file ike_sa_id.c + * + * @brief Implementation of ike_sa_id_t. + * + */ + +/* + * 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 <printf.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); +} + +/** + * output handler in printf() + */ +static int print(FILE *stream, const struct printf_info *info, + const void *const *args) +{ + private_ike_sa_id_t *this = *((private_ike_sa_id_t**)(args[0])); + + if (this == NULL) + { + return fprintf(stream, "(null)"); + } + return fprintf(stream, "0x%0llx_i%s 0x%0llx_r%s", + this->initiator_spi, + this->is_initiator_flag ? "*" : "", + this->responder_spi, + this->is_initiator_flag ? "" : "*"); +} + +/** + * register printf() handlers + */ +static void __attribute__ ((constructor))print_register() +{ + register_printf_function(PRINTF_IKE_SA_ID, print, arginfo_ptr); +} + +/** + * 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/charon/sa/ike_sa_id.h b/src/charon/sa/ike_sa_id.h new file mode 100644 index 000000000..0606b7222 --- /dev/null +++ b/src/charon/sa/ike_sa_id.h @@ -0,0 +1,147 @@ +/** + * @file ike_sa_id.h + * + * @brief Interface of ike_sa_id_t. + * + */ + +/* + * 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. + */ + + +#ifndef IKE_SA_ID_H_ +#define IKE_SA_ID_H_ + +typedef struct ike_sa_id_t ike_sa_id_t; + +#include <library.h> + + +/** + * @brief 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). + * + * @b Constructors: + * - ike_sa_id_create() + * + * @ingroup sa + */ +struct ike_sa_id_t { + + /** + * @brief Set the SPI of the responder. + * + * This function is called when a request or reply of a IKE_SA_INIT is received. + * + * @param this calling object + * @param responder_spi SPI of responder to set + */ + void (*set_responder_spi) (ike_sa_id_t *this, u_int64_t responder_spi); + + /** + * @brief Set the SPI of the initiator. + * + * @param this calling object + * @param initiator_spi SPI to set + */ + void (*set_initiator_spi) (ike_sa_id_t *this, u_int64_t initiator_spi); + + /** + * @brief Get the initiator SPI. + * + * @param this calling object + * @return SPI of the initiator + */ + u_int64_t (*get_initiator_spi) (ike_sa_id_t *this); + + /** + * @brief Get the responder SPI. + * + * @param this calling object + * @return SPI of the responder + */ + u_int64_t (*get_responder_spi) (ike_sa_id_t *this); + + /** + * @brief 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 this calling object + * @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); + + /** + * @brief 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 this calling object + * @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); + + /** + * @brief Get the initiator flag. + * + * @param this calling object + * @return TRUE if we are the original initator + */ + bool (*is_initiator) (ike_sa_id_t *this); + + /** + * @brief Switche the original initiator flag. + * + * @param this calling object + * @return TRUE if we are the original initator after switch, FALSE otherwise + */ + bool (*switch_initiator) (ike_sa_id_t *this); + + /** + * @brief Clones a given ike_sa_id_t object. + * + * @param this calling object + * @return cloned ike_sa_id_t object + */ + ike_sa_id_t *(*clone) (ike_sa_id_t *this); + + /** + * @brief Destroys an ike_sa_id_t object. + * + * @param this calling object + */ + void (*destroy) (ike_sa_id_t *this); +}; + +/** + * @brief 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 + * + * @ingroup sa + */ +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/charon/sa/ike_sa_manager.c b/src/charon/sa/ike_sa_manager.c new file mode 100644 index 000000000..791ef805e --- /dev/null +++ b/src/charon/sa/ike_sa_manager.c @@ -0,0 +1,914 @@ +/** + * @file ike_sa_manager.c + * + * @brief Implementation of ike_sa_mananger_t. + * + */ + +/* + * 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 <pthread.h> +#include <string.h> + +#include "ike_sa_manager.h" + +#include <daemon.h> +#include <sa/ike_sa_id.h> +#include <bus/bus.h> +#include <utils/linked_list.h> + +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. + */ + pthread_cond_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; + + /** + * Identifiaction 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; + + /** + * 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); + free(this); + return SUCCESS; +} + +/** + * Creates a new entry for the ike_sa_t list. + */ +static entry_t *entry_create(ike_sa_id_t *ike_sa_id) +{ + entry_t *this = malloc_thing(entry_t); + + this->waiting_threads = 0; + pthread_cond_init(&this->condvar, NULL); + + /* 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; + + /* ike_sa_id is always cloned */ + this->ike_sa_id = ike_sa_id->clone(ike_sa_id); + + /* create new ike_sa */ + this->ike_sa = ike_sa_create(ike_sa_id); + + return this; +} + + +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; + + /** + * Lock for exclusivly accessing the manager. + */ + pthread_mutex_t mutex; + + /** + * Linked list with entries for the ike_sa_t objects. + */ + linked_list_t *ike_sa_list; + + /** + * A randomizer, to get random SPIs for our side + */ + randomizer_t *randomizer; + + /** + * SHA1 hasher for IKE_SA_INIT retransmit detection + */ + hasher_t *hasher; +}; + +/** + * Implementation of private_ike_sa_manager_t.get_entry_by_id. + */ +static status_t get_entry_by_id(private_ike_sa_manager_t *this, ike_sa_id_t *ike_sa_id, entry_t **entry) +{ + linked_list_t *list = this->ike_sa_list; + iterator_t *iterator; + entry_t *current; + status_t status; + + /* create iterator over list of ike_sa's */ + iterator = list->create_iterator(list, TRUE); + + /* default status */ + status = NOT_FOUND; + + while (iterator->iterate(iterator, (void**)¤t)) + { + if (current->ike_sa_id->equals(current->ike_sa_id, ike_sa_id)) + { + DBG2(DBG_MGR, "found entry by both SPIs"); + *entry = current; + status = SUCCESS; + break; + } + if (ike_sa_id->get_responder_spi(ike_sa_id) == 0 || + current->ike_sa_id->get_responder_spi(current->ike_sa_id) == 0) + { + /* seems to be a half ready ike_sa */ + if ((current->ike_sa_id->get_initiator_spi(current->ike_sa_id) == + ike_sa_id->get_initiator_spi(ike_sa_id)) && + (current->ike_sa_id->is_initiator(ike_sa_id) == + ike_sa_id->is_initiator(current->ike_sa_id))) + { + DBG2(DBG_MGR, "found entry by initiator SPI"); + *entry = current; + status = SUCCESS; + break; + } + } + } + + iterator->destroy(iterator); + return status; +} + +/** + * Implementation of private_ike_sa_manager_t.get_entry_by_sa. + */ +static status_t get_entry_by_sa(private_ike_sa_manager_t *this, ike_sa_t *ike_sa, entry_t **entry) +{ + linked_list_t *list = this->ike_sa_list; + iterator_t *iterator; + entry_t *current; + status_t status; + + iterator = list->create_iterator(list, TRUE); + + /* default status */ + status = NOT_FOUND; + + while (iterator->iterate(iterator, (void**)¤t)) + { + /* only pointers are compared */ + if (current->ike_sa == ike_sa) + { + DBG2(DBG_MGR, "found entry by pointer"); + *entry = current; + status = SUCCESS; + break; + } + } + iterator->destroy(iterator); + + return status; +} + +/** + * Implementation of private_ike_sa_manager_s.delete_entry. + */ +static status_t delete_entry(private_ike_sa_manager_t *this, entry_t *entry) +{ + linked_list_t *list = this->ike_sa_list; + iterator_t *iterator; + entry_t *current; + status_t status; + + iterator = list->create_iterator(list, TRUE); + + status = NOT_FOUND; + + while (iterator->iterate(iterator, (void**)¤t)) + { + if (current == entry) + { + /* mark it, so now 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 */ + pthread_cond_broadcast(&(entry->condvar)); + /* they will wake us again when their work is done */ + pthread_cond_wait(&(entry->condvar), &(this->mutex)); + } + + DBG2(DBG_MGR, "found entry by pointer, deleting it"); + iterator->remove(iterator); + entry_destroy(entry); + status = SUCCESS; + break; + } + } + iterator->destroy(iterator); + return status; +} + +/** + * Wait until no other thread is using an IKE_SA, return FALSE if entry not + * acquireable + */ +static bool wait_for_entry(private_ike_sa_manager_t *this, entry_t *entry) +{ + 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++; + pthread_cond_wait(&(entry->condvar), &(this->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 */ + pthread_cond_signal(&(entry->condvar)); + return FALSE; + } + return TRUE; +} + +/** + * 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->randomizer->get_pseudo_random_bytes(this->randomizer, 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; + + DBG2(DBG_MGR, "checkout IKE_SA: %J, %d IKE_SAs in manager", + ike_sa_id, this->ike_sa_list->get_count(this->ike_sa_list)); + + pthread_mutex_lock(&(this->mutex)); + if (get_entry_by_id(this, ike_sa_id, &entry) == SUCCESS) + { + if (wait_for_entry(this, entry)) + { + DBG2(DBG_MGR, "IKE_SA successfully checked out"); + entry->checked_out = TRUE; + ike_sa = entry->ike_sa; + } + } + pthread_mutex_unlock(&this->mutex); + 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) +{ + entry_t *entry; + ike_sa_id_t *id; + + if (initiator) + { + id = ike_sa_id_create(get_next_spi(this), 0, TRUE); + } + else + { + id = ike_sa_id_create(0, get_next_spi(this), FALSE); + } + entry = entry_create(id); + pthread_mutex_lock(&this->mutex); + this->ike_sa_list->insert_last(this->ike_sa_list, entry); + entry->checked_out = TRUE; + pthread_mutex_unlock(&this->mutex); + DBG2(DBG_MGR, "created IKE_SA: %J, %d IKE_SAs in manager", + id, this->ike_sa_list->get_count(this->ike_sa_list)); + return entry->ike_sa; +} + +/** + * Implementation of of ike_sa_manager.checkout_by_id. + */ +static ike_sa_t* checkout_by_message(private_ike_sa_manager_t* this, + message_t *message) +{ + 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: %J by message, %d IKE_SAs in manager", + id, this->ike_sa_list->get_count(this->ike_sa_list)); + + 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. */ + iterator_t *iterator; + chunk_t data, hash; + + data = message->get_packet_data(message); + this->hasher->allocate_hash(this->hasher, data, &hash); + chunk_free(&data); + + pthread_mutex_lock(&this->mutex); + iterator = this->ike_sa_list->create_iterator(this->ike_sa_list, TRUE); + while (iterator->iterate(iterator, (void**)&entry)) + { + if (chunk_equals(hash, entry->init_hash)) + { + if (entry->message_id == 0) + { + iterator->destroy(iterator); + pthread_mutex_unlock(&this->mutex); + chunk_free(&hash); + id->destroy(id); + DBG1(DBG_MGR, "ignoring IKE_SA_INIT, already processing"); + return NULL; + } + else if (wait_for_entry(this, entry)) + { + 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; + } + break; + } + } + iterator->destroy(iterator); + pthread_mutex_unlock(&this->mutex); + + 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(id); + + pthread_mutex_lock(&this->mutex); + this->ike_sa_list->insert_last(this->ike_sa_list, entry); + entry->checked_out = TRUE; + entry->message_id = message->get_message_id(message); + pthread_mutex_unlock(&this->mutex); + entry->init_hash = hash; + ike_sa = entry->ike_sa; + } + else + { + DBG1(DBG_MGR, "ignoring message for %J, no such IKE_SA", id); + } + } + else + { + chunk_free(&hash); + } + id->destroy(id); + charon->bus->set_sa(charon->bus, ike_sa); + return ike_sa; + } + + pthread_mutex_lock(&(this->mutex)); + if (get_entry_by_id(this, id, &entry) == 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)) + { + 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; + } + } + pthread_mutex_unlock(&this->mutex); + id->destroy(id); + 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_peer(private_ike_sa_manager_t *this, + host_t *my_host, host_t *other_host, + identification_t *my_id, + identification_t *other_id) +{ + iterator_t *iterator; + entry_t *entry; + ike_sa_t *ike_sa = NULL; + + pthread_mutex_lock(&(this->mutex)); + + iterator = this->ike_sa_list->create_iterator(this->ike_sa_list, TRUE); + while (iterator->iterate(iterator, (void**)&entry)) + { + identification_t *found_my_id, *found_other_id; + host_t *found_my_host, *found_other_host; + int wc; + + if (!wait_for_entry(this, entry)) + { + continue; + } + + if (entry->ike_sa->get_state(entry->ike_sa) == IKE_DELETING) + { + /* skip IKE_SA which are not useable */ + continue; + } + + found_my_id = entry->ike_sa->get_my_id(entry->ike_sa); + found_other_id = entry->ike_sa->get_other_id(entry->ike_sa); + found_my_host = entry->ike_sa->get_my_host(entry->ike_sa); + found_other_host = entry->ike_sa->get_other_host(entry->ike_sa); + + if (found_my_id->get_type(found_my_id) == ID_ANY && + found_other_id->get_type(found_other_id) == ID_ANY) + { + /* IKE_SA has no IDs yet, so we can't use it */ + continue; + } + + /* compare ID and hosts. Supplied ID may contain wildcards, and IP + * may be %any. */ + if ((found_my_host->is_anyaddr(found_my_host) || + my_host->ip_equals(my_host, found_my_host)) && + (found_other_host->is_anyaddr(found_other_host) || + other_host->ip_equals(other_host, found_other_host)) && + found_my_id->matches(found_my_id, my_id, &wc) && + found_other_id->matches(found_other_id, other_id, &wc)) + { + /* looks good, we take this one */ + DBG2(DBG_MGR, "found an existing IKE_SA for %H[%D]...%H[%D]", + my_host, other_host, my_id, other_id); + entry->checked_out = TRUE; + ike_sa = entry->ike_sa; + } + } + iterator->destroy(iterator); + + if (!ike_sa) + { + u_int64_t initiator_spi; + entry_t *new_entry; + ike_sa_id_t *new_ike_sa_id; + + initiator_spi = get_next_spi(this); + new_ike_sa_id = ike_sa_id_create(0, 0, TRUE); + new_ike_sa_id->set_initiator_spi(new_ike_sa_id, initiator_spi); + + /* create entry */ + new_entry = entry_create(new_ike_sa_id); + DBG2(DBG_MGR, "created IKE_SA: %J", new_ike_sa_id); + new_ike_sa_id->destroy(new_ike_sa_id); + + this->ike_sa_list->insert_last(this->ike_sa_list, new_entry); + + /* check ike_sa out */ + DBG2(DBG_MGR, "new IKE_SA created for IDs [%D]...[%D]", my_id, other_id); + new_entry->checked_out = TRUE; + ike_sa = new_entry->ike_sa; + } + pthread_mutex_unlock(&(this->mutex)); + 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) +{ + iterator_t *iterator, *children; + entry_t *entry; + ike_sa_t *ike_sa = NULL; + child_sa_t *child_sa; + + pthread_mutex_lock(&(this->mutex)); + + iterator = this->ike_sa_list->create_iterator(this->ike_sa_list, TRUE); + while (iterator->iterate(iterator, (void**)&entry)) + { + if (wait_for_entry(this, entry)) + { + /* 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; + } + } + } + iterator->destroy(iterator); + pthread_mutex_unlock(&(this->mutex)); + + 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) +{ + iterator_t *iterator, *children; + entry_t *entry; + ike_sa_t *ike_sa = NULL; + child_sa_t *child_sa; + + pthread_mutex_lock(&(this->mutex)); + + iterator = this->ike_sa_list->create_iterator(this->ike_sa_list, TRUE); + while (iterator->iterate(iterator, (void**)&entry)) + { + if (wait_for_entry(this, entry)) + { + /* 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; + } + } + } + iterator->destroy(iterator); + pthread_mutex_unlock(&(this->mutex)); + + charon->bus->set_sa(charon->bus, ike_sa); + return ike_sa; +} + +/** + * Iterator hook for iterate, gets ike_sas instead of entries + */ +static bool iterator_hook(private_ike_sa_manager_t* this, entry_t *in, + ike_sa_t **out) +{ + /* check out entry */ + if (wait_for_entry(this, in)) + { + *out = in->ike_sa; + return TRUE; + } + return FALSE; +} + +/** + * Implementation of ike_sa_manager_t.create_iterator. + */ +static iterator_t *create_iterator(private_ike_sa_manager_t* this) +{ + iterator_t *iterator = this->ike_sa_list->create_iterator_locked( + this->ike_sa_list, &this->mutex); + /* register hook to iterator over ike_sas, not entries */ + iterator->set_iterator_hook(iterator, (iterator_hook_t*)iterator_hook, this); + return iterator; +} + +/** + * Implementation of ike_sa_manager_t.checkin. + */ +static status_t 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. + * We can't search by SPI's since the MAY have changed (e.g. on reception + * of a IKE_SA_INIT response). Updating of the SPI MAY be necessary... + */ + status_t retval; + entry_t *entry; + ike_sa_id_t *ike_sa_id; + + ike_sa_id = ike_sa->get_id(ike_sa); + + DBG2(DBG_MGR, "checkin IKE_SA: %J", ike_sa_id); + + pthread_mutex_lock(&(this->mutex)); + + /* look for the entry */ + if (get_entry_by_sa(this, ike_sa, &entry) == 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; + DBG2(DBG_MGR, "check-in of IKE_SA successful."); + pthread_cond_signal(&(entry->condvar)); + retval = SUCCESS; + } + else + { + DBG2(DBG_MGR, "tried to check in nonexisting IKE_SA"); + /* this SA is no more, this REALLY should not happen */ + retval = NOT_FOUND; + } + + DBG2(DBG_MGR, "%d IKE_SAs in manager now", + this->ike_sa_list->get_count(this->ike_sa_list)); + pthread_mutex_unlock(&(this->mutex)); + + charon->bus->set_sa(charon->bus, NULL); + return retval; +} + + +/** + * Implementation of ike_sa_manager_t.checkin_and_destroy. + */ +static status_t checkin_and_destroy(private_ike_sa_manager_t *this, ike_sa_t *ike_sa) +{ + /* deletion is a bit complex, we must garant that no thread is waiting for + * this SA. + * We take this SA from the list, and start signaling while threads + * are in the condvar. + */ + entry_t *entry; + status_t retval; + ike_sa_id_t *ike_sa_id; + + ike_sa_id = ike_sa->get_id(ike_sa); + DBG2(DBG_MGR, "checkin and destroy IKE_SA: %J", ike_sa_id); + + pthread_mutex_lock(&(this->mutex)); + + if (get_entry_by_sa(this, ike_sa, &entry) == SUCCESS) + { + /* drive out waiting threads, as we are in hurry */ + entry->driveout_waiting_threads = TRUE; + + delete_entry(this, entry); + + DBG2(DBG_MGR, "check-in and destroy of IKE_SA successful"); + retval = SUCCESS; + } + else + { + DBG2(DBG_MGR, "tried to check-in and delete nonexisting IKE_SA"); + retval = NOT_FOUND; + } + + pthread_mutex_unlock(&(this->mutex)); + charon->bus->set_sa(charon->bus, ike_sa); + return retval; +} + +/** + * 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) +{ + iterator_t *iterator; + entry_t *entry; + int count = 0; + + pthread_mutex_lock(&(this->mutex)); + iterator = this->ike_sa_list->create_iterator(this->ike_sa_list, TRUE); + while (iterator->iterate(iterator, (void**)&entry)) + { + /* we check if we have a responder CONNECTING IKE_SA without checkout */ + if (!entry->ike_sa_id->is_initiator(entry->ike_sa_id) && + entry->ike_sa->get_state(entry->ike_sa) == IKE_CONNECTING) + { + /* if we have a host, we have wait until no other uses the IKE_SA */ + if (ip) + { + if (wait_for_entry(this, entry) && ip->ip_equals(ip, + entry->ike_sa->get_other_host(entry->ike_sa))) + { + count++; + } + } + else + { + count++; + } + } + } + iterator->destroy(iterator); + + pthread_mutex_unlock(&(this->mutex)); + return count; +} + +/** + * Implementation of ike_sa_manager_t.destroy. + */ +static void destroy(private_ike_sa_manager_t *this) +{ + /* destroy all list entries */ + linked_list_t *list = this->ike_sa_list; + iterator_t *iterator; + entry_t *entry; + + pthread_mutex_lock(&(this->mutex)); + 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"); + iterator = list->create_iterator(list, TRUE); + while (iterator->iterate(iterator, (void**)&entry)) + { + /* do not accept new threads, drive out waiting threads */ + entry->driveout_new_threads = TRUE; + entry->driveout_waiting_threads = TRUE; + } + DBG2(DBG_MGR, "wait for all threads to leave IKE_SA's"); + /* Step 2: wait until all are gone */ + iterator->reset(iterator); + while (iterator->iterate(iterator, (void**)&entry)) + { + while (entry->waiting_threads) + { + /* wake up all */ + pthread_cond_broadcast(&(entry->condvar)); + /* go sleeping until they are gone */ + pthread_cond_wait(&(entry->condvar), &(this->mutex)); + } + } + DBG2(DBG_MGR, "delete all IKE_SA's"); + /* Step 3: initiate deletion of all IKE_SAs */ + iterator->reset(iterator); + while (iterator->iterate(iterator, (void**)&entry)) + { + entry->ike_sa->delete(entry->ike_sa); + } + iterator->destroy(iterator); + + DBG2(DBG_MGR, "destroy all entries"); + /* Step 4: destroy all entries */ + list->destroy_function(list, (void*)entry_destroy); + pthread_mutex_unlock(&(this->mutex)); + + this->randomizer->destroy(this->randomizer); + this->hasher->destroy(this->hasher); + + free(this); +} + +/* + * Described in header. + */ +ike_sa_manager_t *ike_sa_manager_create() +{ + private_ike_sa_manager_t *this = malloc_thing(private_ike_sa_manager_t); + + /* assign public functions */ + 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_peer = (ike_sa_t*(*)(ike_sa_manager_t*,host_t*,host_t*,identification_t*,identification_t*))checkout_by_peer; + 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.create_iterator = (iterator_t*(*)(ike_sa_manager_t*))create_iterator; + this->public.checkin = (status_t(*)(ike_sa_manager_t*,ike_sa_t*))checkin; + this->public.checkin_and_destroy = (status_t(*)(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->ike_sa_list = linked_list_create(); + pthread_mutex_init(&this->mutex, NULL); + this->randomizer = randomizer_create(); + this->hasher = hasher_create(HASH_SHA1); + + return &this->public; +} diff --git a/src/charon/sa/ike_sa_manager.h b/src/charon/sa/ike_sa_manager.h new file mode 100644 index 000000000..1125e5d16 --- /dev/null +++ b/src/charon/sa/ike_sa_manager.h @@ -0,0 +1,231 @@ +/** + * @file ike_sa_manager.h + * + * @brief Interface of ike_sa_manager_t. + * + */ + +/* + * 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. + */ + +#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> + +/** + * @brief The IKE_SA-Manager is responsible for managing all initiated and responded IKE_SA's. + * + * To avoid access from multiple threads, IKE_SAs must be checked out from + * the manager, and checked in after usage. + * The manager also handles deletion of SAs. + * + * @todo checking of double-checkouts from the same threads would be nice. + * This could be done by comparing thread-ids via pthread_self()... + * + * @todo Managing of ike_sa_t objects in a hash table instead of linked list. + * + * @b Constructors: + * - ike_sa_manager_create() + * + * @ingroup sa + */ +struct ike_sa_manager_t { + + /** + * @brief Checkout an existing IKE_SA. + * + * @param this the manager object + * @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); + + /** + * @brief Create and check out a new IKE_SA. + * + * @param this the manager object + * @param initiator TRUE for initiator, FALSE otherwise + * @returns created andchecked out IKE_SA + */ + ike_sa_t* (*checkout_new) (ike_sa_manager_t* this, bool initiator); + + /** + * @brief 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 folloing 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 unneded 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 this the manager object + * @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); + + /** + * @brief Checkout an existing IKE_SA by hosts and identifications. + * + * Allows the lookup of an IKE_SA by user IDs and hosts. It returns the + * first found occurence, if there are multiple candidates. Supplied IDs + * may contain wildcards, hosts may be %any. + * 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 this the manager object + * @param my_host address of our host + * @param other_id address of remote host + * @param my_id ID used by us + * @param other_id ID used by remote + * @return checked out/created IKE_SA + */ + ike_sa_t* (*checkout_by_peer) (ike_sa_manager_t* this, + host_t *my_host, host_t* other_host, + identification_t *my_id, + identification_t *other_id); + + /** + * @brief 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 this the manager object + * @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); + + /** + * @brief Check out an IKE_SA by the policy/connection name. + * + * Check out the IKE_SA by the connections name or by a CHILD_SAs policy + * name. + * + * @param this the manager object + * @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); + + /** + * @brief Create an iterator over all stored IKE_SAs. + * + * The avoid synchronization issues, the iterator locks access + * to the manager exclusively, until it gets destroyed. + * This iterator is for reading only! Writing will corrupt the manager. + * + * @param this the manager object + * @return iterator over all IKE_SAs. + */ + iterator_t *(*create_iterator) (ike_sa_manager_t* this); + + /** + * @brief Checkin the SA after usage. + * + * @warning the SA pointer MUST NOT be used after checkin! + * The SA must be checked out again! + * + * @param this the manager object + * @param ike_sa_id the SA identifier, will be updated + * @param ike_sa checked out SA + * @returns + * - SUCCESS if checked in + * - NOT_FOUND when not found (shouldn't happen!) + */ + status_t (*checkin) (ike_sa_manager_t* this, ike_sa_t *ike_sa); + + /** + * @brief 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 this the manager object + * @param ike_sa SA to delete + * @returns + * - SUCCESS if found + * - NOT_FOUND when no such SA is available + */ + status_t (*checkin_and_destroy) (ike_sa_manager_t* this, ike_sa_t *ike_sa); + + /** + * @brief 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 this the manager object + * @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); + + /** + * @brief Destroys the manager with all associated SAs. + * + * Threads will be driven out, so all SAs can be deleted cleanly. + * + * @param this the manager object + */ + void (*destroy) (ike_sa_manager_t *this); +}; + +/** + * @brief Create a manager. + * + * @returns ike_sa_manager_t object + * + * @ingroup sa + */ +ike_sa_manager_t *ike_sa_manager_create(void); + +#endif /*IKE_SA_MANAGER_H_*/ diff --git a/src/charon/sa/task_manager.c b/src/charon/sa/task_manager.c new file mode 100644 index 000000000..844300735 --- /dev/null +++ b/src/charon/sa/task_manager.c @@ -0,0 +1,854 @@ +/** + * @file task_manager.c + * + * @brief Implementation of task_manager_t. + * + */ + +/* + * 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 <daemon.h> +#include <sa/tasks/ike_init.h> +#include <sa/tasks/ike_natd.h> +#include <sa/tasks/ike_auth.h> +#include <sa/tasks/ike_cert.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/child_create.h> +#include <sa/tasks/child_rekey.h> +#include <sa/tasks/child_delete.h> +#include <encoding/payloads/delete_payload.h> +#include <queues/jobs/retransmit_job.h> + +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; +}; + +/** + * flush all tasks in the task manager + */ +static void flush(private_task_manager_t *this) +{ + task_t *task; + + this->queued_tasks->destroy_offset(this->queued_tasks, + offsetof(task_t, destroy)); + this->passive_tasks->destroy_offset(this->passive_tasks, + offsetof(task_t, destroy)); + + /* emmit outstanding signals for tasks */ + while (this->active_tasks->remove_last(this->active_tasks, + (void**)&task) == SUCCESS) + { + switch (task->get_type(task)) + { + case IKE_AUTH: + SIG(IKE_UP_FAILED, "establishing IKE_SA failed"); + break; + case IKE_DELETE: + SIG(IKE_DOWN_FAILED, "IKE_SA deleted"); + break; + case IKE_REKEY: + SIG(IKE_REKEY_FAILED, "rekeying IKE_SA failed"); + break; + case CHILD_CREATE: + SIG(CHILD_UP_FAILED, "establishing CHILD_SA failed"); + break; + case CHILD_DELETE: + SIG(CHILD_DOWN_FAILED, "deleting CHILD_SA failed"); + break; + case CHILD_REKEY: + SIG(IKE_REKEY_FAILED, "rekeying CHILD_SA failed"); + break; + default: + break; + } + task->destroy(task); + } + this->queued_tasks = linked_list_create(); + this->passive_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; + + timeout = charon->configuration->get_retransmit_timeout( + charon->configuration, this->initiating.retransmitted); + if (timeout == 0) + { + DBG1(DBG_IKE, "giving up after %d retransmits", + this->initiating.retransmitted - 1); + return DESTROY_ME; + } + + if (this->initiating.retransmitted) + { + DBG1(DBG_IKE, "retransmit %d of request with message ID %d", + this->initiating.retransmitted, message_id); + } + this->initiating.retransmitted++; + + charon->sender->send(charon->sender, + this->initiating.packet->clone(this->initiating.packet)); + job = (job_t*)retransmit_job_create(this->initiating.mid, + this->ike_sa->get_id(this->ike_sa)); + charon->event_queue->add_relative(charon->event_queue, 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; + 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: + if (activate_task(this, IKE_INIT)) + { + exchange = IKE_SA_INIT; + activate_task(this, IKE_NATD); + activate_task(this, IKE_CERT); + activate_task(this, IKE_AUTHENTICATE); + activate_task(this, IKE_CONFIG); + activate_task(this, CHILD_CREATE); + } + break; + case IKE_ESTABLISHED: + if (activate_task(this, CHILD_CREATE)) + { + exchange = CREATE_CHILD_SA; + activate_task(this, IKE_CONFIG); + 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_DEADPEER)) + { + exchange = INFORMATIONAL; + break; + } + 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; + default: + continue; + } + break; + } + iterator->destroy(iterator); + } + + if (exchange == 0) + { + DBG2(DBG_IKE, "nothing to initiate"); + /* nothing to do yet... */ + return SUCCESS; + } + + message = message_create(); + message->set_message_id(message, this->initiating.mid); + 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: + /* critical failure, destroy IKE_SA */ + iterator->destroy(iterator); + message->destroy(message); + flush(this); + return DESTROY_ME; + } + } + iterator->destroy(iterator); + + DESTROY_IF(this->initiating.packet); + status = this->ike_sa->generate_message(this->ike_sa, message, + &this->initiating.packet); + message->destroy(message); + if (status != SUCCESS) + { + /* message generation failed. There is nothing more to do than to + * close the SA */ + flush(this); + return DESTROY_ME; + } + + 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); + return DESTROY_ME; + } + + 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: + /* critical failure, destroy IKE_SA */ + iterator->destroy(iterator); + return DESTROY_ME; + } + } + iterator->destroy(iterator); + + this->initiating.mid++; + this->initiating.type = EXCHANGE_TYPE_UNDEFINED; + + 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) + { + /* 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) + { + 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, + exchange_type_t exchange) +{ + iterator_t *iterator; + task_t *task; + message_t *message; + bool delete = FALSE; + status_t status; + + message = message_create(); + message->set_exchange_type(message, exchange); + 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: + /* 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 && exchange == 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); + status = this->ike_sa->generate_message(this->ike_sa, message, + &this->responding.packet); + message->destroy(message); + if (status != SUCCESS) + { + 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) +{ + iterator_t *iterator; + task_t *task = NULL; + exchange_type_t exchange; + payload_t *payload; + notify_payload_t *notify; + + exchange = message->get_exchange_type(message); + + /* create tasks depending on request type */ + switch (exchange) + { + case IKE_SA_INIT: + { + 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_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t*)ike_auth_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t*)ike_config_create(this->ike_sa, NULL); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t*)child_create_create(this->ike_sa, NULL); + this->passive_tasks->insert_last(this->passive_tasks, task); + break; + } + case CREATE_CHILD_SA: + { + bool notify_found = FALSE, ts_found = FALSE; + iterator = message->get_payload_iterator(message); + while (iterator->iterate(iterator, (void**)&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; + } + } + iterator->destroy(iterator); + + if (ts_found) + { + if (notify_found) + { + task = (task_t*)child_rekey_create(this->ike_sa, NULL); + } + else + { + task = (task_t*)child_create_create(this->ike_sa, NULL); + } + } + else + { + task = (task_t*)ike_rekey_create(this->ike_sa, FALSE); + } + this->passive_tasks->insert_last(this->passive_tasks, task); + break; + } + case INFORMATIONAL: + { + delete_payload_t *delete; + + delete = (delete_payload_t*)message->get_payload(message, DELETE); + if (delete) + { + if (delete->get_protocol_id(delete) == PROTO_IKE) + { + task = (task_t*)ike_delete_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + } + else + { + task = (task_t*)child_delete_create(this->ike_sa, NULL); + this->passive_tasks->insert_last(this->passive_tasks, task); + } + } + else + { + task = (task_t*)ike_dpd_create(FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + } + break; + } + 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: + /* critical failure, destroy IKE_SA */ + iterator->destroy(iterator); + return DESTROY_ME; + } + } + iterator->destroy(iterator); + + return build_response(this, exchange); +} + +/** + * 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) + { + if (process_request(this, msg) != SUCCESS) + { + flush(this); + return DESTROY_ME; + } + this->responding.mid++; + } + else if ((mid == this->responding.mid - 1) && this->responding.packet) + { + DBG1(DBG_IKE, "received retransmit of request with ID %d, " + "retransmitting response", mid); + charon->sender->send(charon->sender, + this->responding.packet->clone(this->responding.packet)); + } + else + { + DBG1(DBG_IKE, "received message ID %d, excepted %d. Ignored", + mid, this->responding.mid); + } + } + else + { + if (mid == this->initiating.mid) + { + if (process_response(this, msg) != SUCCESS) + { + flush(this); + return DESTROY_ME; + } + } + else + { + DBG1(DBG_IKE, "received message ID %d, excepted %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) +{ + 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); + } + + /* reset active tasks and move them to others queued tasks */ + while (other->active_tasks->remove_last(other->active_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) +{ + 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; + this->responding.mid = 0; + this->initiating.mid = -1; + 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); + } +} + +/** + * 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*))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(); + + return &this->public; +} diff --git a/src/charon/sa/task_manager.h b/src/charon/sa/task_manager.h new file mode 100644 index 000000000..c766d4a65 --- /dev/null +++ b/src/charon/sa/task_manager.h @@ -0,0 +1,144 @@ +/** + * @file task_manager.h + * + * @brief Interface of task_manager_t. + * + */ + +/* + * 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. + */ + +#ifndef TASK_MANAGER_H_ +#define TASK_MANAGER_H_ + +typedef struct task_manager_t task_manager_t; + +#include <library.h> +#include <encoding/message.h> +#include <sa/ike_sa.h> +#include <sa/tasks/task.h> + +/** + * @brief 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. + * + * @b Constructors: + * - task_manager_create() + * + * @ingroup sa + */ +struct task_manager_t { + + /** + * @brief Process an incoming message. + * + * @param this calling object + * @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); + + /** + * @brief Initiate an exchange with the currently queued tasks. + * + * @param this calling object + */ + status_t (*initiate) (task_manager_t *this); + + /** + * @brief Queue a task in the manager. + * + * @param this calling object + * @param task task to queue + */ + void (*queue_task) (task_manager_t *this, task_t *task); + + /** + * @brief 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 this calling object + * @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); + + /** + * @brief 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 this manager which gets all tasks + * @param other manager which gives away its tasks + */ + void (*adopt_tasks) (task_manager_t *this, task_manager_t *other); + + /** + * @brief 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. + * + * @param this calling object + * @param other manager which gives away its tasks + */ + void (*reset) (task_manager_t *this); + + /** + * @brief Check if we are currently waiting for a reply. + * + * @param this calling object + * @return TRUE if we are waiting, FALSE otherwise + */ + bool (*busy) (task_manager_t *this); + + /** + * @brief Destroy the task_manager_t. + * + * @param this calling object + */ + void (*destroy) (task_manager_t *this); +}; + +/** + * @brief Create an instance of the task manager. + * + * @param ike_sa IKE_SA to manage. + * + * @ingroup sa + */ +task_manager_t *task_manager_create(ike_sa_t *ike_sa); + +#endif /* TASK_MANAGER_H_ */ diff --git a/src/charon/sa/tasks/child_create.c b/src/charon/sa/tasks/child_create.c new file mode 100644 index 000000000..781d679f2 --- /dev/null +++ b/src/charon/sa/tasks/child_create.c @@ -0,0 +1,804 @@ +/** + * @file child_create.c + * + * @brief Implementation of the child_create task. + * + */ + +/* + * 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_create.h" + +#include <daemon.h> +#include <crypto/diffie_hellman.h> +#include <encoding/payloads/sa_payload.h> +#include <encoding/payloads/ts_payload.h> +#include <encoding/payloads/nonce_payload.h> +#include <encoding/payloads/notify_payload.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; + + /** + * policy to create the CHILD_SA from + */ + policy_t *policy; + + /** + * 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; + + /** + * mode the new CHILD_SA uses (transport/tunnel/beet) + */ + mode_t mode; + + /** + * 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; +}; + +/** + * 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) +{ + status_t status; + randomizer_t *randomizer = randomizer_create(); + + status = randomizer->allocate_pseudo_random_bytes(randomizer, NONCE_SIZE, + nonce); + randomizer->destroy(randomizer); + if (status != SUCCESS) + { + DBG1(DBG_IKE, "error generating random nonce value"); + return FAILED; + } + 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; +} + +/** + * Install a CHILD_SA for usage + */ +static status_t select_and_install(private_child_create_t *this) +{ + prf_plus_t *prf_plus; + status_t status; + chunk_t nonce_i, nonce_r, seed; + linked_list_t *my_ts, *other_ts; + host_t *me, *other, *other_vip, *my_vip; + + if (this->proposals == NULL || this->tsi == NULL || this->tsr == NULL) + { + SIG(CHILD_UP_FAILED, "SA/TS payloads missing in message"); + return FAILED; + } + + 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; + } + + 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); + + this->proposal = this->policy->select_proposal(this->policy, this->proposals); + + if (this->proposal == NULL) + { + SIG(CHILD_UP_FAILED, "no acceptable proposal found"); + return FAILED; + } + + if (this->initiator && my_vip) + { /* if we have a virtual IP, shorten our TS to the minimum */ + my_ts = this->policy->select_my_traffic_selectors(this->policy, my_ts, + my_vip); + /* to setup firewall rules correctly, CHILD_SA needs the virtual IP */ + this->child_sa->set_virtual_ip(this->child_sa, my_vip); + } + else + { /* shorten in the host2host case only */ + my_ts = this->policy->select_my_traffic_selectors(this->policy, + my_ts, me); + } + if (other_vip) + { /* if other has a virtual IP, shorten it's traffic selectors to it */ + other_ts = this->policy->select_other_traffic_selectors(this->policy, + other_ts, other_vip); + } + else + { /* use his host for the host2host case */ + other_ts = this->policy->select_other_traffic_selectors(this->policy, + other_ts, other); + } + 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->tsi->get_count(this->tsi) == 0 || + this->tsr->get_count(this->tsr) == 0) + { + SIG(CHILD_UP_FAILED, "no acceptable traffic selectors found"); + return FAILED; + } + + if (!this->initiator) + { + /* check if requested mode is acceptable, downgrade if required */ + switch (this->mode) + { + case MODE_TRANSPORT: + if (!ts_list_is_host(this->tsi, other) || + !ts_list_is_host(this->tsr, me)) + { + this->mode = MODE_TUNNEL; + DBG1(DBG_IKE, "not using tranport mode, not host-to-host"); + } + else if (this->ike_sa->is_natt_enabled(this->ike_sa)) + { + this->mode = MODE_TUNNEL; + DBG1(DBG_IKE, "not using tranport 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; + } + } + + seed = chunk_cata("cc", nonce_i, nonce_r); + prf_plus = prf_plus_create(this->ike_sa->get_child_prf(this->ike_sa), seed); + + if (this->initiator) + { + status = this->child_sa->update(this->child_sa, this->proposal, + this->mode, prf_plus); + } + else + { + status = this->child_sa->add(this->child_sa, this->proposal, + this->mode, prf_plus); + } + prf_plus->destroy(prf_plus); + + if (status != SUCCESS) + { + SIG(CHILD_UP_FAILED, "unable to install IPsec SA (SAD) in kernel"); + return status; + } + + status = this->child_sa->add_policies(this->child_sa, my_ts, other_ts, + this->mode); + + if (status != SUCCESS) + { + SIG(CHILD_UP_FAILED, "unable to install IPsec policies (SPD) in kernel"); + return status; + } + /* 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; + 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; + ts_payload_t *ts_payload; + nonce_payload_t *nonce_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); + } + + /* 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; + } +} + +/** + * Read payloads from message + */ +static void process_payloads(private_child_create_t *this, message_t *message) +{ + iterator_t *iterator; + payload_t *payload; + sa_payload_t *sa_payload; + ts_payload_t *ts_payload; + notify_payload_t *notify_payload; + + /* defaults to TUNNEL mode */ + this->mode = MODE_TUNNEL; + + iterator = message->get_payload_iterator(message); + while (iterator->iterate(iterator, (void**)&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 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: + notify_payload = (notify_payload_t*)payload; + switch (notify_payload ->get_notify_type(notify_payload )) + { + case USE_TRANSPORT_MODE: + this->mode = MODE_TRANSPORT; + break; + case USE_BEET_MODE: + this->mode = MODE_BEET; + break; + default: + break; + } + break; + default: + break; + } + } + iterator->destroy(iterator); +} + +/** + * 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; + + 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; + } + break; + case IKE_AUTH: + if (!message->get_payload(message, ID_INITIATOR)) + { + /* send only in the first request, not in subsequent EAP */ + return NEED_MORE; + } + break; + default: + break; + } + + SIG(CHILD_UP_START, "establishing CHILD_SA"); + + me = this->ike_sa->get_my_host(this->ike_sa); + other = this->ike_sa->get_other_host(this->ike_sa); + vip = this->policy->get_virtual_ip(this->policy, NULL); + + if (vip) + { /* propose a 0.0.0.0/0 subnet when we use virtual ip */ + this->tsi = this->policy->get_my_traffic_selectors(this->policy, NULL); + vip->destroy(vip); + } + else + { /* but shorten a 0.0.0.0/0 subnet to the actual address if host2host */ + this->tsi = this->policy->get_my_traffic_selectors(this->policy, me); + } + this->tsr = this->policy->get_other_traffic_selectors(this->policy, other); + this->proposals = this->policy->get_proposals(this->policy); + this->mode = this->policy->get_mode(this->policy); + + this->child_sa = child_sa_create(me, other, + this->ike_sa->get_my_id(this->ike_sa), + this->ike_sa->get_other_id(this->ike_sa), + this->policy, this->reqid, + this->ike_sa->is_natt_enabled(this->ike_sa)); + + if (this->child_sa->alloc(this->child_sa, this->proposals) != SUCCESS) + { + SIG(CHILD_UP_FAILED, "unable to allocate SPIs from kernel"); + return FAILED; + } + + 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 initiator + */ +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_payload(message, ID_INITIATOR) == NULL) + { + /* wait until extensible authentication completed, if used */ + return NEED_MORE; + } + default: + break; + } + + process_payloads(this, message); + + if (this->tsi == NULL || this->tsr == NULL) + { + DBG1(DBG_IKE, "TS payload missing in message"); + return NEED_MORE; + } + + this->policy = charon->policies->get_policy(charon->policies, + this->ike_sa->get_my_id(this->ike_sa), + this->ike_sa->get_other_id(this->ike_sa), + this->tsr, this->tsi, + this->ike_sa->get_my_host(this->ike_sa), + this->ike_sa->get_other_host(this->ike_sa)); + + if (this->policy && this->ike_sa->get_policy(this->ike_sa) == NULL) + { + this->ike_sa->set_policy(this->ike_sa, this->policy); + } + + return NEED_MORE; +} + +/** + * Implementation of task_t.build for responder + */ +static status_t build_r(private_child_create_t *this, message_t *message) +{ + 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; + } + break; + case IKE_AUTH: + if (message->get_payload(message, EXTENSIBLE_AUTHENTICATION)) + { + /* wait until extensible authentication completed, if used */ + return NEED_MORE; + } + default: + break; + } + + if (this->ike_sa->get_state(this->ike_sa) == IKE_REKEYING) + { + SIG(CHILD_UP_FAILED, "unable to create CHILD_SA while rekeying IKE_SA"); + message->add_notify(message, TRUE, NO_ADDITIONAL_SAS, chunk_empty); + return SUCCESS; + } + + if (this->policy == NULL) + { + SIG(CHILD_UP_FAILED, "no acceptable policy found"); + message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN, chunk_empty); + return SUCCESS; + } + + 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->ike_sa->get_my_id(this->ike_sa), + this->ike_sa->get_other_id(this->ike_sa), + this->policy, this->reqid, + this->ike_sa->is_natt_enabled(this->ike_sa)); + + if (select_and_install(this) != SUCCESS) + { + message->add_notify(message, FALSE, TS_UNACCEPTABLE, chunk_empty); + return SUCCESS; + } + + build_payloads(this, message); + + SIG(CHILD_UP_SUCCESS, "established CHILD_SA successfully"); + + return SUCCESS; +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t process_i(private_child_create_t *this, message_t *message) +{ + iterator_t *iterator; + payload_t *payload; + + 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_payload(message, EXTENSIBLE_AUTHENTICATION)) + { + /* wait until extensible authentication completed, if used */ + return NEED_MORE; + } + default: + break; + } + + /* check for erronous notifies */ + iterator = message->get_payload_iterator(message); + while (iterator->iterate(iterator, (void**)&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: + { + SIG(CHILD_UP_FAILED, "received %N notify, no CHILD_SA built", + notify_type_names, type); + iterator->destroy(iterator); + /* an error in CHILD_SA creation is not critical */ + return SUCCESS; + } + default: + break; + } + } + } + iterator->destroy(iterator); + + process_payloads(this, message); + + if (select_and_install(this) == SUCCESS) + { + SIG(CHILD_UP_SUCCESS, "established CHILD_SA successfully"); + } + 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); + if (this->proposals) + { + this->proposals->destroy_offset(this->proposals, offsetof(proposal_t, destroy)); + } + + this->ike_sa = ike_sa; + this->proposals = NULL; + this->tsi = NULL; + this->tsr = NULL; + this->child_sa = NULL; + this->mode = MODE_TUNNEL; + 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->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)); + } + if (!this->established) + { + DESTROY_IF(this->child_sa); + } + DESTROY_IF(this->proposal); + if (this->proposals) + { + this->proposals->destroy_offset(this->proposals, offsetof(proposal_t, destroy)); + } + + DESTROY_IF(this->policy); + free(this); +} + +/* + * Described in header. + */ +child_create_t *child_create_create(ike_sa_t *ike_sa, policy_t *policy) +{ + 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 (policy) + { + 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; + policy->get_ref(policy); + } + 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->policy = policy; + this->my_nonce = chunk_empty; + this->other_nonce = chunk_empty; + this->proposals = NULL; + this->proposal = NULL; + this->tsi = NULL; + this->tsr = NULL; + this->child_sa = NULL; + this->mode = MODE_TUNNEL; + this->reqid = 0; + this->established = FALSE; + + return &this->public; +} diff --git a/src/charon/sa/tasks/child_create.h b/src/charon/sa/tasks/child_create.h new file mode 100644 index 000000000..200d37457 --- /dev/null +++ b/src/charon/sa/tasks/child_create.h @@ -0,0 +1,88 @@ +/** + * @file child_create.h + * + * @brief Interface child_create_t. + * + */ + +/* + * 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. + */ + +#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/policies/policy.h> + +/** + * @brief 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. + * + * @b Constructors: + * - child_create_create() + * + * @ingroup tasks + */ +struct child_create_t { + + /** + * Implements the task_t interface + */ + task_t task; + + /** + * @brief 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 this calling object + * @param reqid reqid to use + */ + void (*use_reqid) (child_create_t *this, u_int32_t reqid); + + /** + * @brief Get the lower of the two nonces, used for rekey collisions. + * + * @param this calling object + * @return lower nonce + */ + chunk_t (*get_lower_nonce) (child_create_t *this); + + /** + * @brief Get the CHILD_SA established/establishing by this task. + * + * @param this calling object + * @return child_sa + */ + child_sa_t* (*get_child) (child_create_t *this); +}; + +/** + * @brief Create a new child_create task. + * + * @param ike_sa IKE_SA this task works for + * @param policy policy if task initiator, NULL if responder + * @return child_create task to handle by the task_manager + */ +child_create_t *child_create_create(ike_sa_t *ike_sa, policy_t *policy); + +#endif /* CHILD_CREATE_H_ */ diff --git a/src/charon/sa/tasks/child_delete.c b/src/charon/sa/tasks/child_delete.c new file mode 100644 index 000000000..23d509de5 --- /dev/null +++ b/src/charon/sa/tasks/child_delete.c @@ -0,0 +1,292 @@ +/** + * @file child_delete.c + * + * @brief Implementation of the child_delete task. + * + */ + +/* + * 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; + + /** + * 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) +{ + iterator_t *iterator; + delete_payload_t *ah = NULL, *esp = NULL; + u_int32_t spi; + child_sa_t *child_sa; + + iterator = this->child_sas->create_iterator(this->child_sas, TRUE); + while (iterator->iterate(iterator, (void**)&child_sa)) + { + spi = child_sa->get_spi(child_sa, TRUE); + switch (child_sa->get_protocol(child_sa)) + { + case PROTO_ESP: + if (esp == NULL) + { + esp = delete_payload_create(PROTO_ESP); + message->add_payload(message, (payload_t*)esp); + } + esp->add_spi(esp, 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); + 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) +{ + iterator_t *payloads, *spis; + payload_t *payload; + delete_payload_t *delete_payload; + u_int32_t *spi; + protocol_id_t protocol; + child_sa_t *child_sa; + + payloads = message->get_payload_iterator(message); + while (payloads->iterate(payloads, (void**)&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 0x%x, " + "but no such SA", protocol_id_names, protocol, ntohl(*spi)); + continue; + } + DBG2(DBG_IKE, "received DELETE for %N CHILD_SA with SPI 0x%x", + protocol_id_names, protocol, ntohl(*spi)); + + switch (child_sa->get_state(child_sa)) + { + case CHILD_REKEYING: + /* 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; + } + 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 + */ +static void destroy_children(private_child_delete_t *this) +{ + iterator_t *iterator; + child_sa_t *child_sa; + protocol_id_t protocol; + u_int32_t spi; + + iterator = this->child_sas->create_iterator(this->child_sas, TRUE); + while (iterator->iterate(iterator, (void**)&child_sa)) + { + spi = child_sa->get_spi(child_sa, TRUE); + protocol = child_sa->get_protocol(child_sa); + this->ike_sa->destroy_child_sa(this->ike_sa, protocol, spi); + } + iterator->destroy(iterator); +} + +/** + * Implementation of task_t.build for initiator + */ +static status_t build_i(private_child_delete_t *this, message_t *message) +{ + 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); + destroy_children(this); + return SUCCESS; +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t process_r(private_child_delete_t *this, message_t *message) +{ + process_payloads(this, message); + 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); + } + destroy_children(this); + return SUCCESS; +} + +/** + * 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->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, child_sa_t *child_sa) +{ + 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->child_sas = linked_list_create(); + + if (child_sa != NULL) + { + 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_sas->insert_last(this->child_sas, child_sa); + } + 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/charon/sa/tasks/child_delete.h b/src/charon/sa/tasks/child_delete.h new file mode 100644 index 000000000..a7e676a50 --- /dev/null +++ b/src/charon/sa/tasks/child_delete.h @@ -0,0 +1,66 @@ +/** + * @file child_delete.h + * + * @brief Interface child_delete_t. + * + */ + +/* + * 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. + */ + +#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> + +/** + * @brief Task of type child_delete, delete a CHILD_SA. + * + * @b Constructors: + * - child_delete_create() + * + * @ingroup tasks + */ +struct child_delete_t { + + /** + * Implements the task_t interface + */ + task_t task; + + /** + * @brief Get the CHILD_SA to delete by this task. + * + * @param this calling object + * @return child_sa + */ + child_sa_t* (*get_child) (child_delete_t *this); +}; + +/** + * @brief Create a new child_delete task. + * + * @param ike_sa IKE_SA this task works for + * @param child_sa CHILD_SA to delete, or NULL as responder + * @return child_delete task to handle by the task_manager + */ +child_delete_t *child_delete_create(ike_sa_t *ike_sa, child_sa_t *child_sa); + +#endif /* CHILD_DELETE_H_ */ diff --git a/src/charon/sa/tasks/child_rekey.c b/src/charon/sa/tasks/child_rekey.c new file mode 100644 index 000000000..745895dbb --- /dev/null +++ b/src/charon/sa/tasks/child_rekey.c @@ -0,0 +1,346 @@ +/** + * @file child_rekey.c + * + * @brief Implementation of the child_rekey task. + * + */ + +/* + * 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 <queues/jobs/rekey_child_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; + + /** + * the CHILD_CREATE task which is reused to simplify rekeying + */ + child_create_t *child_create; + + /** + * CHILD_SA which gets rekeyed + */ + child_sa_t *child_sa; + + /** + * colliding task, may be delete or rekey + */ + task_t *collision; +}; + +/** + * find a child using the REKEY_SA notify + */ +static void find_child(private_child_rekey_t *this, message_t *message) +{ + iterator_t *iterator; + payload_t *payload; + + iterator = message->get_payload_iterator(message); + while (iterator->iterate(iterator, (void**)&payload)) + { + notify_payload_t *notify; + u_int32_t spi; + protocol_id_t protocol; + + if (payload->get_type(payload) != NOTIFY) + { + continue; + } + + notify = (notify_payload_t*)payload; + protocol = notify->get_protocol_id(notify); + spi = notify->get_spi(notify); + + if (protocol != PROTO_ESP && protocol != PROTO_AH) + { + continue; + } + this->child_sa = this->ike_sa->get_child_sa(this->ike_sa, protocol, + spi, FALSE); + break; + + } + iterator->destroy(iterator); +} + +/** + * Implementation of task_t.build for initiator + */ +static status_t build_i(private_child_rekey_t *this, message_t *message) +{ + notify_payload_t *notify; + protocol_id_t protocol; + u_int32_t spi, reqid; + + /* we just need the rekey notify ... */ + protocol = this->child_sa->get_protocol(this->child_sa); + spi = this->child_sa->get_spi(this->child_sa, TRUE); + notify = notify_payload_create_from_protocol_and_type(protocol, REKEY_SA); + notify->set_spi(notify, spi); + message->add_payload(message, (payload_t*)notify); + + /* ... our CHILD_CREATE task does the hard work for us. */ + 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); + 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; + + this->child_create->task.process(&this->child_create->task, message); + 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 = charon->configuration->get_retry_interval( + charon->configuration); + 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->event_queue->add_relative(charon->event_queue, job, retry * 1000); + } + 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; + } + } + } + + spi = to_delete->get_spi(to_delete, TRUE); + protocol = to_delete->get_protocol(to_delete); + if (this->ike_sa->delete_child_sa(this->ike_sa, protocol, spi) != SUCCESS) + { + return FAILED; + } + return SUCCESS; +} + +/** + * 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 */ + 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 */ + return; + } + } + else + { + /* any other task is not critical for collisisions, ignore */ + 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) +{ + this->child_create->task.migrate(&this->child_create->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) +{ + this->child_create->task.destroy(&this->child_create->task); + DESTROY_IF(this->collision); + free(this); +} + +/* + * Described in header. + */ +child_rekey_t *child_rekey_create(ike_sa_t *ike_sa, child_sa_t *child_sa) +{ + private_child_rekey_t *this = malloc_thing(private_child_rekey_t); + policy_t *policy; + + 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 (child_sa != NULL) + { + 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; + policy = child_sa->get_policy(child_sa); + this->child_create = child_create_create(ike_sa, policy); + } + 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); + } + + this->ike_sa = ike_sa; + this->child_sa = child_sa; + this->collision = NULL; + + return &this->public; +} diff --git a/src/charon/sa/tasks/child_rekey.h b/src/charon/sa/tasks/child_rekey.h new file mode 100644 index 000000000..3515f0c3f --- /dev/null +++ b/src/charon/sa/tasks/child_rekey.h @@ -0,0 +1,70 @@ +/** + * @file child_rekey.h + * + * @brief Interface child_rekey_t. + * + */ + +/* + * 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. + */ + +#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> + +/** + * @brief Task of type CHILD_REKEY, rekey an established CHILD_SA. + * + * @b Constructors: + * - child_rekey_create() + * + * @ingroup tasks + */ +struct child_rekey_t { + + /** + * Implements the task_t interface + */ + task_t task; + + /** + * @brief 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 this task initated by us + * @param other incoming task + */ + void (*collide)(child_rekey_t* this, task_t *other); +}; + +/** + * @brief Create a new CHILD_REKEY task. + * + * @param ike_sa IKE_SA this task works for + * @param child_sa child_sa to rekey, NULL if responder + * @return child_rekey task to handle by the task_manager + */ +child_rekey_t *child_rekey_create(ike_sa_t *ike_sa, child_sa_t *child_sa); + +#endif /* CHILD_REKEY_H_ */ diff --git a/src/charon/sa/tasks/ike_auth.c b/src/charon/sa/tasks/ike_auth.c new file mode 100644 index 000000000..541e1bb37 --- /dev/null +++ b/src/charon/sa/tasks/ike_auth.c @@ -0,0 +1,750 @@ +/** + * @file ike_auth.c + * + * @brief Implementation of the ike_auth task. + * + */ + +/* + * 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 "ike_auth.h" + +#include <string.h> + +#include <daemon.h> +#include <crypto/diffie_hellman.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; + + /** + * EAP authenticator when using EAP + */ + eap_authenticator_t *eap_auth; + + /** + * EAP payload received and ready to process + */ + eap_payload_t *eap_payload; + + /** + * has the peer been authenticated successfully? + */ + bool peer_authenticated; +}; + +/** + * build the AUTH payload + */ +static status_t build_auth(private_ike_auth_t *this, message_t *message) +{ + authenticator_t *auth; + auth_payload_t *auth_payload; + policy_t *policy; + auth_method_t method; + status_t status; + + /* create own authenticator and add auth payload */ + policy = this->ike_sa->get_policy(this->ike_sa); + if (!policy) + { + SIG(IKE_UP_FAILED, "unable to authenticate, no policy found"); + return FAILED; + } + method = policy->get_auth_method(policy); + + auth = authenticator_create(this->ike_sa, method); + if (auth == NULL) + { + SIG(IKE_UP_FAILED, "configured authentication method %N not supported", + auth_method_names, method); + return FAILED; + } + + status = auth->build(auth, this->my_packet->get_data(this->my_packet), + this->other_nonce, &auth_payload); + auth->destroy(auth); + if (status != SUCCESS) + { + SIG(IKE_UP_FAILED, "generating authentication data failed"); + return FAILED; + } + message->add_payload(message, (payload_t*)auth_payload); + return SUCCESS; +} + +/** + * build ID payload(s) + */ +static status_t build_id(private_ike_auth_t *this, message_t *message) +{ + identification_t *me, *other; + id_payload_t *id; + policy_t *policy; + + me = this->ike_sa->get_my_id(this->ike_sa); + other = this->ike_sa->get_other_id(this->ike_sa); + policy = this->ike_sa->get_policy(this->ike_sa); + + if (me->contains_wildcards(me)) + { + me = policy->get_my_id(policy); + if (me->contains_wildcards(me)) + { + SIG(IKE_UP_FAILED, "negotiation of own ID failed"); + return FAILED; + } + this->ike_sa->set_my_id(this->ike_sa, me->clone(me)); + } + + id = id_payload_create_from_identification(this->initiator, me); + message->add_payload(message, (payload_t*)id); + + /* as initiator, include other ID if it does not contain wildcards */ + if (this->initiator && !other->contains_wildcards(other)) + { + id = id_payload_create_from_identification(FALSE, other); + message->add_payload(message, (payload_t*)id); + } + return SUCCESS; +} + +/** + * process AUTH payload + */ +static status_t process_auth(private_ike_auth_t *this, message_t *message) +{ + auth_payload_t *auth_payload; + authenticator_t *auth; + auth_method_t auth_method; + status_t status; + + auth_payload = (auth_payload_t*)message->get_payload(message, AUTHENTICATION); + + if (auth_payload == NULL) + { + /* AUTH payload is missing, client wants to use EAP authentication */ + return NOT_FOUND; + } + + auth_method = auth_payload->get_auth_method(auth_payload); + auth = authenticator_create(this->ike_sa, auth_method); + + if (auth == NULL) + { + SIG(IKE_UP_FAILED, "authentication method %N used by %D not " + "supported", auth_method_names, auth_method, + this->ike_sa->get_other_id(this->ike_sa)); + return NOT_SUPPORTED; + } + status = auth->verify(auth, this->other_packet->get_data(this->other_packet), + this->my_nonce, auth_payload); + auth->destroy(auth); + if (status != SUCCESS) + { + SIG(IKE_UP_FAILED, "authentication of %D using %N failed", + this->ike_sa->get_other_id(this->ike_sa), + auth_method_names, auth_method); + return FAILED; + } + return SUCCESS; +} + +/** + * process ID payload(s) + */ +static status_t process_id(private_ike_auth_t *this, message_t *message) +{ + identification_t *id; + id_payload_t *idr, *idi; + + idi = (id_payload_t*)message->get_payload(message, ID_INITIATOR); + idr = (id_payload_t*)message->get_payload(message, ID_RESPONDER); + + if ((this->initiator && idr == NULL) || (!this->initiator && idi == NULL)) + { + SIG(IKE_UP_FAILED, "ID payload missing in message"); + return FAILED; + } + + if (this->initiator) + { + id = idr->get_identification(idr); + this->ike_sa->set_other_id(this->ike_sa, id); + } + else + { + id = idi->get_identification(idi); + this->ike_sa->set_other_id(this->ike_sa, id); + if (idr) + { + id = idr->get_identification(idr); + this->ike_sa->set_my_id(this->ike_sa, id); + } + } + return SUCCESS; +} + +/** + * 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, so we can store it for us */ + 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); + + /* pre-generate the message, so we can store it for us */ + this->other_packet = message->get_packet(message); + return NEED_MORE; +} + +/** + * Implementation of task_t.build to create AUTH payload from EAP data + */ +static status_t build_auth_eap(private_ike_auth_t *this, message_t *message) +{ + authenticator_t *auth; + auth_payload_t *auth_payload; + + auth = (authenticator_t*)this->eap_auth; + if (auth->build(auth, this->my_packet->get_data(this->my_packet), + this->other_nonce, &auth_payload) != SUCCESS) + { + SIG(IKE_UP_FAILED, "generating authentication data failed"); + if (!this->initiator) + { + message->add_notify(message, TRUE, AUTHENTICATION_FAILED, chunk_empty); + } + return FAILED; + } + message->add_payload(message, (payload_t*)auth_payload); + if (!this->initiator) + { + this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED); + SIG(IKE_UP_SUCCESS, "IKE_SA established between %D[%H]...[%H]%D", + this->ike_sa->get_my_id(this->ike_sa), + this->ike_sa->get_my_host(this->ike_sa), + this->ike_sa->get_other_host(this->ike_sa), + this->ike_sa->get_other_id(this->ike_sa)); + return SUCCESS; + } + return NEED_MORE; +} + +/** + * Implementation of task_t.process to verify AUTH payload after EAP + */ +static status_t process_auth_eap(private_ike_auth_t *this, message_t *message) +{ + auth_payload_t *auth_payload; + authenticator_t *auth; + + auth_payload = (auth_payload_t*)message->get_payload(message, AUTHENTICATION); + this->peer_authenticated = FALSE; + + if (auth_payload) + { + auth = (authenticator_t*)this->eap_auth; + if (auth->verify(auth, this->other_packet->get_data(this->other_packet), + this->my_nonce, auth_payload) == SUCCESS) + { + this->peer_authenticated = TRUE; + } + } + + if (!this->peer_authenticated) + { + SIG(IKE_UP_FAILED, "authentication of %D using %N failed", + this->ike_sa->get_other_id(this->ike_sa), + auth_method_names, AUTH_EAP); + if (this->initiator) + { + return FAILED; + } + return NEED_MORE; + } + if (this->initiator) + { + this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED); + SIG(IKE_UP_SUCCESS, "IKE_SA established between %D[%H]...[%H]%D", + this->ike_sa->get_my_id(this->ike_sa), + this->ike_sa->get_my_host(this->ike_sa), + this->ike_sa->get_other_host(this->ike_sa), + this->ike_sa->get_other_id(this->ike_sa)); + return SUCCESS; + } + return NEED_MORE; +} + +/** + * Implementation of task_t.process for EAP exchanges + */ +static status_t process_eap_i(private_ike_auth_t *this, message_t *message) +{ + eap_payload_t *eap; + + eap = (eap_payload_t*)message->get_payload(message, EXTENSIBLE_AUTHENTICATION); + if (eap == NULL) + { + SIG(IKE_UP_FAILED, "EAP payload missing"); + return FAILED; + } + switch (this->eap_auth->process(this->eap_auth, eap, &eap)) + { + case NEED_MORE: + this->eap_payload = eap; + return NEED_MORE; + case SUCCESS: + /* EAP exchange completed, now create and process AUTH */ + this->eap_payload = NULL; + this->public.task.build = (status_t(*)(task_t*,message_t*))build_auth_eap; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_auth_eap; + return NEED_MORE; + default: + this->eap_payload = NULL; + SIG(IKE_UP_FAILED, "failed to authenticate against %D using EAP", + this->ike_sa->get_other_id(this->ike_sa)); + return FAILED; + } +} + +/** + * Implementation of task_t.process for EAP exchanges + */ +static status_t process_eap_r(private_ike_auth_t *this, message_t *message) +{ + this->eap_payload = (eap_payload_t*)message->get_payload(message, + EXTENSIBLE_AUTHENTICATION); + return NEED_MORE; +} + +/** + * Implementation of task_t.build for EAP exchanges + */ +static status_t build_eap_i(private_ike_auth_t *this, message_t *message) +{ + message->add_payload(message, (payload_t*)this->eap_payload); + return NEED_MORE; +} + +/** + * Implementation of task_t.build for EAP exchanges + */ +static status_t build_eap_r(private_ike_auth_t *this, message_t *message) +{ + status_t status = NEED_MORE; + eap_payload_t *eap; + + if (this->eap_payload == NULL) + { + SIG(IKE_UP_FAILED, "EAP payload missing"); + return FAILED; + } + + switch (this->eap_auth->process(this->eap_auth, this->eap_payload, &eap)) + { + case NEED_MORE: + + break; + case SUCCESS: + /* EAP exchange completed, now create and process AUTH */ + this->public.task.build = (status_t(*)(task_t*,message_t*))build_auth_eap; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_auth_eap; + break; + default: + SIG(IKE_UP_FAILED, "authentication of %D using %N failed", + this->ike_sa->get_other_id(this->ike_sa), + auth_method_names, AUTH_EAP); + status = FAILED; + break; + } + message->add_payload(message, (payload_t*)eap); + return status; +} + +/** + * Implementation of task_t.build for initiator + */ +static status_t build_i(private_ike_auth_t *this, message_t *message) +{ + policy_t *policy; + + if (message->get_exchange_type(message) == IKE_SA_INIT) + { + return collect_my_init_data(this, message); + } + + if (build_id(this, message) != SUCCESS) + { + return FAILED; + } + + policy = this->ike_sa->get_policy(this->ike_sa); + if (policy->get_auth_method(policy) == AUTH_EAP) + { + this->eap_auth = eap_authenticator_create(this->ike_sa); + } + else + { + if (build_auth(this, message) != SUCCESS) + { + return FAILED; + } + } + + return NEED_MORE; +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t process_r(private_ike_auth_t *this, message_t *message) +{ + if (message->get_exchange_type(message) == IKE_SA_INIT) + { + return collect_other_init_data(this, message); + } + + if (process_id(this, message) != SUCCESS) + { + return NEED_MORE; + } + + switch (process_auth(this, message)) + { + case SUCCESS: + this->peer_authenticated = TRUE; + break; + case NOT_FOUND: + /* use EAP if no AUTH payload found */ + this->eap_auth = eap_authenticator_create(this->ike_sa); + break; + default: + break; + } + return NEED_MORE; +} + +/** + * Implementation of task_t.build for responder + */ +static status_t build_r(private_ike_auth_t *this, message_t *message) +{ + policy_t *policy; + eap_type_t eap_type; + eap_payload_t *eap_payload; + status_t status; + + if (message->get_exchange_type(message) == IKE_SA_INIT) + { + return collect_my_init_data(this, message); + } + + policy = this->ike_sa->get_policy(this->ike_sa); + if (policy == NULL) + { + SIG(IKE_UP_FAILED, "no acceptable policy found"); + message->add_notify(message, TRUE, AUTHENTICATION_FAILED, chunk_empty); + return FAILED; + } + + if (build_id(this, message) != SUCCESS || + build_auth(this, message) != SUCCESS) + { + message->add_notify(message, TRUE, AUTHENTICATION_FAILED, chunk_empty); + return FAILED; + } + + /* use "traditional" authentication if we could authenticate peer */ + if (this->peer_authenticated) + { + this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED); + SIG(IKE_UP_SUCCESS, "IKE_SA established between %D[%H]...[%H]%D", + this->ike_sa->get_my_id(this->ike_sa), + this->ike_sa->get_my_host(this->ike_sa), + this->ike_sa->get_other_host(this->ike_sa), + this->ike_sa->get_other_id(this->ike_sa)); + return SUCCESS; + } + + if (this->eap_auth == NULL) + { + /* peer not authenticated, nor does it want to use EAP */ + message->add_notify(message, TRUE, AUTHENTICATION_FAILED, chunk_empty); + return FAILED; + } + + /* initiate EAP authenitcation */ + eap_type = policy->get_eap_type(policy); + status = this->eap_auth->initiate(this->eap_auth, eap_type, &eap_payload); + message->add_payload(message, (payload_t*)eap_payload); + if (status != NEED_MORE) + { + SIG(IKE_UP_FAILED, "unable to initiate EAP authentication"); + return FAILED; + } + + /* switch to EAP methods */ + this->public.task.build = (status_t(*)(task_t*,message_t*))build_eap_r; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_eap_r; + return NEED_MORE; +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t process_i(private_ike_auth_t *this, message_t *message) +{ + iterator_t *iterator; + payload_t *payload; + + if (message->get_exchange_type(message) == IKE_SA_INIT) + { + return collect_other_init_data(this, message); + } + + iterator = message->get_payload_iterator(message); + while (iterator->iterate(iterator, (void**)&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; + default: + { + if (type < 16383) + { + SIG(IKE_UP_FAILED, "received %N notify error", + notify_type_names, type); + iterator->destroy(iterator); + return FAILED; + } + DBG1(DBG_IKE, "received %N notify", + notify_type_names, type); + break; + } + } + } + } + iterator->destroy(iterator); + + if (process_id(this, message) != SUCCESS || + process_auth(this, message) != SUCCESS) + { + return FAILED; + } + + if (this->eap_auth) + { + /* switch to EAP authentication methods */ + this->public.task.build = (status_t(*)(task_t*,message_t*))build_eap_i; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_eap_i; + return process_eap_i(this, message); + } + + this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED); + SIG(IKE_UP_SUCCESS, "IKE_SA established between %D[%H]...[%H]%D", + this->ike_sa->get_my_id(this->ike_sa), + this->ike_sa->get_my_host(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.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); + if (this->eap_auth) + { + this->eap_auth->authenticator_interface.destroy( + &this->eap_auth->authenticator_interface); + } + + this->my_packet = NULL; + this->other_packet = NULL; + this->peer_authenticated = FALSE; + this->eap_auth = NULL; + this->eap_payload = NULL; + this->ike_sa = ike_sa; + if (this->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; + } +} + +/** + * 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); + if (this->eap_auth) + { + this->eap_auth->authenticator_interface.destroy( + &this->eap_auth->authenticator_interface); + } + 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_authenticated = FALSE; + this->eap_auth = NULL; + this->eap_payload = NULL; + + return &this->public; +} diff --git a/src/charon/sa/tasks/ike_auth.h b/src/charon/sa/tasks/ike_auth.h new file mode 100644 index 000000000..d7326c988 --- /dev/null +++ b/src/charon/sa/tasks/ike_auth.h @@ -0,0 +1,64 @@ +/** + * @file ike_auth.h + * + * @brief Interface ike_auth_t. + * + */ + +/* + * 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. + */ + +#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> + +/** + * @brief 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. + * + * @b Constructors: + * - ike_auth_create() + * + * @ingroup tasks + */ +struct ike_auth_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * @brief 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/charon/sa/tasks/ike_cert.c b/src/charon/sa/tasks/ike_cert.c new file mode 100644 index 000000000..160600742 --- /dev/null +++ b/src/charon/sa/tasks/ike_cert.c @@ -0,0 +1,370 @@ +/** + * @file ike_cert.c + * + * @brief Implementation of the ike_cert task. + * + */ + +/* + * 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_cert.h" + +#include <daemon.h> +#include <sa/ike_sa.h> +#include <crypto/hashers/hasher.h> +#include <encoding/payloads/cert_payload.h> +#include <encoding/payloads/certreq_payload.h> + + +typedef struct private_ike_cert_t private_ike_cert_t; + +/** + * Private members of a ike_cert_t task. + */ +struct private_ike_cert_t { + + /** + * Public methods and task_t interface. + */ + ike_cert_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; + + /** + * Are we the initiator? + */ + bool initiator; + + /** + * list of CA cert hashes requested, items point to 20 byte chunk + */ + linked_list_t *cas; + + /** + * have we seen a certificate request? + */ + bool certreq_seen; +}; + +/** + * read certificate requests + */ +static void process_certreqs(private_ike_cert_t *this, message_t *message) +{ + iterator_t *iterator; + payload_t *payload; + + iterator = message->get_payload_iterator(message); + while (iterator->iterate(iterator, (void**)&payload)) + { + if (payload->get_type(payload) == CERTIFICATE_REQUEST) + { + certreq_payload_t *certreq = (certreq_payload_t*)payload; + cert_encoding_t encoding; + chunk_t keyids, keyid; + + this->certreq_seen = TRUE; + + encoding = certreq->get_cert_encoding(certreq); + if (encoding != CERT_X509_SIGNATURE) + { + DBG1(DBG_IKE, "certreq payload %N not supported, ignored", + cert_encoding_names, encoding); + continue; + } + + keyids = certreq->get_data(certreq); + + while (keyids.len >= HASH_SIZE_SHA1) + { + keyid = chunk_create(keyids.ptr, HASH_SIZE_SHA1); + keyid = chunk_clone(keyid); + this->cas->insert_last(this->cas, keyid.ptr); + keyids = chunk_skip(keyids, HASH_SIZE_SHA1); + } + } + } + iterator->destroy(iterator); +} + +/** + * import certificates + */ +static void process_certs(private_ike_cert_t *this, message_t *message) +{ + iterator_t *iterator; + payload_t *payload; + + iterator = message->get_payload_iterator(message); + while (iterator->iterate(iterator, (void**)&payload)) + { + if (payload->get_type(payload) == CERTIFICATE) + { + cert_encoding_t encoding; + x509_t *cert; + chunk_t cert_data; + bool found; + cert_payload_t *cert_payload = (cert_payload_t*)payload; + + encoding = cert_payload->get_cert_encoding(cert_payload); + if (encoding != CERT_X509_SIGNATURE) + { + DBG1(DBG_IKE, "certificate payload %N not supported, ignored", + cert_encoding_names, encoding); + continue; + } + + cert_data = cert_payload->get_data_clone(cert_payload); + cert = x509_create_from_chunk(cert_data, 0); + if (cert) + { + if (charon->credentials->verify(charon->credentials, + cert, &found)) + { + DBG2(DBG_IKE, "received end entity certificate is trusted, " + "added to store"); + if (!found) + { + charon->credentials->add_end_certificate( + charon->credentials, cert); + } + else + { + cert->destroy(cert); + } + } + else + { + DBG1(DBG_IKE, "received end entity certificate is not " + "trusted, discarded"); + cert->destroy(cert); + } + } + else + { + DBG1(DBG_IKE, "parsing of received certificate failed, discarded"); + chunk_free(&cert_data); + } + } + } + iterator->destroy(iterator); +} + +/** + * build certificate requests + */ +static void build_certreqs(private_ike_cert_t *this, message_t *message) +{ + connection_t *connection; + policy_t *policy; + identification_t *ca; + certreq_payload_t *certreq; + + connection = this->ike_sa->get_connection(this->ike_sa); + + if (connection->get_certreq_policy(connection) != CERT_NEVER_SEND) + { + policy = this->ike_sa->get_policy(this->ike_sa); + + if (policy) + { + ca = policy->get_other_ca(policy); + + if (ca && ca->get_type(ca) != ID_ANY) + { + certreq = certreq_payload_create_from_cacert(ca); + } + else + { + certreq = certreq_payload_create_from_cacerts(); + } + } + else + { + certreq = certreq_payload_create_from_cacerts(); + } + + if (certreq) + { + message->add_payload(message, (payload_t*)certreq); + } + } +} + +/** + * add certificates to message + */ +static void build_certs(private_ike_cert_t *this, message_t *message) +{ + policy_t *policy; + connection_t *connection; + x509_t *cert; + cert_payload_t *payload; + + policy = this->ike_sa->get_policy(this->ike_sa); + connection = this->ike_sa->get_connection(this->ike_sa); + + if (policy && policy->get_auth_method(policy) == AUTH_RSA) + { + switch (connection->get_cert_policy(connection)) + { + case CERT_NEVER_SEND: + break; + case CERT_SEND_IF_ASKED: + if (!this->certreq_seen) + { + break; + } + /* FALL */ + case CERT_ALWAYS_SEND: + { + /* TODO: respect CA cert request */ + cert = charon->credentials->get_certificate(charon->credentials, + policy->get_my_id(policy)); + if (cert) + { + payload = cert_payload_create_from_x509(cert); + message->add_payload(message, (payload_t*)payload); + } + } + } + } +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t build_i(private_ike_cert_t *this, message_t *message) +{ + if (message->get_exchange_type(message) == IKE_SA_INIT) + { + return NEED_MORE; + } + + build_certreqs(this, message); + build_certs(this, message); + + return NEED_MORE; +} + +/** + * Implementation of task_t.process for responder + */ +static status_t process_r(private_ike_cert_t *this, message_t *message) +{ + if (message->get_exchange_type(message) == IKE_SA_INIT) + { + return NEED_MORE; + } + + process_certreqs(this, message); + process_certs(this, message); + + return NEED_MORE; +} + +/** + * Implementation of task_t.build for responder + */ +static status_t build_r(private_ike_cert_t *this, message_t *message) +{ + if (message->get_exchange_type(message) == IKE_SA_INIT) + { + build_certreqs(this, message); + return NEED_MORE; + } + + build_certs(this, message); + + return SUCCESS; +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t process_i(private_ike_cert_t *this, message_t *message) +{ + if (message->get_exchange_type(message) == IKE_SA_INIT) + { + process_certreqs(this, message); + return NEED_MORE; + } + + process_certs(this, message); + return SUCCESS; +} + +/** + * Implementation of task_t.get_type + */ +static task_type_t get_type(private_ike_cert_t *this) +{ + return IKE_CERT; +} + +/** + * Implementation of task_t.migrate + */ +static void migrate(private_ike_cert_t *this, ike_sa_t *ike_sa) +{ + this->ike_sa = ike_sa; + + this->cas->destroy_function(this->cas, free); + this->cas = linked_list_create(); + this->certreq_seen = FALSE; +} + +/** + * Implementation of task_t.destroy + */ +static void destroy(private_ike_cert_t *this) +{ + this->cas->destroy_function(this->cas, free); + free(this); +} + +/* + * Described in header. + */ +ike_cert_t *ike_cert_create(ike_sa_t *ike_sa, bool initiator) +{ + private_ike_cert_t *this = malloc_thing(private_ike_cert_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->cas = linked_list_create(); + this->certreq_seen = FALSE; + + return &this->public; +} diff --git a/src/charon/sa/tasks/ike_cert.h b/src/charon/sa/tasks/ike_cert.h new file mode 100644 index 000000000..ba0283953 --- /dev/null +++ b/src/charon/sa/tasks/ike_cert.h @@ -0,0 +1,61 @@ +/** + * @file ike_cert.h + * + * @brief Interface ike_cert_t. + * + */ + +/* + * 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. + */ + +#ifndef IKE_CERT_H_ +#define IKE_CERT_H_ + +typedef struct ike_cert_t ike_cert_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/tasks/task.h> + +/** + * @brief Task of type ike_cert, exchanges certificates and + * certificate requests. + * + * @b Constructors: + * - ike_cert_create() + * + * @ingroup tasks + */ +struct ike_cert_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * @brief Create a new ike_cert 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 task to handle by the task_manager + */ +ike_cert_t *ike_cert_create(ike_sa_t *ike_sa, bool initiator); + +#endif /* IKE_CERT_H_ */ diff --git a/src/charon/sa/tasks/ike_config.c b/src/charon/sa/tasks/ike_config.c new file mode 100644 index 000000000..ce29b9220 --- /dev/null +++ b/src/charon/sa/tasks/ike_config.c @@ -0,0 +1,428 @@ +/** + * @file ike_config.c + * + * @brief Implementation of the ike_config task. + * + */ + +/* + * 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 <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; + + /** + * associated policy with virtual IP configuration + */ + policy_t *policy; + + /** + * virtual ip + */ + host_t *virtual_ip; + + /** + * list of DNS servers + */ + linked_list_t *dns; +}; + +/** + * build configuration payloads and attributes + */ +static void build_payloads(private_ike_config_t *this, message_t *message, + config_type_t type) +{ + cp_payload_t *cp; + configuration_attribute_t *ca; + chunk_t chunk, prefix; + + if (!this->virtual_ip) + { + return; + } + + cp = cp_payload_create(); + cp->set_config_type(cp, type); + + ca = configuration_attribute_create(); + + if (this->virtual_ip->get_family(this->virtual_ip) == AF_INET) + { + ca->set_type(ca, INTERNAL_IP4_ADDRESS); + if (this->virtual_ip->is_anyaddr(this->virtual_ip)) + { + chunk = chunk_empty; + } + else + { + chunk = this->virtual_ip->get_address(this->virtual_ip); + } + } + else + { + ca->set_type(ca, INTERNAL_IP6_ADDRESS); + if (this->virtual_ip->is_anyaddr(this->virtual_ip)) + { + chunk = chunk_empty; + } + else + { + prefix = chunk_alloca(1); + *prefix.ptr = 64; + chunk = this->virtual_ip->get_address(this->virtual_ip); + chunk = chunk_cata("cc", chunk, prefix); + } + } + ca->set_value(ca, chunk); + cp->add_configuration_attribute(cp, ca); + + /* we currently always add a DNS request if we request an IP */ + if (this->initiator) + { + ca = configuration_attribute_create(); + if (this->virtual_ip->get_family(this->virtual_ip) == AF_INET) + { + ca->set_type(ca, INTERNAL_IP4_DNS); + } + else + { + ca->set_type(ca, INTERNAL_IP6_DNS); + } + cp->add_configuration_attribute(cp, ca); + } + else + { + host_t *ip; + iterator_t *iterator = this->dns->create_iterator(this->dns, TRUE); + while (iterator->iterate(iterator, (void**)&ip)) + { + ca = configuration_attribute_create(); + if (ip->get_family(ip) == AF_INET) + { + ca->set_type(ca, INTERNAL_IP4_DNS); + } + else + { + ca->set_type(ca, INTERNAL_IP6_DNS); + } + chunk = ip->get_address(ip); + ca->set_value(ca, chunk); + cp->add_configuration_attribute(cp, ca); + } + iterator->destroy(iterator); + } + message->add_payload(message, (payload_t*)cp); +} + +/** + * 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 && !this->virtual_ip) + { + this->virtual_ip = ip; + } + break; + } + case INTERNAL_IP4_DNS: + family = AF_INET; + /* fall */ + case INTERNAL_IP6_DNS: + { + addr = ca->get_value(ca); + if (addr.len == 0) + { + ip = host_create_any(family); + } + else + { + ip = host_create_from_chunk(family, addr, 0); + } + if (ip) + { + this->dns->insert_last(this->dns, ip); + } + break; + } + case INTERNAL_IP4_NBNS: + case INTERNAL_IP6_NBNS: + /* TODO */ + default: + DBG1(DBG_IKE, "ignoring %N config attribute", + configuration_attribute_type_names, + ca->get_type(ca)); + break; + } +} + +/** + * Scan for configuration payloads and attributes + */ +static void process_payloads(private_ike_config_t *this, message_t *message) +{ + iterator_t *iterator, *attributes; + payload_t *payload; + + iterator = message->get_payload_iterator(message); + while (iterator->iterate(iterator, (void**)&payload)) + { + if (payload->get_type(payload) == CONFIGURATION) + { + cp_payload_t *cp = (cp_payload_t*)payload; + configuration_attribute_t *ca; + switch (cp->get_config_type(cp)) + { + case CFG_REQUEST: + case CFG_REPLY: + { + attributes = cp->create_attribute_iterator(cp); + while (attributes->iterate(attributes, (void**)&ca)) + { + process_attribute(this, ca); + } + attributes->destroy(attributes); + break; + } + default: + DBG1(DBG_IKE, "ignoring %N config payload", + config_type_names, cp->get_config_type(cp)); + break; + } + } + } + iterator->destroy(iterator); +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t build_i(private_ike_config_t *this, message_t *message) +{ + if (message->get_exchange_type(message) == IKE_AUTH && + message->get_payload(message, ID_INITIATOR)) + { + this->virtual_ip = this->policy->get_virtual_ip(this->policy, NULL); + + build_payloads(this, message, CFG_REQUEST); + } + + 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_exchange_type(message) == IKE_AUTH && + message->get_payload(message, ID_INITIATOR)) + { + 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 (message->get_exchange_type(message) == IKE_AUTH && + message->get_payload(message, EXTENSIBLE_AUTHENTICATION) == NULL) + { + this->policy = this->ike_sa->get_policy(this->ike_sa); + + if (this->policy && this->virtual_ip) + { + host_t *ip; + + DBG1(DBG_IKE, "peer requested virtual IP %H", this->virtual_ip); + ip = this->policy->get_virtual_ip(this->policy, this->virtual_ip); + if (ip == NULL || ip->is_anyaddr(ip)) + { + DBG1(DBG_IKE, "not assigning a virtual IP to peer"); + return SUCCESS; + } + DBG1(DBG_IKE, "assigning virtual IP %H to peer", ip); + this->ike_sa->set_virtual_ip(this->ike_sa, FALSE, ip); + + this->virtual_ip->destroy(this->virtual_ip); + this->virtual_ip = ip; + + /* DNS testing values + if (this->dns->remove_last(this->dns, (void**)&ip) == SUCCESS) + { + ip->destroy(ip); + ip = host_create_from_string("10.3.0.1", 0); + this->dns->insert_last(this->dns, ip); + ip = host_create_from_string("10.3.0.2", 0); + this->dns->insert_last(this->dns, ip); + } */ + + build_payloads(this, message, CFG_REPLY); + } + 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 (message->get_exchange_type(message) == IKE_AUTH && + !message->get_payload(message, EXTENSIBLE_AUTHENTICATION)) + { + host_t *ip; + + DESTROY_IF(this->virtual_ip); + this->virtual_ip = NULL; + + process_payloads(this, message); + + if (this->virtual_ip) + { + this->ike_sa->set_virtual_ip(this->ike_sa, TRUE, this->virtual_ip); + + while (this->dns->remove_last(this->dns, (void**)&ip) == SUCCESS) + { + if (!ip->is_anyaddr(ip)) + { + this->ike_sa->add_dns_server(this->ike_sa, ip); + } + ip->destroy(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->dns->destroy_offset(this->dns, offsetof(host_t, destroy)); + + this->ike_sa = ike_sa; + this->virtual_ip = NULL; + this->dns = linked_list_create(); +} + +/** + * Implementation of task_t.destroy + */ +static void destroy(private_ike_config_t *this) +{ + DESTROY_IF(this->virtual_ip); + this->dns->destroy_offset(this->dns, offsetof(host_t, destroy)); + free(this); +} + +/* + * Described in header. + */ +ike_config_t *ike_config_create(ike_sa_t *ike_sa, policy_t *policy) +{ + 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; + + if (policy) + { + 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; + } + + this->ike_sa = ike_sa; + this->policy = policy; + this->virtual_ip = NULL; + this->dns = linked_list_create(); + + return &this->public; +} diff --git a/src/charon/sa/tasks/ike_config.h b/src/charon/sa/tasks/ike_config.h new file mode 100644 index 000000000..0c9b961b4 --- /dev/null +++ b/src/charon/sa/tasks/ike_config.h @@ -0,0 +1,59 @@ +/** + * @file ike_config.h + * + * @brief Interface ike_config_t. + * + */ + +/* + * 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. + */ + +#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> +#include <config/policies/policy.h> + +/** + * @brief Task of type IKE_CONFIG, sets up a virtual IP and other + * configurations for an IKE_SA. + * + * @b Constructors: + * - ike_config_create() + * + * @ingroup tasks + */ +struct ike_config_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * @brief Create a new ike_config task. + * + * @param ike_sa IKE_SA this task works for + * @param policy policy for the initiator, NULL for the responder + * @return ike_config task to handle by the task_manager + */ +ike_config_t *ike_config_create(ike_sa_t *ike_sa, policy_t *policy); + +#endif /* IKE_CONFIG_H_ */ diff --git a/src/charon/sa/tasks/ike_delete.c b/src/charon/sa/tasks/ike_delete.c new file mode 100644 index 000000000..9c4fdac0e --- /dev/null +++ b/src/charon/sa/tasks/ike_delete.c @@ -0,0 +1,172 @@ +/** + * @file ike_delete.c + * + * @brief Implementation of the ike_delete task. + * + */ + +/* + * 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 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; + + delete_payload = delete_payload_create(PROTO_IKE); + message->add_payload(message, (payload_t*)delete_payload); + + this->ike_sa->set_state(this->ike_sa, IKE_DELETING); + + return NEED_MORE; +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t process_i(private_ike_delete_t *this, message_t *message) +{ + /* completed, delete IKE_SA by returning FAILED */ + return FAILED; +} + +/** + * Implementation of task_t.process for initiator + */ +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 */ + switch (this->ike_sa->get_state(this->ike_sa)) + { + case IKE_DELETING: + this->simultaneous = TRUE; + break; + case IKE_ESTABLISHED: + DBG1(DBG_IKE, "deleting IKE_SA on request"); + break; + case IKE_REKEYING: + DBG1(DBG_IKE, "initiated rekeying, but received delete for IKE_SA"); + 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) +{ + if (this->simultaneous) + { + /* wait for peers response for our delete request, but set a timeout */ + return SUCCESS; + } + /* completed, delete IKE_SA by returning FAILED */ + return FAILED; +} + +/** + * 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->simultaneous = FALSE; + + return &this->public; +} diff --git a/src/charon/sa/tasks/ike_delete.h b/src/charon/sa/tasks/ike_delete.h new file mode 100644 index 000000000..e8ec5ebbe --- /dev/null +++ b/src/charon/sa/tasks/ike_delete.h @@ -0,0 +1,57 @@ +/** + * @file ike_delete.h + * + * @brief Interface ike_delete_t. + * + */ + +/* + * 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. + */ + +#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> + +/** + * @brief Task of type ike_delete, delete an IKE_SA. + * + * @b Constructors: + * - ike_delete_create() + * + * @ingroup tasks + */ +struct ike_delete_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * @brief 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/charon/sa/tasks/ike_dpd.c b/src/charon/sa/tasks/ike_dpd.c new file mode 100644 index 000000000..1cb05c45c --- /dev/null +++ b/src/charon/sa/tasks/ike_dpd.c @@ -0,0 +1,106 @@ +/** + * @file ike_dpd.c + * + * @brief Implementation of the ike_dpd task. + * + */ + +/* + * 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_DEADPEER; +} + +/** + * 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/charon/sa/tasks/ike_dpd.h b/src/charon/sa/tasks/ike_dpd.h new file mode 100644 index 000000000..531b0502d --- /dev/null +++ b/src/charon/sa/tasks/ike_dpd.h @@ -0,0 +1,58 @@ +/** + * @file ike_dpd.h + * + * @brief Interface ike_dpd_t. + * + */ + +/* + * 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. + */ + +#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> + +/** + * @brief Task of type ike_dpd, detects dead peers. + * + * The DPD task actually does nothing, as a DPD has no associated payloads. + * + * @b Constructors: + * - ike_dpd_create() + * + * @ingroup tasks + */ +struct ike_dpd_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * @brief 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/charon/sa/tasks/ike_init.c b/src/charon/sa/tasks/ike_init.c new file mode 100644 index 000000000..0b493666a --- /dev/null +++ b/src/charon/sa/tasks/ike_init.c @@ -0,0 +1,598 @@ +/** + * @file ike_init.c + * + * @brief Implementation of the ike_init task. + * + */ + +/* + * 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 "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; + + /** + * Connection established by this IKE_SA + */ + connection_t *connection; + + /** + * diffie hellman group to use + */ + diffie_hellman_group_t dh_group; + + /** + * Diffie hellman object used to generate public DH value. + */ + diffie_hellman_t *diffie_hellman; + + /** + * 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->connection = this->ike_sa->get_connection(this->ike_sa); + + if (this->initiator) + { + proposal_list = this->connection->get_proposals(this->connection); + 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); + message->add_payload(message, (payload_t*)nonce_payload); + + ke_payload = ke_payload_create_from_diffie_hellman(this->diffie_hellman); + message->add_payload(message, (payload_t*)ke_payload); +} + +/** + * Read payloads from message + */ +static void process_payloads(private_ike_init_t *this, message_t *message) +{ + iterator_t *iterator; + payload_t *payload; + + iterator = message->get_payload_iterator(message); + while (iterator->iterate(iterator, (void**)&payload)) + { + switch (payload->get_type(payload)) + { + case SECURITY_ASSOCIATION: + { + sa_payload_t *sa_payload = (sa_payload_t*)payload; + linked_list_t *proposal_list; + + proposal_list = sa_payload->get_proposals(sa_payload); + this->proposal = this->connection->select_proposal( + this->connection, proposal_list); + proposal_list->destroy_offset(proposal_list, + offsetof(proposal_t, destroy)); + break; + } + case KEY_EXCHANGE: + { + ke_payload_t *ke_payload = (ke_payload_t*)payload; + diffie_hellman_group_t dh_group; + chunk_t key_data; + + dh_group = ke_payload->get_dh_group_number(ke_payload); + + if (this->initiator) + { + if (dh_group != this->dh_group) + { + DBG1(DBG_IKE, "received a DH group not requested (%N)", + diffie_hellman_group_names, dh_group); + break; + } + } + else + { + this->dh_group = dh_group; + if (!this->connection->check_dh_group(this->connection, + dh_group)) + { + break; + } + this->diffie_hellman = diffie_hellman_create(dh_group); + } + if (this->diffie_hellman) + { + key_data = ke_payload->get_key_exchange_data(ke_payload); + this->diffie_hellman->set_other_public_value(this->diffie_hellman, key_data); + } + break; + } + case NONCE: + { + nonce_payload_t *nonce_payload = (nonce_payload_t*)payload; + this->other_nonce = nonce_payload->get_nonce(nonce_payload); + break; + } + default: + break; + } + } + iterator->destroy(iterator); +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t build_i(private_ike_init_t *this, message_t *message) +{ + randomizer_t *randomizer; + status_t status; + + this->connection = this->ike_sa->get_connection(this->ike_sa); + SIG(IKE_UP_START, "initiating IKE_SA to %H", + this->connection->get_other_host(this->connection)); + this->ike_sa->set_state(this->ike_sa, IKE_CONNECTING); + + if (this->retry++ >= MAX_RETRIES) + { + SIG(IKE_UP_FAILED, "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->diffie_hellman) + { + this->dh_group = this->connection->get_dh_group(this->connection); + this->diffie_hellman = diffie_hellman_create(this->dh_group); + if (this->diffie_hellman == NULL) + { + SIG(IKE_UP_FAILED, "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) + { + randomizer = randomizer_create(); + status = randomizer->allocate_pseudo_random_bytes(randomizer, NONCE_SIZE, + &this->my_nonce); + randomizer->destroy(randomizer); + if (status != SUCCESS) + { + SIG(IKE_UP_FAILED, "error generating random nonce value"); + return FAILED; + } + } + + if (this->cookie.ptr) + { + message->add_notify(message, FALSE, COOKIE, this->cookie); + } + + build_payloads(this, message); + + + return NEED_MORE; +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t process_r(private_ike_init_t *this, message_t *message) +{ + randomizer_t *randomizer; + + this->connection = this->ike_sa->get_connection(this->ike_sa); + SIG(IKE_UP_FAILED, "%H is initiating an IKE_SA", + message->get_source(message)); + this->ike_sa->set_state(this->ike_sa, IKE_CONNECTING); + + randomizer = randomizer_create(); + if (randomizer->allocate_pseudo_random_bytes(randomizer, NONCE_SIZE, + &this->my_nonce) != SUCCESS) + { + DBG1(DBG_IKE, "error generating random nonce value"); + } + randomizer->destroy(randomizer); + + process_payloads(this, message); + + return NEED_MORE; +} + +/** + * Implementation of task_t.build for responder + */ +static status_t build_r(private_ike_init_t *this, message_t *message) +{ + chunk_t secret; + status_t status; + + /* check if we have everything we need */ + if (this->proposal == NULL || + this->other_nonce.len == 0 || this->my_nonce.len == 0) + { + SIG(IKE_UP_FAILED, "received proposals inacceptable"); + message->add_notify(message, TRUE, NO_PROPOSAL_CHOSEN, chunk_empty); + return FAILED; + } + + if (this->diffie_hellman == NULL || + this->diffie_hellman->get_shared_secret(this->diffie_hellman, + &secret) != SUCCESS) + { + chunk_t chunk; + u_int16_t dh_enc; + + SIG(IKE_UP_FAILED, "received inacceptable DH group (%N)", + diffie_hellman_group_names, this->dh_group); + this->dh_group = this->connection->get_dh_group(this->connection); + dh_enc = htons(this->dh_group); + chunk.ptr = (u_int8_t*)&dh_enc; + chunk.len = sizeof(dh_enc); + message->add_notify(message, TRUE, INVALID_KE_PAYLOAD, chunk); + DBG1(DBG_IKE, "requesting DH group %N", + diffie_hellman_group_names, this->dh_group); + return FAILED; + } + + + if (this->old_sa) + { + ike_sa_id_t *id; + prf_t *prf, *child_prf; + + /* Apply SPI if we are rekeying */ + id = this->ike_sa->get_id(this->ike_sa); + id->set_initiator_spi(id, this->proposal->get_spi(this->proposal)); + + /* setup crypto keys for the rekeyed SA */ + prf = this->old_sa->get_prf(this->old_sa); + child_prf = this->old_sa->get_child_prf(this->old_sa); + status = this->ike_sa->derive_keys(this->ike_sa, this->proposal, secret, + this->other_nonce, this->my_nonce, + FALSE, child_prf, prf); + } + else + { + /* setup crypto keys */ + status = this->ike_sa->derive_keys(this->ike_sa, this->proposal, secret, + this->other_nonce, this->my_nonce, + FALSE, NULL, NULL); + } + if (status != SUCCESS) + { + SIG(IKE_UP_FAILED, "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) +{ + chunk_t secret; + status_t status; + iterator_t *iterator; + payload_t *payload; + + /* check for erronous notifies */ + iterator = message->get_payload_iterator(message); + while (iterator->iterate(iterator, (void**)&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 old_dh_group; + + old_dh_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, old_dh_group, + diffie_hellman_group_names, this->dh_group); + if (!this->connection->check_dh_group(this->connection, + this->dh_group)) + { + DBG1(DBG_IKE, "requested DH group %N not acceptable, " + "giving up", diffie_hellman_group_names, + this->dh_group); + iterator->destroy(iterator); + return FAILED; + } + + this->ike_sa->reset(this->ike_sa); + + iterator->destroy(iterator); + return NEED_MORE; + } + case NAT_DETECTION_SOURCE_IP: + case NAT_DETECTION_DESTINATION_IP: + /* skip, handled in ike_natd_t */ + break; + case COOKIE: + { + chunk_free(&this->cookie); + this->cookie = chunk_clone(notify->get_notification_data(notify)); + this->ike_sa->reset(this->ike_sa); + iterator->destroy(iterator); + DBG1(DBG_IKE, "received %N notify", notify_type_names, type); + return NEED_MORE; + } + default: + { + if (type < 16383) + { + SIG(IKE_UP_FAILED, "received %N notify error", + notify_type_names, type); + iterator->destroy(iterator); + return FAILED; + } + DBG1(DBG_IKE, "received %N notify", + notify_type_names, type); + break; + } + } + } + } + iterator->destroy(iterator); + + process_payloads(this, message); + + /* check if we have everything */ + if (this->proposal == NULL || + this->other_nonce.len == 0 || this->my_nonce.len == 0) + { + SIG(IKE_UP_FAILED, "peers proposal selection invalid"); + return FAILED; + } + + if (this->diffie_hellman == NULL || + this->diffie_hellman->get_shared_secret(this->diffie_hellman, + &secret) != SUCCESS) + { + SIG(IKE_UP_FAILED, "peers DH group selection invalid"); + return FAILED; + } + + /* Apply SPI if we are rekeying */ + if (this->old_sa) + { + ike_sa_id_t *id; + prf_t *prf, *child_prf; + + id = this->ike_sa->get_id(this->ike_sa); + id->set_responder_spi(id, this->proposal->get_spi(this->proposal)); + + /* setup crypto keys for the rekeyed SA */ + prf = this->old_sa->get_prf(this->old_sa); + child_prf = this->old_sa->get_child_prf(this->old_sa); + status = this->ike_sa->derive_keys(this->ike_sa, this->proposal, secret, + this->my_nonce, this->other_nonce, + TRUE, child_prf, prf); + } + else + { + /* setup crypto keys for a new SA */ + status = this->ike_sa->derive_keys(this->ike_sa, this->proposal, secret, + this->my_nonce, this->other_nonce, + TRUE, NULL, NULL); + } + if (status != SUCCESS) + { + SIG(IKE_UP_FAILED, "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); + DESTROY_IF(this->diffie_hellman); + chunk_free(&this->other_nonce); + + this->ike_sa = ike_sa; + this->proposal = NULL; + this->diffie_hellman = diffie_hellman_create(this->dh_group); +} + +/** + * Implementation of task_t.destroy + */ +static void destroy(private_ike_init_t *this) +{ + DESTROY_IF(this->proposal); + DESTROY_IF(this->diffie_hellman); + 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->diffie_hellman = NULL; + this->my_nonce = chunk_empty; + this->other_nonce = chunk_empty; + this->cookie = chunk_empty; + this->proposal = NULL; + this->connection = NULL; + this->old_sa = old_sa; + this->retry = 0; + + return &this->public; +} diff --git a/src/charon/sa/tasks/ike_init.h b/src/charon/sa/tasks/ike_init.h new file mode 100644 index 000000000..f60c096e8 --- /dev/null +++ b/src/charon/sa/tasks/ike_init.h @@ -0,0 +1,68 @@ +/** + * @file ike_init.h + * + * @brief Interface ike_init_t. + * + */ + +/* + * 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. + */ + +#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> + +/** + * @brief Task of type IKE_INIT, creates an IKE_SA without authentication. + * + * The authentication of is handle in the ike_auth task. + * + * @b Constructors: + * - ike_init_create() + * + * @ingroup tasks + */ +struct ike_init_t { + + /** + * Implements the task_t interface + */ + task_t task; + + /** + * @brief Get the lower of the two nonces, used for rekey collisions. + * + * @param this calling object + * @return lower nonce + */ + chunk_t (*get_lower_nonce) (ike_init_t *this); +}; + +/** + * @brief 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/charon/sa/tasks/ike_natd.c b/src/charon/sa/tasks/ike_natd.c new file mode 100644 index 000000000..50b5d652b --- /dev/null +++ b/src/charon/sa/tasks/ike_natd.c @@ -0,0 +1,371 @@ +/** + * @file ike_natd.c + * + * @brief Implementation of the ike_natd task. + * + */ + +/* + * 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 <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; +}; + + +/** + * 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 requred 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 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_sa_id = this->ike_sa->get_id(this->ike_sa); + notify = notify_payload_create(); + notify->set_notify_type(notify, type); + hash = generate_natd_hash(this, ike_sa_id, host); + 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) +{ + iterator_t *iterator; + 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; + + /* Precompute NAT-D hashes for incoming NAT notify comparison */ + ike_sa_id = message->get_ike_sa_id(message); + me = this->ike_sa->get_my_host(this->ike_sa); + other = this->ike_sa->get_other_host(this->ike_sa); + 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); + + iterator = message->get_payload_iterator(message); + while (iterator->iterate(iterator, (void**)&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; + if (!this->dst_matched) + { + hash = notify->get_notification_data(notify); + DBG3(DBG_IKE, "received dst_hash %B", &hash); + if (chunk_equals(hash, dst_hash)) + { + this->dst_matched = TRUE; + } + } + 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; + } + } + iterator->destroy(iterator); + + chunk_free(&src_hash); + chunk_free(&dst_hash); + + if (this->src_seen && this->dst_seen) + { + if (!this->dst_matched) + { + this->ike_sa->enable_natt(this->ike_sa, TRUE); + } + if (!this->src_matched) + { + this->ike_sa->enable_natt(this->ike_sa, FALSE); + } + } +} + +/** + * 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 (this->ike_sa->is_natt_enabled(this->ike_sa)) + { + host_t *me, *other; + + me = this->ike_sa->get_my_host(this->ike_sa); + me->set_port(me, IKEV2_NATT_PORT); + other = this->ike_sa->get_other_host(this->ike_sa); + 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; + linked_list_t *list; + host_t *host; + + /* include one notify if our address is defined, all addresses otherwise */ + host = this->ike_sa->get_my_host(this->ike_sa); + if (host->is_anyaddr(host)) + { + /* TODO: we could get the src address from netlink!? */ + list = charon->kernel_interface->create_address_list(charon->kernel_interface); + while (list->remove_first(list, (void**)&host) == SUCCESS) + { + notify = build_natd_payload(this, NAT_DETECTION_SOURCE_IP, host); + host->destroy(host); + message->add_payload(message, (payload_t*)notify); + } + list->destroy(list); + } + else + { + notify = build_natd_payload(this, NAT_DETECTION_SOURCE_IP, host); + message->add_payload(message, (payload_t*)notify); + } + + host = this->ike_sa->get_other_host(this->ike_sa); + notify = build_natd_payload(this, NAT_DETECTION_DESTINATION_IP, host); + message->add_payload(message, (payload_t*)notify); + + 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_payload(message, SECURITY_ASSOCIATION) == NULL) + { + return SUCCESS; + } + + if (this->src_seen && this->dst_seen) + { + /* initiator seems to support NAT detection, add response */ + me = this->ike_sa->get_my_host(this->ike_sa); + notify = build_natd_payload(this, NAT_DETECTION_SOURCE_IP, me); + message->add_payload(message, (payload_t*)notify); + + other = this->ike_sa->get_other_host(this->ike_sa); + 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; +} + +/** + * Implementation of task_t.destroy + */ +static void destroy(private_ike_natd_t *this) +{ + this->hasher->destroy(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->ike_sa = ike_sa; + this->initiator = initiator; + this->hasher = hasher_create(HASH_SHA1); + this->src_seen = FALSE; + this->dst_seen = FALSE; + this->src_matched = FALSE; + this->dst_matched = FALSE; + + return &this->public; +} diff --git a/src/charon/sa/tasks/ike_natd.h b/src/charon/sa/tasks/ike_natd.h new file mode 100644 index 000000000..8d0cb58b4 --- /dev/null +++ b/src/charon/sa/tasks/ike_natd.h @@ -0,0 +1,57 @@ +/** + * @file ike_natd.h + * + * @brief Interface ike_natd_t. + * + */ + +/* + * 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. + */ + +#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> + +/** + * @brief Task of type ike_natd, detects NAT situation in IKE_SA_INIT exchange. + * + * @b Constructors: + * - ike_natd_create() + * + * @ingroup tasks + */ +struct ike_natd_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * @brief 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/charon/sa/tasks/ike_rekey.c b/src/charon/sa/tasks/ike_rekey.c new file mode 100644 index 000000000..a33e7ee34 --- /dev/null +++ b/src/charon/sa/tasks/ike_rekey.c @@ -0,0 +1,329 @@ +/** + * @file ike_rekey.c + * + * @brief Implementation of the ike_rekey task. + * + */ + +/* + * 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 "ike_rekey.h" + +#include <daemon.h> +#include <encoding/payloads/notify_payload.h> +#include <sa/tasks/ike_init.h> +#include <queues/jobs/delete_ike_sa_job.h> +#include <queues/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; + + /** + * colliding task detected by the task manager + */ + task_t *collision; +}; + +/** + * Implementation of task_t.build for initiator + */ +static status_t build_i(private_ike_rekey_t *this, message_t *message) +{ + connection_t *connection; + policy_t *policy; + + this->new_sa = charon->ike_sa_manager->checkout_new(charon->ike_sa_manager, + TRUE); + + connection = this->ike_sa->get_connection(this->ike_sa); + policy = this->ike_sa->get_policy(this->ike_sa); + this->new_sa->set_connection(this->new_sa, connection); + this->new_sa->set_policy(this->new_sa, policy); + + this->ike_init = ike_init_create(this->new_sa, TRUE, this->ike_sa); + this->ike_init->task.build(&this->ike_init->task, message); + + this->ike_sa->set_state(this->ike_sa, IKE_REKEYING); + + return NEED_MORE; +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t process_r(private_ike_rekey_t *this, message_t *message) +{ + connection_t *connection; + policy_t *policy; + 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); + + connection = this->ike_sa->get_connection(this->ike_sa); + policy = this->ike_sa->get_policy(this->ike_sa); + this->new_sa->set_connection(this->new_sa, connection); + this->new_sa->set_policy(this->new_sa, policy); + + 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); + + return SUCCESS; +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t process_i(private_ike_rekey_t *this, message_t *message) +{ + job_t *job; + ike_sa_id_t *to_delete; + + if (this->ike_init->task.process(&this->ike_init->task, message) == FAILED) + { + /* rekeying failed, fallback to old SA */ + if (!(this->collision && + this->collision->get_type(this->collision) == IKE_DELETE)) + { + job_t *job; + u_int32_t retry = charon->configuration->get_retry_interval( + charon->configuration); + 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->event_queue->add_relative(charon->event_queue, job, retry * 1000); + } + return SUCCESS; + } + + this->new_sa->set_state(this->new_sa, IKE_ESTABLISHED); + to_delete = this->ike_sa->get_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) + { + DBG1(DBG_IKE, "IKE_SA rekey collision won, deleting rekeyed IKE_SA"); + charon->ike_sa_manager->checkin(charon->ike_sa_manager, other->new_sa); + } + 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); + to_delete = this->new_sa->get_id(this->new_sa); + charon->ike_sa_manager->checkin(charon->ike_sa_manager, this->new_sa); + /* inherit to other->new_sa in destroy() */ + this->new_sa = other->new_sa; + other->new_sa = NULL; + } + } + + job = (job_t*)delete_ike_sa_job_create(to_delete, TRUE); + charon->job_queue->add(charon->job_queue, job); + + return SUCCESS; +} + +/** + * 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->new_sa) + { + charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, + this->new_sa); + } + DESTROY_IF(this->collision); + + this->collision = NULL; + this->ike_sa = ike_sa; + this->new_sa = NULL; + this->ike_init = 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) + { + 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); + } + } + if (this->ike_init) + { + this->ike_init->task.destroy(&this->ike_init->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->initiator = initiator; + this->collision = NULL; + + return &this->public; +} diff --git a/src/charon/sa/tasks/ike_rekey.h b/src/charon/sa/tasks/ike_rekey.h new file mode 100644 index 000000000..125422efd --- /dev/null +++ b/src/charon/sa/tasks/ike_rekey.h @@ -0,0 +1,69 @@ +/** + * @file ike_rekey.h + * + * @brief Interface ike_rekey_t. + * + */ + +/* + * 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. + */ + +#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> + +/** + * @brief Task of type IKE_REKEY, rekey an established IKE_SA. + * + * @b Constructors: + * - ike_rekey_create() + * + * @ingroup tasks + */ +struct ike_rekey_t { + + /** + * Implements the task_t interface + */ + task_t task; + + /** + * @brief 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 this task initated by us + * @param other incoming task + */ + void (*collide)(ike_rekey_t* this, task_t *other); +}; + +/** + * @brief 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/charon/sa/tasks/task.c b/src/charon/sa/tasks/task.c new file mode 100644 index 000000000..68d8ebf0c --- /dev/null +++ b/src/charon/sa/tasks/task.c @@ -0,0 +1,38 @@ +/** + * @file task.c + * + * @brief Enum values for task types + * + */ + +/* + * 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" + +ENUM(task_type_names, IKE_INIT, CHILD_REKEY, + "IKE_INIT", + "IKE_NATD", + "IKE_AUTHENTICATE", + "IKE_CERT", + "IKE_CONFIG", + "IKE_DPD", + "IKE_REKEY", + "IKE_DELETE", + "IKE_DEADPEER", + "CHILD_CREATE", + "CHILD_DELETE", + "CHILD_REKEY", +); diff --git a/src/charon/sa/tasks/task.h b/src/charon/sa/tasks/task.h new file mode 100644 index 000000000..128d7db4a --- /dev/null +++ b/src/charon/sa/tasks/task.h @@ -0,0 +1,151 @@ +/** + * @file task.h + * + * @brief Interface task_t. + * + */ + +/* + * 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. + */ + +#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> + +/** + * @brief Different kinds of tasks. + * + * @ingroup tasks + */ +enum task_type_t { + /** establish an unauthenticated IKE_SA */ + IKE_INIT, + /** detect NAT situation */ + IKE_NATD, + /** authenticate the initiated IKE_SA */ + IKE_AUTHENTICATE, + /** exchange certificates and requests */ + IKE_CERT, + /** Configuration payloads, virtual IP and such */ + IKE_CONFIG, + /** DPD detection */ + IKE_DEADPEER, + /** rekey an IKE_SA */ + IKE_REKEY, + /** delete an IKE_SA */ + IKE_DELETE, + /** liveness check */ + IKE_DPD, + /** 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; + +/** + * @brief 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. + * + * @b Constructors: + * - None, use implementations specific constructors + * + * @ingroup tasks + */ +struct task_t { + + /** + * @brief Build a request or response message for this task. + * + * @param this calling object + * @param message message to add payloads to + * @return + * - FAILED if a critical error occured + * - NEED_MORE if another call to build/process needed + * - SUCCESS if task completed + */ + status_t (*build) (task_t *this, message_t *message); + + /** + * @brief Process a request or response message for this task. + * + * @param this calling object + * @param message message to read payloads from + * @return + * - FAILED if a critical error occured + * - NEED_MORE if another call to build/process needed + * - SUCCESS if task completed + */ + status_t (*process) (task_t *this, message_t *message); + + /** + * @brief Get the type of the task implementation. + * + * @param this calling object + */ + task_type_t (*get_type) (task_t *this); + + /** + * @brief 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 this calling object + * @param ike_sa new IKE_SA this task works for + */ + void (*migrate) (task_t *this, ike_sa_t *ike_sa); + + /** + * @brief Destroys a task_t object. + * + * @param this calling object + */ + void (*destroy) (task_t *this); +}; + +#endif /* TASK_H_ */ |