diff --git a/configure.in b/configure.in index fc2149417..5d9c256ca 100644 --- a/configure.in +++ b/configure.in @@ -465,6 +465,14 @@ AC_ARG_ENABLE( fi] ) +AC_ARG_ENABLE( + [eap-radius], + AS_HELP_STRING([--enable-eap-radius],[build RADIUS proxy authenication module for EAP (default is NO).]), + [if test x$enableval = xyes; then + eap_radius=true + fi] +) + AC_ARG_ENABLE( [kernel-netlink], AS_HELP_STRING([--disable-kernel-netlink],[disable the netlink kernel interface. (default is NO).]), @@ -963,6 +971,7 @@ AM_CONDITIONAL(USE_EAP_MD5, test x$eap_md5 = xtrue) AM_CONDITIONAL(USE_EAP_GTC, test x$eap_gtc = xtrue) AM_CONDITIONAL(USE_EAP_AKA, test x$eap_aka = xtrue) AM_CONDITIONAL(USE_EAP_MSCHAPV2, test x$eap_mschapv2 = xtrue) +AM_CONDITIONAL(USE_EAP_RADIUS, test x$eap_radius = xtrue) AM_CONDITIONAL(USE_KERNEL_NETLINK, test x$kernel_netlink = xtrue) AM_CONDITIONAL(USE_KERNEL_PFKEY, test x$kernel_pfkey = xtrue) AM_CONDITIONAL(USE_KERNEL_KLIPS, test x$kernel_klips = xtrue) @@ -1044,6 +1053,7 @@ AC_OUTPUT( src/charon/plugins/eap_sim/Makefile src/charon/plugins/eap_sim_file/Makefile src/charon/plugins/eap_mschapv2/Makefile + src/charon/plugins/eap_radius/Makefile src/charon/plugins/kernel_netlink/Makefile src/charon/plugins/kernel_pfkey/Makefile src/charon/plugins/kernel_klips/Makefile diff --git a/src/charon/Makefile.am b/src/charon/Makefile.am index 3393b4516..9da2b238a 100644 --- a/src/charon/Makefile.am +++ b/src/charon/Makefile.am @@ -221,6 +221,11 @@ if USE_EAP_MSCHAPV2 PLUGINS += eapmschapv2 endif +if USE_EAP_RADIUS + SUBDIRS += plugins/eap_radius + PLUGINS += eapradius +endif + if USE_MEDSRV SUBDIRS += plugins/medsrv PLUGINS += medsrv diff --git a/src/charon/plugins/eap_radius/Makefile.am b/src/charon/plugins/eap_radius/Makefile.am new file mode 100644 index 000000000..f7de2f14f --- /dev/null +++ b/src/charon/plugins/eap_radius/Makefile.am @@ -0,0 +1,14 @@ + +INCLUDES = -I$(top_srcdir)/src/libstrongswan -I$(top_srcdir)/src/charon + +AM_CFLAGS = -rdynamic + +plugin_LTLIBRARIES = libstrongswan-eapradius.la + +libstrongswan_eapradius_la_SOURCES = \ + eap_radius_plugin.h eap_radius_plugin.c \ + eap_radius.h eap_radius.c \ + radius_client.h radius_client.c \ + radius_message.h radius_message.c +libstrongswan_eapradius_la_LDFLAGS = -module + diff --git a/src/charon/plugins/eap_radius/eap_radius.c b/src/charon/plugins/eap_radius/eap_radius.c new file mode 100644 index 000000000..1a02c5acf --- /dev/null +++ b/src/charon/plugins/eap_radius/eap_radius.c @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2009 Martin Willi + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * 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. + * + * $Id$ + */ + +#include "eap_radius.h" + +#include "radius_message.h" +#include "radius_client.h" + +#include + + +typedef struct private_eap_radius_t private_eap_radius_t; + +/** + * Private data of an eap_radius_t object. + */ +struct private_eap_radius_t { + + /** + * Public authenticator_t interface. + */ + eap_radius_t public; + + /** + * ID of the server + */ + identification_t *server; + + /** + * ID of the peer + */ + identification_t *peer; + + /** + * EAP method type we are proxying + */ + eap_type_t type; + + /** + * EAP vendor, if any + */ + u_int32_t vendor; + + /** + * EAP MSK, if method established one + */ + chunk_t msk; + + /** + * RADIUS client instance + */ + radius_client_t *client; +}; + +/** + * Add EAP-Identity to RADIUS message + */ +static void add_eap_identity(private_eap_radius_t *this, + radius_message_t *request) +{ + struct { + /** EAP code (REQUEST/RESPONSE) */ + u_int8_t code; + /** unique message identifier */ + u_int8_t identifier; + /** length of whole message */ + u_int16_t length; + /** EAP type */ + u_int8_t type; + /** identity data */ + u_int8_t data[]; + } __attribute__((__packed__)) *hdr; + chunk_t id; + size_t len; + + id = this->peer->get_encoding(this->peer); + len = sizeof(*hdr) + id.len; + + hdr = alloca(len); + hdr->code = EAP_RESPONSE; + hdr->identifier = 0; + hdr->length = htons(len); + hdr->type = EAP_IDENTITY; + memcpy(hdr->data, id.ptr, id.len); + + request->add(request, RAT_EAP_MESSAGE, chunk_create((u_char*)hdr, len)); +} + +/** + * Copy EAP-Message attribute from RADIUS message to an new EAP payload + */ +static bool radius2ike(private_eap_radius_t *this, + radius_message_t *msg, eap_payload_t **out) +{ + enumerator_t *enumerator; + eap_payload_t *payload; + chunk_t data; + int type; + + enumerator = msg->create_enumerator(msg); + while (enumerator->enumerate(enumerator, &type, &data)) + { + if (type == RAT_EAP_MESSAGE) + { + *out = payload = eap_payload_create_data(data); + /* apply EAP method selected by RADIUS server */ + this->type = payload->get_type(payload, &this->vendor); + enumerator->destroy(enumerator); + return TRUE; + } + } + enumerator->destroy(enumerator); + return FALSE; +} + +/** + * Implementation of eap_method_t.initiate + */ +static status_t initiate(private_eap_radius_t *this, eap_payload_t **out) +{ + radius_message_t *request, *response; + status_t status = FAILED; + + request = radius_message_create_request(); + request->add(request, RAT_USER_NAME, this->peer->get_encoding(this->peer)); + add_eap_identity(this, request); + response = this->client->request(this->client, request); + if (response) + { + if (radius2ike(this, response, out)) + { + status = NEED_MORE; + } + response->destroy(response); + } + request->destroy(request); + return status; +} + +/** + * Implementation of eap_method_t.process + */ +static status_t process(private_eap_radius_t *this, + eap_payload_t *in, eap_payload_t **out) +{ + radius_message_t *request, *response; + status_t status = FAILED; + + request = radius_message_create_request(); + request->add(request, RAT_USER_NAME, this->peer->get_encoding(this->peer)); + request->add(request, RAT_EAP_MESSAGE, in->get_data(in)); + + response = this->client->request(this->client, request); + if (response) + { + switch (response->get_code(response)) + { + case RMC_ACCESS_CHALLENGE: + if (radius2ike(this, response, out)) + { + status = NEED_MORE; + break; + } + status = FAILED; + break; + case RMC_ACCESS_ACCEPT: + this->msk = this->client->decrypt_msk(this->client, + response, request); + status = SUCCESS; + break; + case RMC_ACCESS_REJECT: + default: + DBG1(DBG_CFG, "received %N from RADIUS server", + radius_message_code_names, response->get_code(response)); + status = FAILED; + break; + } + response->destroy(response); + } + request->destroy(request); + return status; +} + +/** + * Implementation of eap_method_t.get_type. + */ +static eap_type_t get_type(private_eap_radius_t *this, u_int32_t *vendor) +{ + *vendor = this->vendor; + return this->type; +} + +/** + * Implementation of eap_method_t.get_msk. + */ +static status_t get_msk(private_eap_radius_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_radius_t *this) +{ + switch (this->type) + { + case EAP_AKA: + case EAP_SIM: + return TRUE; + default: + return FALSE; + } +} + +/** + * Implementation of eap_method_t.destroy. + */ +static void destroy(private_eap_radius_t *this) +{ + this->peer->destroy(this->peer); + this->server->destroy(this->server); + this->client->destroy(this->client); + chunk_clear(&this->msk); + free(this); +} + +/** + * Generic constructor + */ +eap_radius_t *eap_radius_create(identification_t *server, identification_t *peer) +{ + private_eap_radius_t *this = malloc_thing(private_eap_radius_t); + + 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*,u_int32_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; + + this->client = radius_client_create(); + if (!this->client) + { + free(this); + return NULL; + } + this->peer = peer->clone(peer); + this->server = server->clone(server); + /* initially EAP_RADIUS, but is set to the method selected by RADIUS */ + this->type = EAP_RADIUS; + this->vendor = 0; + this->msk = chunk_empty; + + return &this->public; +} + diff --git a/src/charon/plugins/eap_radius/eap_radius.h b/src/charon/plugins/eap_radius/eap_radius.h new file mode 100644 index 000000000..5af72eb2e --- /dev/null +++ b/src/charon/plugins/eap_radius/eap_radius.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2009 Martin Willi + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * 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. + * + * $Id$ + */ + +/** + * @defgroup eap_radius_i eap_radius + * @{ @ingroup eap_radius + */ + +#ifndef EAP_RADIUS_H_ +#define EAP_RADIUS_H_ + +typedef struct eap_radius_t eap_radius_t; + +#include + +/** + * Implementation of the eap_method_t interface using a RADIUS server. + */ +struct eap_radius_t { + + /** + * Implemented eap_method_t interface. + */ + eap_method_t eap_method_interface; +}; + +/** + * Create a EAP RADIUS proxy. + * + * @param server ID of the EAP server + * @param peer ID of the EAP client + * @return eap_radius_t object + */ +eap_radius_t *eap_radius_create(identification_t *server, identification_t *peer); + +#endif /* EAP_RADIUS_H_ @}*/ diff --git a/src/charon/plugins/eap_radius/eap_radius_plugin.c b/src/charon/plugins/eap_radius/eap_radius_plugin.c new file mode 100644 index 000000000..a429859a7 --- /dev/null +++ b/src/charon/plugins/eap_radius/eap_radius_plugin.c @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2009 Martin Willi + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * 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. + * + * $Id$ + */ + +#include "eap_radius_plugin.h" + +#include "eap_radius.h" +#include "radius_client.h" + +#include + +/** + * Implementation of plugin_t.destroy + */ +static void destroy(eap_radius_plugin_t *this) +{ + charon->eap->remove_method(charon->eap, (eap_constructor_t)eap_radius_create); + radius_client_cleanup(); + free(this); +} + +/* + * see header file + */ +plugin_t *plugin_create() +{ + eap_radius_plugin_t *this; + + if (!radius_client_init()) + { + DBG1(DBG_CFG, "RADIUS plugin initialization failed"); + return NULL; + } + + this = malloc_thing(eap_radius_plugin_t); + this->plugin.destroy = (void(*)(plugin_t*))destroy; + + charon->eap->add_method(charon->eap, EAP_RADIUS, 0, + EAP_SERVER, (eap_constructor_t)eap_radius_create); + + return &this->plugin; +} + diff --git a/src/charon/plugins/eap_radius/eap_radius_plugin.h b/src/charon/plugins/eap_radius/eap_radius_plugin.h new file mode 100644 index 000000000..2c3978f42 --- /dev/null +++ b/src/charon/plugins/eap_radius/eap_radius_plugin.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2009 Martin Willi + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * 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. + * + * $Id$ + */ + +/** + * @defgroup eap_radius eap_radius + * @ingroup cplugins + * + * @defgroup eap_radius_plugin eap_radius_plugin + * @{ @ingroup eap_radius + */ + +#ifndef EAP_RADIUS_PLUGIN_H_ +#define EAP_RADIUS_PLUGIN_H_ + +#include + +typedef struct eap_radius_plugin_t eap_radius_plugin_t; + +/** + * EAP RADIUS proxy plugin. + * + * This plugin provides not a single EAP method, but a proxy to forwared + * EAP packets to a RADIUS server. It only provides server implementations. + */ +struct eap_radius_plugin_t { + + /** + * implements plugin interface + */ + plugin_t plugin; +}; + +/** + * Create a eap_radius_plugin instance. + */ +plugin_t *plugin_create(); + +#endif /* EAP_RADIUS_PLUGIN_H_ @}*/ diff --git a/src/charon/plugins/eap_radius/radius_client.c b/src/charon/plugins/eap_radius/radius_client.c new file mode 100644 index 000000000..7c718e8ff --- /dev/null +++ b/src/charon/plugins/eap_radius/radius_client.c @@ -0,0 +1,493 @@ +/* + * Copyright (C) 2009 Martin Willi + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * 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. + * + * $Id$ + */ + +#include "radius_client.h" + +#include +#include + +#include +#include +#include +#include + +/** + * Default RADIUS server port, when not configured + */ +#define RADIUS_PORT 1812 + +/** + * Vendor-Id of Microsoft specific attributes + */ +#define VENDOR_ID_MICROSOFT 311 + +/** + * Microsoft specific vendor attributes + */ +#define MS_MPPE_SEND_KEY 16 +#define MS_MPPE_RECV_KEY 17 + +typedef struct private_radius_client_t private_radius_client_t; + +typedef struct entry_t entry_t; + +/** + * A socket pool entry. + */ +struct entry_t { + /** socket file descriptor */ + int fd; + /** current RADIUS identifier */ + u_int8_t identifier; + /** hasher to use for response verification */ + hasher_t *hasher; + /** HMAC-MD5 signer to build Message-Authenticator attribute */ + signer_t *signer; + /** random number generator for RADIUS request authenticator */ + rng_t *rng; +}; + +/** + * Private data of an radius_client_t object. + */ +struct private_radius_client_t { + + /** + * Public radius_client_t interface. + */ + radius_client_t public; + + /** + * The clients socket from the pool + */ + entry_t *socket; + + /** + * RADIUS servers State attribute + */ + chunk_t state; +}; + +/** + * Global list of radius sockets, contains entry_t's + */ +static linked_list_t *sockets; + +/** + * mutex to lock sockets list + */ +static mutex_t *mutex; + +/** + * condvar to wait for sockets + */ +static condvar_t *condvar; + +/** + * RADIUS secret + */ +static chunk_t secret; + +/** + * NAS-Identifier + */ +static chunk_t nas_identifier; + +/** + * Clean up socket list + */ +void radius_client_cleanup() +{ + entry_t *entry; + + mutex->destroy(mutex); + condvar->destroy(condvar); + while (sockets->remove_last(sockets, (void**)&entry) == SUCCESS) + { + entry->rng->destroy(entry->rng); + entry->hasher->destroy(entry->hasher); + entry->signer->destroy(entry->signer); + close(entry->fd); + free(entry); + } + sockets->destroy(sockets); +} + +/** + * Initialize the socket list + */ +bool radius_client_init() +{ + int i, count, fd; + u_int16_t port; + entry_t *entry; + host_t *host; + char *server; + + nas_identifier.ptr = lib->settings->get_str(lib->settings, + "charon.plugins.eap_radius.nas_identifier", "strongSwan"); + nas_identifier.len = strlen(nas_identifier.ptr); + + secret.ptr = lib->settings->get_str(lib->settings, + "charon.plugins.eap_radius.secret", NULL); + if (!secret.ptr) + { + DBG1(DBG_CFG, "no RADUIS secret defined"); + return FALSE; + } + secret.len = strlen(secret.ptr); + server = lib->settings->get_str(lib->settings, + "charon.plugins.eap_radius.server", NULL); + if (!server) + { + DBG1(DBG_CFG, "no RADUIS server defined"); + return FALSE; + } + port = lib->settings->get_int(lib->settings, + "charon.plugins.eap_radius.port", RADIUS_PORT); + host = host_create_from_dns(server, 0, port); + if (!host) + { + return FALSE; + } + count = lib->settings->get_int(lib->settings, + "charon.plugins.eap_radius.sockets", 5); + + sockets = linked_list_create(); + mutex = mutex_create(MUTEX_DEFAULT); + condvar = condvar_create(CONDVAR_DEFAULT); + for (i = 0; i < count; i++) + { + fd = socket(host->get_family(host), SOCK_DGRAM, IPPROTO_UDP); + if (fd < 0) + { + DBG1(DBG_CFG, "opening RADIUS socket failed"); + host->destroy(host); + radius_client_cleanup(); + return FALSE; + } + if (connect(fd, host->get_sockaddr(host), + *host->get_sockaddr_len(host)) < 0) + { + DBG1(DBG_CFG, "connecting RADIUS socket failed"); + host->destroy(host); + radius_client_cleanup(); + return FALSE; + } + entry = malloc_thing(entry_t); + entry->fd = fd; + /* we use per-socket crypto elements: this reduces overhead, but + * is still thread-save. */ + entry->hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5); + entry->signer = lib->crypto->create_signer(lib->crypto, AUTH_HMAC_MD5_128); + entry->rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK); + if (!entry->hasher || !entry->signer || !entry->rng) + { + DBG1(DBG_CFG, "RADIUS initialization failed, HMAC/MD5/RNG required"); + DESTROY_IF(entry->hasher); + DESTROY_IF(entry->signer); + DESTROY_IF(entry->rng); + free(entry); + host->destroy(host); + radius_client_cleanup(); + return FALSE; + } + entry->signer->set_key(entry->signer, secret); + /* we use a random identifier, helps if we restart often (testing) */ + entry->identifier = random(); + sockets->insert_last(sockets, entry); + } + host->destroy(host); + return TRUE; +} + +/** + * Get a socket from the pool, block if none available + */ +static entry_t* get_socket() +{ + entry_t *entry; + + mutex->lock(mutex); + while (sockets->remove_first(sockets, (void**)&entry) != SUCCESS) + { + condvar->wait(condvar, mutex); + } + mutex->unlock(mutex); + return entry; +} + +/** + * Release a socket to the pool + */ +static void put_socket(entry_t *entry) +{ + mutex->lock(mutex); + sockets->insert_last(sockets, entry); + mutex->unlock(mutex); + condvar->signal(condvar); +} + +/** + * Save the state attribute to include in further request + */ +static void save_state(private_radius_client_t *this, radius_message_t *msg) +{ + enumerator_t *enumerator; + int type; + chunk_t data; + + enumerator = msg->create_enumerator(msg); + while (enumerator->enumerate(enumerator, &type, &data)) + { + if (type == RAT_STATE) + { + free(this->state.ptr); + this->state = chunk_clone(data); + enumerator->destroy(enumerator); + return; + } + } + enumerator->destroy(enumerator); + /* no state attribute found, remove state */ + chunk_free(&this->state); +} + +/** + * Implementation of radius_client_t.request + */ +static radius_message_t* request(private_radius_client_t *this, + radius_message_t *req) +{ + char virtual[] = {0x00,0x00,0x00,0x05}; + radius_message_t *response; + chunk_t data; + fd_set fds; + int i; + + /* set Message Identifier */ + req->set_identifier(req, this->socket->identifier++); + /* we add the "Virtual" NAS-Port-Type, as we SHOULD include one */ + req->add(req, RAT_NAS_PORT_TYPE, chunk_create(virtual, sizeof(virtual))); + /* add our NAS-Identifier */ + req->add(req, RAT_NAS_IDENTIFIER, nas_identifier); + /* add State attribute, if server sent one */ + if (this->state.ptr) + { + req->add(req, RAT_STATE, this->state); + } + /* sign the request */ + req->sign(req, this->socket->rng, this->socket->signer); + + data = req->get_encoding(req); + FD_ZERO(&fds); + FD_SET(this->socket->fd, &fds); + /* timeout after 2, 3, 4, 5 seconds */ + for (i = 2; i <= 5; i++) + { + bool retransmit = FALSE; + struct timeval tv; + char buf[1024]; + int res, retry = 0; + + if (send(this->socket->fd, data.ptr, data.len, 0) != data.len) + { + DBG1(DBG_CFG, "sending RADIUS message failed: %s", strerror(errno)); + return NULL; + } + while (TRUE) + { + tv.tv_sec = i; + tv.tv_usec = 0; + + res = select(this->socket->fd + 1, &fds, NULL, NULL, &tv); + if (res < 0) + { /* failed */ + DBG1(DBG_CFG, "waiting for RADIUS message failed: %s", + strerror(errno)); + break; + } + if (res == 0) + { /* timeout */ + DBG1(DBG_CFG, "retransmitting RADIUS message"); + retransmit = TRUE; + break; + } + res = recv(this->socket->fd, buf, sizeof(buf), MSG_DONTWAIT); + if (res <= 0) + { + DBG1(DBG_CFG, "receiving RADIUS message failed: %s", + strerror(errno)); + break; + } + response = radius_message_parse_response(chunk_create(buf, res)); + if (response) + { + if (response->verify(response, req->get_authenticator(req), + secret, this->socket->hasher, this->socket->signer)) + { + save_state(this, response); + return response; + } + response->destroy(response); + } + /* might be a maliciously injected packet, read onother one. + * Limit the number of retries, an attacker could us trick into + * a loop otherwise. */ + if (retry++ > 5) + { + break; + } + } + if (!retransmit) + { + break; + } + } + DBG1(DBG_CFG, "RADIUS server is not responding"); + return NULL; +} + +/** + * Decrypt a MS-MPPE-Send/Recv-Key + */ +static chunk_t decrypt_mppe_key(private_radius_client_t *this, u_int16_t salt, + chunk_t C, radius_message_t *request) +{ + chunk_t A, R, P, seed; + u_char *c, *p; + + /** + * From RFC2548 (encryption): + * b(1) = MD5(S + R + A) c(1) = p(1) xor b(1) C = c(1) + * b(2) = MD5(S + c(1)) c(2) = p(2) xor b(2) C = C + c(2) + * . . . + * b(i) = MD5(S + c(i-1)) c(i) = p(i) xor b(i) C = C + c(i) + */ + + if (C.len % HASH_SIZE_MD5 || C.len < HASH_SIZE_MD5) + { + return chunk_empty; + } + + A = chunk_create((u_char*)&salt, sizeof(salt)); + R = chunk_create(request->get_authenticator(request), HASH_SIZE_MD5); + P = chunk_alloca(C.len); + p = P.ptr; + c = C.ptr; + + seed = chunk_cata("cc", R, A); + + while (c < C.ptr + C.len) + { + /* b(i) = MD5(S + c(i-1)) */ + this->socket->hasher->get_hash(this->socket->hasher, secret, NULL); + this->socket->hasher->get_hash(this->socket->hasher, seed, p); + + /* p(i) = b(i) xor c(1) */ + memxor(p, c, HASH_SIZE_MD5); + + /* prepare next round */ + seed = chunk_create(c, HASH_SIZE_MD5); + c += HASH_SIZE_MD5; + p += HASH_SIZE_MD5; + } + + /* remove truncation, first byte is key length */ + if (*P.ptr >= P.len) + { /* decryption failed? */ + return chunk_empty; + } + return chunk_clone(chunk_create(P.ptr + 1, *P.ptr)); +} + +/** + * Implementation of radius_client_t.decrypt_msk + */ +static chunk_t decrypt_msk(private_radius_client_t *this, + radius_message_t *response, radius_message_t *request) +{ + struct { + u_int32_t id; + u_int8_t type; + u_int8_t length; + u_int16_t salt; + u_int8_t key[]; + } __attribute__((packed)) *mppe_key; + enumerator_t *enumerator; + chunk_t data, send = chunk_empty, recv = chunk_empty; + int type; + + enumerator = response->create_enumerator(response); + while (enumerator->enumerate(enumerator, &type, &data)) + { + if (type == RAT_VENDOR_SPECIFIC && + data.len > sizeof(*mppe_key)) + { + mppe_key = (void*)data.ptr; + if (ntohl(mppe_key->id) == VENDOR_ID_MICROSOFT && + mppe_key->length == data.len - sizeof(mppe_key->id)) + { + data = chunk_create(mppe_key->key, data.len - sizeof(*mppe_key)); + if (mppe_key->type == MS_MPPE_SEND_KEY) + { + send = decrypt_mppe_key(this, mppe_key->salt, data, request); + } + if (mppe_key->type == MS_MPPE_RECV_KEY) + { + recv = decrypt_mppe_key(this, mppe_key->salt, data, request); + } + } + } + } + enumerator->destroy(enumerator); + if (send.ptr && recv.ptr) + { + return chunk_cat("mm", recv, send); + } + chunk_clear(&send); + chunk_clear(&recv); + return chunk_empty; +} + +/** + * Implementation of radius_client_t.destroy. + */ +static void destroy(private_radius_client_t *this) +{ + put_socket(this->socket); + free(this->state.ptr); + free(this); +} + +/** + * See header + */ +radius_client_t *radius_client_create() +{ + private_radius_client_t *this = malloc_thing(private_radius_client_t); + + this->public.request = (radius_message_t*(*)(radius_client_t*, radius_message_t *msg))request; + this->public.decrypt_msk = (chunk_t(*)(radius_client_t*, radius_message_t *, radius_message_t *))decrypt_msk; + this->public.destroy = (void(*)(radius_client_t*))destroy; + + this->socket = get_socket(); + this->state = chunk_empty; + + return &this->public; +} + diff --git a/src/charon/plugins/eap_radius/radius_client.h b/src/charon/plugins/eap_radius/radius_client.h new file mode 100644 index 000000000..5b8a88b12 --- /dev/null +++ b/src/charon/plugins/eap_radius/radius_client.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2009 Martin Willi + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * 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. + * + * $Id$ + */ + +/** + * @defgroup radius_client radius_client + * @{ @ingroup eap_radius + */ + +#ifndef RADIUS_CLIENT_H_ +#define RADIUS_CLIENT_H_ + +#include "radius_message.h" + +typedef struct radius_client_t radius_client_t; + +/** + * RADIUS client functionality. + * + * To communicate with a RADIUS server, create a client and send messages over + * it. All instances share a fixed size pool of sockets. During construction, + * one sockets gets reserved for the client, so each client uses a different + * but fixed port during its lifetime. On destruction, the socket is restored + * to the pool. + */ +struct radius_client_t { + + /** + * Send a RADIUS request and wait for the response. + * + * The client fills in RADIUS Message identifier, NAS-Identifier, + * NAS-Port-Type, builds a Request-Authenticator and calculates the + * Message-Authenticator attribute. + * The received response gets verified using the Response-Identifier + * and the Message-Authenticator attribute. + * + * @param msg RADIUS request message to send + * @return response, NULL if timed out/verification failed + */ + radius_message_t* (*request)(radius_client_t *this, radius_message_t *msg); + + /** + * Decrypt the MSK encoded in a messages MS-MPPE-Send/Recv-Key. + * + * @param response RADIUS response message containing attributes + * @param request associated RADIUS request message + * @return allocated MSK, empty chunk if none found + */ + chunk_t (*decrypt_msk)(radius_client_t *this, radius_message_t *response, + radius_message_t *request); + + /** + * Destroy the client, release the socket. + */ + void (*destroy)(radius_client_t *this); +}; + +/** + * Create a RADIUS client, acquire a socket. + * + * This call might block if the socket pool is empty. + * + * @return radius_client_t object + */ +radius_client_t *radius_client_create(); + +/** + * Initialize the socket pool. + * + * @return TRUE if initialization successful + */ +bool radius_client_init(); + +/** + * Cleanup the socket pool. + */ +void radius_client_cleanup(); + +#endif /* radius_client_H_ @}*/ diff --git a/src/charon/plugins/eap_radius/radius_message.c b/src/charon/plugins/eap_radius/radius_message.c new file mode 100644 index 000000000..7279c7957 --- /dev/null +++ b/src/charon/plugins/eap_radius/radius_message.c @@ -0,0 +1,465 @@ +/* + * Copyright (C) 2009 Martin Willi + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * 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. + * + * $Id$ + */ + +#include "radius_message.h" + +#include +#include + +typedef struct private_radius_message_t private_radius_message_t; +typedef struct rmsg_t rmsg_t; +typedef struct rattr_t rattr_t; + +/** + * RADIUS message header + */ +struct rmsg_t { + /** message code, radius_message_code_t */ + u_int8_t code; + /** message identifier */ + u_int8_t identifier; + /** length of Code, Identifier, Length, Authenticator and Attributes */ + u_int16_t length; + /** message authenticator, MD5 hash */ + u_int8_t authenticator[HASH_SIZE_MD5]; + /** variable list of packed attributes */ + u_int8_t attributes[]; +} __attribute__((packed)); + +/** + * RADIUS message attribute. + */ +struct rattr_t { + /** attribute type, radius_attribute_type_t */ + u_int8_t type; + /** length of the attriubte, including the Type, Length and Value fields */ + u_int8_t length; + /** variable length attribute value */ + u_int8_t value[]; +} __attribute__((packed)); + +/** + * Private data of an radius_message_t object. + */ +struct private_radius_message_t { + + /** + * Public radius_message_t interface. + */ + radius_message_t public; + + /** + * message data, allocated + */ + rmsg_t *msg; +}; + +ENUM_BEGIN(radius_message_code_names, RMC_ACCESS_REQUEST, RMC_ACCOUNTING_RESPONSE, + "Access-Request", + "Access-Accept", + "Access-Reject", + "Accounting-Request", + "Accounting-Response"); +ENUM_NEXT(radius_message_code_names, RMC_ACCESS_CHALLENGE, RMC_ACCESS_CHALLENGE, RMC_ACCOUNTING_RESPONSE, + "Access-Challenge"); +ENUM_END(radius_message_code_names, RMC_ACCESS_CHALLENGE); + +ENUM(radius_attribute_type_names, RAT_USER_NAME, RAT_MIP6_HOME_LINK_PREFIX, + "User-Name", + "User-Password", + "CHAP-Password", + "NAS-IP-Address", + "NAS-Port", + "Service-Type", + "Framed-Protocol", + "Framed-IP-Address", + "Framed-IP-Netmask", + "Framed-Routing", + "Filter-Id", + "Framed-MTU", + "Framed-Compression", + "Login-IP-Host", + "Login-Service", + "Login-TCP-Port", + "Unassigned", + "Reply-Message", + "Callback-Number", + "Callback-Id", + "Unassigned", + "Framed-Route", + "Framed-IPX-Network", + "State", + "Class", + "Vendor-Specific", + "Session-Timeout", + "Idle-Timeout", + "Termination-Action", + "Called-Station-Id", + "Calling-Station-Id", + "NAS-Identifier", + "Proxy-State", + "Login-LAT-Service", + "Login-LAT-Node", + "Login-LAT-Group", + "Framed-AppleTalk-Link", + "Framed-AppleTalk-Network", + "Framed-AppleTalk-Zone", + "Acct-Status-Type", + "Acct-Delay-Time", + "Acct-Input-Octets", + "Acct-Output-Octets", + "Acct-Session-Id", + "Acct-Authentic", + "Acct-Session-Time", + "Acct-Input-Packets", + "Acct-Output-Packets", + "Acct-Terminate-Cause", + "Acct-Multi-Session-Id", + "Acct-Link-Count", + "Acct-Input-Gigawords", + "Acct-Output-Gigawords", + "Unassigned", + "Event-Timestamp", + "Egress-VLANID", + "Ingress-Filters", + "Egress-VLAN-Name", + "User-Priority-Table", + "CHAP-Challenge", + "NAS-Port-Type", + "Port-Limit", + "Login-LAT-Port", + "Tunnel-Type", + "Tunnel-Medium-Type", + "Tunnel-Client-Endpoint", + "Tunnel-Server-Endpoint", + "Acct-Tunnel-Connection", + "Tunnel-Password", + "ARAP-Password", + "ARAP-Features", + "ARAP-Zone-Access", + "ARAP-Security", + "ARAP-Security-Data", + "Password-Retry", + "Prompt", + "Connect-Info", + "Configuration-Token", + "EAP-Message", + "Message-Authenticator", + "Tunnel-Private-Group-ID", + "Tunnel-Assignment-ID", + "Tunnel-Preference", + "ARAP-Challenge-Response", + "Acct-Interim-Interval", + "Acct-Tunnel-Packets-Lost", + "NAS-Port-Id", + "Framed-Pool", + "CUI", + "Tunnel-Client-Auth-ID", + "Tunnel-Server-Auth-ID", + "NAS-Filter-Rule", + "Unassigned", + "Originating-Line-Info", + "NAS-IPv6-Address", + "Framed-Interface-Id", + "Framed-IPv6-Prefix", + "Login-IPv6-Host", + "Framed-IPv6-Route", + "Framed-IPv6-Pool", + "Error-Cause", + "EAP-Key-Name", + "Digest-Response", + "Digest-Realm", + "Digest-Nonce", + "Digest-Response-Auth", + "Digest-Nextnonce", + "Digest-Method", + "Digest-URI", + "Digest-Qop", + "Digest-Algorithm", + "Digest-Entity-Body-Hash", + "Digest-CNonce", + "Digest-Nonce-Count", + "Digest-Username", + "Digest-Opaque", + "Digest-Auth-Param", + "Digest-AKA-Auts", + "Digest-Domain", + "Digest-Stale", + "Digest-HA1", + "SIP-AOR", + "Delegated-IPv6-Prefix", + "MIP6-Feature-Vector", + "MIP6-Home-Link-Prefix"); + +/** + * Attribute enumerator implementation + */ +typedef struct { + /** implements enumerator interface */ + enumerator_t public; + /** currently pointing attribute */ + rattr_t *next; + /** bytes left */ + int left; +} attribute_enumerator_t; + + +/** + * Implementation of attribute_enumerator_t.enumerate + */ +static bool attribute_enumerate(attribute_enumerator_t *this, + int *type, chunk_t *data) + +{ + if (this->left == 0) + { + return FALSE; + } + if (this->left < sizeof(rattr_t) || + this->left < this->next->length) + { + DBG1(DBG_IKE, "RADIUS message truncated"); + return FALSE; + } + *type = this->next->type; + data->ptr = this->next->value; + data->len = this->next->length - sizeof(rattr_t); + this->left -= this->next->length; + this->next = ((void*)this->next) + this->next->length; + return TRUE; +} + +/** + * Implementation of radius_message_t.create_enumerator + */ +static enumerator_t* create_enumerator(private_radius_message_t *this) +{ + attribute_enumerator_t *e; + + if (ntohs(this->msg->length) < sizeof(rmsg_t) + sizeof(rattr_t)) + { + return enumerator_create_empty(); + } + + e = malloc_thing(attribute_enumerator_t); + e->public.enumerate = (void*)attribute_enumerate; + e->public.destroy = (void*)free; + e->next = (rattr_t*)this->msg->attributes; + e->left = ntohs(this->msg->length) - sizeof(rmsg_t); + return &e->public; +} + +/** + * Implementation of radius_message_t.add + */ +static void add(private_radius_message_t *this, radius_attribute_type_t type, + chunk_t data) +{ + rattr_t *attribute; + + this->msg = realloc(this->msg, + ntohs(this->msg->length) + sizeof(rattr_t) + data.len); + attribute = ((void*)this->msg) + ntohs(this->msg->length); + attribute->type = type; + attribute->length = data.len + sizeof(rattr_t); + memcpy(attribute->value, data.ptr, data.len); + this->msg->length = htons(ntohs(this->msg->length) + attribute->length); +} + +/** + * Implementation of radius_message_t.sign + */ +static void sign(private_radius_message_t *this, rng_t *rng, signer_t *signer) +{ + char buf[HASH_SIZE_MD5]; + + /* build Request-Authenticator */ + rng->get_bytes(rng, HASH_SIZE_MD5, this->msg->authenticator); + + /* build Message-Authenticator attribute, using 16 null bytes */ + memset(buf, 0, sizeof(buf)); + add(this, RAT_MESSAGE_AUTHENTICATOR, chunk_create(buf, sizeof(buf))); + signer->get_signature(signer, + chunk_create((u_char*)this->msg, ntohs(this->msg->length)), + ((u_char*)this->msg) + ntohs(this->msg->length) - HASH_SIZE_MD5); +} + +/** + * Implementation of radius_message_t.verify + */ +static bool verify(private_radius_message_t *this, u_int8_t *req_auth, + chunk_t secret, hasher_t *hasher, signer_t *signer) +{ + char buf[HASH_SIZE_MD5], res_auth[HASH_SIZE_MD5]; + enumerator_t *enumerator; + int type; + chunk_t data, msg; + + /* replace Response by Request Authenticator for verification */ + memcpy(res_auth, this->msg->authenticator, HASH_SIZE_MD5); + memcpy(this->msg->authenticator, req_auth, HASH_SIZE_MD5); + msg = chunk_create((u_char*)this->msg, ntohs(this->msg->length)); + + /* verify Response-Authenticator */ + hasher->get_hash(hasher, msg, NULL); + hasher->get_hash(hasher, secret, buf); + if (!memeq(buf, res_auth, HASH_SIZE_MD5)) + { + DBG1(DBG_CFG, "RADIUS Response-Authenticator verification failed"); + return FALSE; + } + + /* verify Message-Authenticator attribute */ + enumerator = create_enumerator(this); + while (enumerator->enumerate(enumerator, &type, &data)) + { + if (type == RAT_MESSAGE_AUTHENTICATOR) + { + if (data.len != HASH_SIZE_MD5) + { + DBG1(DBG_CFG, "RADIUS Message-Authenticator invalid length"); + enumerator->destroy(enumerator); + return FALSE; + } + memcpy(buf, data.ptr, data.len); + memset(data.ptr, 0, data.len); + if (signer->verify_signature(signer, msg, + chunk_create(buf, sizeof(buf)))) + { /* good, restore Authenticators */ + memcpy(this->msg->authenticator, res_auth, HASH_SIZE_MD5); + memcpy(data.ptr, buf, data.len); + enumerator->destroy(enumerator); + return TRUE; + } + else + { + DBG1(DBG_CFG, "RADIUS Message-Authenticator verification failed"); + enumerator->destroy(enumerator); + return FALSE; + } + } + } + enumerator->destroy(enumerator); + DBG1(DBG_CFG, "RADIUS Message-Authenticator attribute missing"); + return FALSE; +} + +/** + * Implementation of radius_message_t.get_code + */ +static radius_message_code_t get_code(private_radius_message_t *this) +{ + return this->msg->code; +} + +/** + * Implementation of radius_message_t.get_identifier + */ +static u_int8_t get_identifier(private_radius_message_t *this) +{ + return this->msg->identifier; +} + +/** + * Implementation of radius_message_t.set_identifier + */ +static void set_identifier(private_radius_message_t *this, u_int8_t identifier) +{ + this->msg->identifier = identifier; +} + +/** + * Implementation of radius_message_t.get_authenticator + */ +static u_int8_t* get_authenticator(private_radius_message_t *this) +{ + return this->msg->authenticator; +} + + +/** + * Implementation of radius_message_t.get_encoding + */ +static chunk_t get_encoding(private_radius_message_t *this) +{ + return chunk_create((u_char*)this->msg, ntohs(this->msg->length)); +} + +/** + * Implementation of radius_message_t.destroy. + */ +static void destroy(private_radius_message_t *this) +{ + free(this->msg); + free(this); +} + +/** + * Generic constructor + */ +static private_radius_message_t *radius_message_create() +{ + private_radius_message_t *this = malloc_thing(private_radius_message_t); + + this->public.create_enumerator = (enumerator_t*(*)(radius_message_t*))create_enumerator; + this->public.add = (void(*)(radius_message_t*, radius_attribute_type_t,chunk_t))add; + this->public.get_code = (radius_message_code_t(*)(radius_message_t*))get_code; + this->public.get_identifier = (u_int8_t(*)(radius_message_t*))get_identifier; + this->public.set_identifier = (void(*)(radius_message_t*, u_int8_t identifier))set_identifier; + this->public.get_authenticator = (u_int8_t*(*)(radius_message_t*))get_authenticator; + this->public.get_encoding = (chunk_t(*)(radius_message_t*))get_encoding; + this->public.sign = (void(*)(radius_message_t*, rng_t *rng, signer_t *signer))sign; + this->public.verify = (bool(*)(radius_message_t*, u_int8_t *req_auth, chunk_t secret, hasher_t *hasher, signer_t *signer))verify; + this->public.destroy = (void(*)(radius_message_t*))destroy; + + return this; +} + +/** + * See header + */ +radius_message_t *radius_message_create_request() +{ + private_radius_message_t *this = radius_message_create(); + + this->msg = malloc_thing(rmsg_t); + this->msg->code = RMC_ACCESS_REQUEST; + this->msg->identifier = 0; + this->msg->length = htons(sizeof(rmsg_t)); + + return &this->public; +} + +/** + * See header + */ +radius_message_t *radius_message_parse_response(chunk_t data) +{ + private_radius_message_t *this = radius_message_create(); + + this->msg = malloc(data.len); + memcpy(this->msg, data.ptr, data.len); + if (data.len < sizeof(rmsg_t) || + ntohs(this->msg->length) != data.len) + { + DBG1(DBG_IKE, "RADIUS message has invalid length"); + destroy(this); + return NULL; + } + return &this->public; +} + diff --git a/src/charon/plugins/eap_radius/radius_message.h b/src/charon/plugins/eap_radius/radius_message.h new file mode 100644 index 000000000..5cf49dceb --- /dev/null +++ b/src/charon/plugins/eap_radius/radius_message.h @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2009 Martin Willi + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * 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. + * + * $Id$ + */ + +/** + * @defgroup radius_message radius_message + * @{ @ingroup eap_radius + */ + +#ifndef RADIUS_MESSAGE_H_ +#define RADIUS_MESSAGE_H_ + +#include + +typedef struct radius_message_t radius_message_t; +typedef enum radius_message_code_t radius_message_code_t; +typedef enum radius_attribute_type_t radius_attribute_type_t; + +/** + * RADIUS Message Codes. + */ +enum radius_message_code_t { + RMC_ACCESS_REQUEST = 1, + RMC_ACCESS_ACCEPT = 2, + RMC_ACCESS_REJECT = 3, + RMC_ACCOUNTING_REQUEST = 4, + RMC_ACCOUNTING_RESPONSE = 5, + RMC_ACCESS_CHALLENGE = 11, +}; + +/** + * Enum names for radius_attribute_type_t. + */ +extern enum_name_t *radius_message_code_names; + +/** + * RADIUS Attribute Types. + */ +enum radius_attribute_type_t { + RAT_USER_NAME = 1, + RAT_USER_PASSWORD = 2, + RAT_CHAP_PASSWORD = 3, + RAT_NAS_IP_ADDRESS = 4, + RAT_NAS_PORT = 5, + RAT_SERVICE_TYPE = 6, + RAT_FRAMED_PROTOCOL = 7, + RAT_FRAMED_IP_ADDRESS = 8, + RAT_FRAMED_IP_NETMASK = 9, + RAT_FRAMED_ROUTING = 10, + RAT_FILTER_ID = 11, + RAT_FRAMED_MTU = 12, + RAT_FRAMED_COMPRESSION = 13, + RAT_LOGIN_IP_HOST = 14, + RAT_LOGIN_SERVICE = 15, + RAT_LOGIN_TCP_PORT = 16, + RAT_REPLY_MESSAGE = 18, + RAT_CALLBACK_NUMBER = 19, + RAT_CALLBACK_ID = 20, + RAT_FRAMED_ROUTE = 22, + RAT_FRAMED_IPX_NETWORK = 23, + RAT_STATE = 24, + RAT_CLASS = 25, + RAT_VENDOR_SPECIFIC = 26, + RAT_SESSION_TIMEOUT = 27, + RAT_IDLE_TIMEOUT = 28, + RAT_TERMINATION_ACTION = 29, + RAT_CALLED_STATION_ID = 30, + RAT_CALLING_STATION_ID = 31, + RAT_NAS_IDENTIFIER = 32, + RAT_PROXY_STATE = 33, + RAT_LOGIN_LAT_SERVICE = 34, + RAT_LOGIN_LAT_NODE = 35, + RAT_LOGIN_LAT_GROUP = 36, + RAT_FRAMED_APPLETALK_LINK = 37, + RAT_FRAMED_APPLETALK_NETWORK = 38, + RAT_FRAMED_APPLETALK_ZONE = 39, + RAT_ACCT_STATUS_TYPE = 40, + RAT_ACCT_DELAY_TIME = 41, + RAT_ACCT_INPUT_OCTETS = 42, + RAT_ACCT_OUTPUT_OCTETS = 43, + RAT_ACCT_SESSION_ID = 44, + RAT_ACCT_AUTHENTIC = 45, + RAT_ACCT_SESSION_TIME = 46, + RAT_ACCT_INPUT_PACKETS = 47, + RAT_ACCT_OUTPUT_PACKETS = 48, + RAT_ACCT_TERMINATE_CAUSE = 49, + RAT_ACCT_MULTI_SESSION_ID = 50, + RAT_ACCT_LINK_COUNT = 51, + RAT_ACCT_INPUT_GIGAWORDS = 52, + RAT_ACCT_OUTPUT_GIGAWORDS = 53, + RAT_EVENT_TIMESTAMP = 55, + RAT_EGRESS_VLANID = 56, + RAT_INGRESS_FILTERS = 57, + RAT_EGRESS_VLAN_NAME = 58, + RAT_USER_PRIORITY_TABLE = 59, + RAT_CHAP_CHALLENGE = 60, + RAT_NAS_PORT_TYPE = 61, + RAT_PORT_LIMIT = 62, + RAT_LOGIN_LAT_PORT = 63, + RAT_TUNNEL_TYPE = 64, + RAT_TUNNEL_MEDIUM_TYPE = 65, + RAT_TUNNEL_CLIENT_ENDPOINT = 66, + RAT_TUNNEL_SERVER_ENDPOINT = 67, + RAT_ACCT_TUNNEL_CONNECTION = 68, + RAT_TUNNEL_PASSWORD = 69, + RAT_ARAP_PASSWORD = 70, + RAT_ARAP_FEATURES = 71, + RAT_ARAP_ZONE_ACCESS = 72, + RAT_ARAP_SECURITY = 73, + RAT_ARAP_SECURITY_DATA = 74, + RAT_PASSWORD_RETRY = 75, + RAT_PROMPT = 76, + RAT_CONNECT_INFO = 77, + RAT_CONFIGURATION_TOKEN = 78, + RAT_EAP_MESSAGE = 79, + RAT_MESSAGE_AUTHENTICATOR = 80, + RAT_TUNNEL_PRIVATE_GROUP_ID = 81, + RAT_TUNNEL_ASSIGNMENT_ID = 82, + RAT_TUNNEL_PREFERENCE = 83, + RAT_ARAP_CHALLENGE_RESPONSE = 84, + RAT_ACCT_INTERIM_INTERVAL = 85, + RAT_ACCT_TUNNEL_PACKETS_LOST = 86, + RAT_NAS_PORT_ID = 87, + RAT_FRAMED_POOL = 88, + RAT_CUI = 89, + RAT_TUNNEL_CLIENT_AUTH_ID = 90, + RAT_TUNNEL_SERVER_AUTH_ID = 91, + RAT_NAS_FILTER_RULE = 92, + RAT_UNASSIGNED = 93, + RAT_ORIGINATING_LINE_INFO = 94, + RAT_NAS_IPV6_ADDRESS = 95, + RAT_FRAMED_INTERFACE_ID = 96, + RAT_FRAMED_IPV6_PREFIX = 97, + RAT_LOGIN_IPV6_HOST = 98, + RAT_FRAMED_IPV6_ROUTE = 99, + RAT_FRAMED_IPV6_POOL = 100, + RAT_ERROR_CAUSE = 101, + RAT_EAP_KEY_NAME = 102, + RAT_DIGEST_RESPONSE = 103, + RAT_DIGEST_REALM = 104, + RAT_DIGEST_NONCE = 105, + RAT_DIGEST_RESPONSE_AUTH = 106, + RAT_DIGEST_NEXTNONCE = 107, + RAT_DIGEST_METHOD = 108, + RAT_DIGEST_URI = 109, + RAT_DIGEST_QOP = 110, + RAT_DIGEST_ALGORITHM = 111, + RAT_DIGEST_ENTITY_BODY_HASH = 112, + RAT_DIGEST_CNONCE = 113, + RAT_DIGEST_NONCE_COUNT = 114, + RAT_DIGEST_USERNAME = 115, + RAT_DIGEST_OPAQUE = 116, + RAT_DIGEST_AUTH_PARAM = 117, + RAT_DIGEST_AKA_AUTS = 118, + RAT_DIGEST_DOMAIN = 119, + RAT_DIGEST_STALE = 120, + RAT_DIGEST_HA1 = 121, + RAT_SIP_AOR = 122, + RAT_DELEGATED_IPV6_PREFIX = 123, + RAT_MIP6_FEATURE_VECTOR = 124, + RAT_MIP6_HOME_LINK_PREFIX = 125, +}; + +/** + * Enum names for radius_attribute_type_t. + */ +extern enum_name_t *radius_attribute_type_names; + +/** + * A RADIUS message, contains attributes. + */ +struct radius_message_t { + + /** + * Create an enumerator over contained RADIUS attributes. + * + * @return enumerator over (int type, chunk_t data) + */ + enumerator_t* (*create_enumerator)(radius_message_t *this); + + /** + * Add a RADIUS attribute to the message. + * + * @param type type of attribute to add + * @param attribute data, gets cloned + */ + void (*add)(radius_message_t *this, radius_attribute_type_t type, + chunk_t data); + + /** + * Get the message type (code). + * + * @return message code + */ + radius_message_code_t (*get_code)(radius_message_t *this); + + /** + * Get the message identifier. + * + * @return message identifier + */ + u_int8_t (*get_identifier)(radius_message_t *this); + + /** + * Set the message identifier. + * + * @param identifier message identifier + */ + void (*set_identifier)(radius_message_t *this, u_int8_t identifier); + + /** + * Get the 16 byte authenticator. + * + * @return pointer to the Authenticator field + */ + u_int8_t* (*get_authenticator)(radius_message_t *this); + + /** + * Get the RADIUS message in its encoded form. + * + * @return chunk pointing to internal RADIUS message. + */ + chunk_t (*get_encoding)(radius_message_t *this); + + /** + * Calculate and add the Message-Authenticator attribute to the message. + * + * @param rng RNG to create Request-Authenticator + * @param signer HMAC-MD5 signer with secret set + */ + void (*sign)(radius_message_t *this, rng_t *rng, signer_t *signer); + + /** + * Verify the integrity of a received RADIUS response. + * + * @param req_auth 16 byte Authenticator of the corresponding request + * @param secret shared RADIUS secret + * @param hasher hasher to verify Response-Authenticator + * @param signer signer to verify Message-Authenticator attribute + */ + bool (*verify)(radius_message_t *this, u_int8_t *req_auth, chunk_t secret, + hasher_t *hasher, signer_t *signer); + + /** + * Destroy the message. + */ + void (*destroy)(radius_message_t *this); +}; + +/** + * Create an empty RADIUS request message (RMT_ACCESS_REQUEST). + * + * @param identifier RADIUS message identifier + * @return radius_message_t object + */ +radius_message_t *radius_message_create_request(); + +/** + * Parse and verify a recevied RADIUS response. + * + * @param data received message data + * @return radius_message_t object, NULL if length invalid + */ +radius_message_t *radius_message_parse_response(chunk_t data); + +#endif /* RADIUS_MESSAGE_H_ @}*/ diff --git a/src/charon/sa/authenticators/eap/eap_method.c b/src/charon/sa/authenticators/eap/eap_method.c index d6c2b867c..d27a8ac2f 100644 --- a/src/charon/sa/authenticators/eap/eap_method.c +++ b/src/charon/sa/authenticators/eap/eap_method.c @@ -30,7 +30,8 @@ ENUM_NEXT(eap_type_names, EAP_AKA, EAP_AKA, EAP_SIM, "EAP_AKA"); ENUM_NEXT(eap_type_names, EAP_MSCHAPV2, EAP_MSCHAPV2, EAP_AKA, "EAP_MSCHAPV2"); -ENUM_NEXT(eap_type_names, EAP_EXPANDED, EAP_EXPERIMENTAL, EAP_MSCHAPV2, +ENUM_NEXT(eap_type_names, EAP_RADIUS, EAP_EXPERIMENTAL, EAP_MSCHAPV2, + "EAP_RADIUS", "EAP_EXPANDED", "EAP_EXPERIMENTAL"); ENUM_END(eap_type_names, EAP_EXPERIMENTAL); diff --git a/src/charon/sa/authenticators/eap/eap_method.h b/src/charon/sa/authenticators/eap/eap_method.h index edc939deb..c3f7c4862 100644 --- a/src/charon/sa/authenticators/eap/eap_method.h +++ b/src/charon/sa/authenticators/eap/eap_method.h @@ -57,6 +57,8 @@ enum eap_type_t { EAP_SIM = 18, EAP_AKA = 23, EAP_MSCHAPV2 = 26, + /** not a method, but an implementation providing different methods */ + EAP_RADIUS = 253, EAP_EXPANDED = 254, EAP_EXPERIMENTAL = 255, }; diff --git a/src/charon/sa/authenticators/eap_authenticator.c b/src/charon/sa/authenticators/eap_authenticator.c index 0bda6f242..3ef84c81b 100644 --- a/src/charon/sa/authenticators/eap_authenticator.c +++ b/src/charon/sa/authenticators/eap_authenticator.c @@ -141,7 +141,8 @@ static identification_t *get_peer_id(private_eap_authenticator_t *this) { config = this->ike_sa->get_peer_cfg(this->ike_sa); auth = config->get_auth(config); - if (!auth->get_item(auth, AUTHN_EAP_IDENTITY, (void**)&id)) + if (!auth->get_item(auth, AUTHN_EAP_IDENTITY, (void**)&id) || + id->get_type(id) == ID_ANY) { if (this->role == EAP_PEER) { @@ -252,7 +253,7 @@ static status_t initiate(private_eap_authenticator_t *this, eap_type_t type, if (this->method->initiate(this->method, out) != NEED_MORE) { DBG1(DBG_IKE, "failed to initiate EAP exchange, sending %N", - eap_type_names, type, eap_code_names, EAP_FAILURE); + eap_code_names, EAP_FAILURE); *out = eap_payload_create_code(EAP_FAILURE, 0); return FAILED; } diff --git a/src/libstrongswan/crypto/signers/signer.h b/src/libstrongswan/crypto/signers/signer.h index cfc6652bc..bafcd6b8c 100644 --- a/src/libstrongswan/crypto/signers/signer.h +++ b/src/libstrongswan/crypto/signers/signer.h @@ -51,6 +51,8 @@ enum integrity_algorithm_t { AUTH_HMAC_SHA2_512_256 = 14, /** Implemented via hmac_signer_t */ AUTH_HMAC_SHA1_128 = 1025, + /** Implemented via hmac_signer_t */ + AUTH_HMAC_MD5_128 = 1026, }; /** diff --git a/src/libstrongswan/plugins/hmac/hmac_plugin.c b/src/libstrongswan/plugins/hmac/hmac_plugin.c index bf9626bd6..3da635859 100644 --- a/src/libstrongswan/plugins/hmac/hmac_plugin.c +++ b/src/libstrongswan/plugins/hmac/hmac_plugin.c @@ -74,6 +74,8 @@ plugin_t *plugin_create() (signer_constructor_t)hmac_signer_create); lib->crypto->add_signer(lib->crypto, AUTH_HMAC_MD5_96, (signer_constructor_t)hmac_signer_create); + lib->crypto->add_signer(lib->crypto, AUTH_HMAC_MD5_128, + (signer_constructor_t)hmac_signer_create); lib->crypto->add_signer(lib->crypto, AUTH_HMAC_SHA2_384_192, (signer_constructor_t)hmac_signer_create); lib->crypto->add_signer(lib->crypto, AUTH_HMAC_SHA2_512_256, diff --git a/src/libstrongswan/plugins/hmac/hmac_signer.c b/src/libstrongswan/plugins/hmac/hmac_signer.c index 1b6f80d7b..7678a3eeb 100644 --- a/src/libstrongswan/plugins/hmac/hmac_signer.c +++ b/src/libstrongswan/plugins/hmac/hmac_signer.c @@ -159,6 +159,10 @@ hmac_signer_t *hmac_signer_create(integrity_algorithm_t algo) hash = HASH_MD5; trunc = 12; break; + case AUTH_HMAC_MD5_128: + hash = HASH_MD5; + trunc = 16; + break; case AUTH_HMAC_SHA2_256_128: hash = HASH_SHA256; trunc = 16; diff --git a/src/starter/confread.c b/src/starter/confread.c index 8bfc6fe68..56d6dd146 100644 --- a/src/starter/confread.c +++ b/src/starter/confread.c @@ -625,6 +625,10 @@ load_conn(starter_conn_t *conn, kw_list_t *kw, starter_config_t *cfg) { conn->eap_type = 26; } + else if (streq(kw->value, "radius")) + { /* pseudo-type */ + conn->eap_type = 253; + } else { conn->eap_type = atoi(kw->value); diff --git a/src/starter/ipsec.conf.5 b/src/starter/ipsec.conf.5 index 84c503cea..bb6bb45a9 100644 --- a/src/starter/ipsec.conf.5 +++ b/src/starter/ipsec.conf.5 @@ -369,6 +369,10 @@ in the form (e.g. .B eap=7-12345 ) can be used to specify vendor specific EAP types. + +To forward EAP authentication to a RADIUS server using the EAP-RADIUS plugin, +set +.B eap=radius .TP .B eap_identity defines the identity the client uses to reply to a EAP Identity request.