Implemented support for multiple RADIUS servers
This commit is contained in:
@@ -13,6 +13,8 @@ endif
|
||||
libstrongswan_eap_radius_la_SOURCES = \
|
||||
eap_radius_plugin.h eap_radius_plugin.c \
|
||||
eap_radius.h eap_radius.c \
|
||||
radius_server.h radius_server.c \
|
||||
radius_socket.h radius_socket.c \
|
||||
radius_client.h radius_client.c \
|
||||
radius_message.h radius_message.c
|
||||
|
||||
|
||||
@@ -52,11 +52,6 @@ struct private_eap_radius_t {
|
||||
*/
|
||||
u_int32_t vendor;
|
||||
|
||||
/**
|
||||
* EAP MSK, if method established one
|
||||
*/
|
||||
chunk_t msk;
|
||||
|
||||
/**
|
||||
* RADIUS client instance
|
||||
*/
|
||||
@@ -248,8 +243,6 @@ METHOD(eap_method_t, process, status_t,
|
||||
status = FAILED;
|
||||
break;
|
||||
case RMC_ACCESS_ACCEPT:
|
||||
this->msk = this->client->decrypt_msk(this->client,
|
||||
response, request);
|
||||
if (this->class_group)
|
||||
{
|
||||
process_class(this, response);
|
||||
@@ -277,11 +270,14 @@ METHOD(eap_method_t, get_type, eap_type_t,
|
||||
}
|
||||
|
||||
METHOD(eap_method_t, get_msk, status_t,
|
||||
private_eap_radius_t *this, chunk_t *msk)
|
||||
private_eap_radius_t *this, chunk_t *out)
|
||||
{
|
||||
if (this->msk.ptr)
|
||||
chunk_t msk;
|
||||
|
||||
msk = this->client->get_msk(this->client);
|
||||
if (msk.len)
|
||||
{
|
||||
*msk = this->msk;
|
||||
*out = msk;
|
||||
return SUCCESS;
|
||||
}
|
||||
return FAILED;
|
||||
@@ -306,7 +302,6 @@ METHOD(eap_method_t, destroy, void,
|
||||
this->peer->destroy(this->peer);
|
||||
this->server->destroy(this->server);
|
||||
this->client->destroy(this->client);
|
||||
chunk_clear(&this->msk);
|
||||
free(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,15 +17,130 @@
|
||||
|
||||
#include "eap_radius.h"
|
||||
#include "radius_client.h"
|
||||
#include "radius_server.h"
|
||||
|
||||
#include <daemon.h>
|
||||
|
||||
/**
|
||||
* Default RADIUS server port, when not configured
|
||||
*/
|
||||
#define RADIUS_PORT 1812
|
||||
|
||||
typedef struct private_eap_radius_plugin_t private_eap_radius_plugin_t;
|
||||
|
||||
/**
|
||||
* Private data of an eap_radius_plugin_t object.
|
||||
*/
|
||||
struct private_eap_radius_plugin_t {
|
||||
|
||||
/**
|
||||
* Public radius_plugin_t interface.
|
||||
*/
|
||||
eap_radius_plugin_t public;
|
||||
|
||||
/**
|
||||
* List of RADIUS servers
|
||||
*/
|
||||
linked_list_t *servers;
|
||||
};
|
||||
|
||||
/**
|
||||
* Instance of the EAP plugin
|
||||
*/
|
||||
static private_eap_radius_plugin_t *instance = NULL;
|
||||
|
||||
METHOD(plugin_t, destroy, void,
|
||||
eap_radius_plugin_t *this)
|
||||
private_eap_radius_plugin_t *this)
|
||||
{
|
||||
charon->eap->remove_method(charon->eap, (eap_constructor_t)eap_radius_create);
|
||||
radius_client_cleanup();
|
||||
this->servers->destroy_offset(this->servers,
|
||||
offsetof(radius_server_t, destroy));
|
||||
free(this);
|
||||
instance = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load RADIUS servers from configuration
|
||||
*/
|
||||
static bool load_servers(private_eap_radius_plugin_t *this)
|
||||
{
|
||||
enumerator_t *enumerator;
|
||||
radius_server_t *server;
|
||||
char *nas_identifier, *secret, *address, *section;
|
||||
int port, sockets, preference;
|
||||
|
||||
address = lib->settings->get_str(lib->settings,
|
||||
"charon.plugins.eap-radius.server", NULL);
|
||||
if (address)
|
||||
{ /* legacy configuration */
|
||||
secret = lib->settings->get_str(lib->settings,
|
||||
"charon.plugins.eap-radius.secret", NULL);
|
||||
if (!secret)
|
||||
{
|
||||
DBG1(DBG_CFG, "no RADUIS secret defined");
|
||||
return FALSE;
|
||||
}
|
||||
nas_identifier = lib->settings->get_str(lib->settings,
|
||||
"charon.plugins.eap-radius.nas_identifier", "strongSwan");
|
||||
port = lib->settings->get_int(lib->settings,
|
||||
"charon.plugins.eap-radius.port", RADIUS_PORT);
|
||||
sockets = lib->settings->get_int(lib->settings,
|
||||
"charon.plugins.eap-radius.sockets", 1);
|
||||
server = radius_server_create(address, port, nas_identifier,
|
||||
secret, sockets, 0);
|
||||
if (!server)
|
||||
{
|
||||
DBG1(DBG_CFG, "no RADUIS server defined");
|
||||
return FALSE;
|
||||
}
|
||||
this->servers->insert_last(this->servers, server);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
enumerator = lib->settings->create_section_enumerator(lib->settings,
|
||||
"charon.plugins.eap-radius.servers");
|
||||
while (enumerator->enumerate(enumerator, §ion))
|
||||
{
|
||||
address = lib->settings->get_str(lib->settings,
|
||||
"charon.plugins.eap-radius.servers.%s.address", NULL, section);
|
||||
if (!address)
|
||||
{
|
||||
DBG1(DBG_CFG, "RADIUS server '%s' misses address, skipped", section);
|
||||
continue;
|
||||
}
|
||||
secret = lib->settings->get_str(lib->settings,
|
||||
"charon.plugins.eap-radius.servers.%s.secret", NULL, section);
|
||||
if (!secret)
|
||||
{
|
||||
DBG1(DBG_CFG, "RADIUS server '%s' misses secret, skipped", section);
|
||||
continue;
|
||||
}
|
||||
nas_identifier = lib->settings->get_str(lib->settings,
|
||||
"charon.plugins.eap-radius.servers.%s.nas_identifier",
|
||||
"strongSwan", section);
|
||||
port = lib->settings->get_int(lib->settings,
|
||||
"charon.plugins.eap-radius.servers.%s.port", RADIUS_PORT, section);
|
||||
sockets = lib->settings->get_int(lib->settings,
|
||||
"charon.plugins.eap-radius.servers.%s.sockets", 1, section);
|
||||
preference = lib->settings->get_int(lib->settings,
|
||||
"charon.plugins.eap-radius.servers.%s.preference", 0, section);
|
||||
server = radius_server_create(address, port, nas_identifier,
|
||||
secret, sockets, preference);
|
||||
if (!server)
|
||||
{
|
||||
DBG1(DBG_CFG, "loading RADIUS server '%s' failed, skipped", section);
|
||||
continue;
|
||||
}
|
||||
this->servers->insert_last(this->servers, server);
|
||||
}
|
||||
enumerator->destroy(enumerator);
|
||||
|
||||
if (this->servers->get_count(this->servers) == 0)
|
||||
{
|
||||
DBG1(DBG_CFG, "no valid RADIUS server configuration found");
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -33,21 +148,35 @@ METHOD(plugin_t, destroy, void,
|
||||
*/
|
||||
plugin_t *eap_radius_plugin_create()
|
||||
{
|
||||
eap_radius_plugin_t *this;
|
||||
|
||||
if (!radius_client_init())
|
||||
{
|
||||
DBG1(DBG_CFG, "RADIUS plugin initialization failed");
|
||||
return NULL;
|
||||
}
|
||||
private_eap_radius_plugin_t *this;
|
||||
|
||||
INIT(this,
|
||||
.plugin.destroy = _destroy,
|
||||
.public.plugin.destroy = _destroy,
|
||||
.servers = linked_list_create(),
|
||||
);
|
||||
|
||||
if (!load_servers(this))
|
||||
{
|
||||
destroy(this);
|
||||
return NULL;
|
||||
}
|
||||
charon->eap->add_method(charon->eap, EAP_RADIUS, 0,
|
||||
EAP_SERVER, (eap_constructor_t)eap_radius_create);
|
||||
|
||||
return &this->plugin;
|
||||
instance = this;
|
||||
|
||||
return &this->public.plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* See header
|
||||
*/
|
||||
enumerator_t *eap_radius_create_server_enumerator()
|
||||
{
|
||||
if (instance)
|
||||
{
|
||||
return instance->servers->create_enumerator(instance->servers);
|
||||
}
|
||||
return enumerator_create_empty();
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#define EAP_RADIUS_PLUGIN_H_
|
||||
|
||||
#include <plugins/plugin.h>
|
||||
#include <utils/enumerator.h>
|
||||
|
||||
typedef struct eap_radius_plugin_t eap_radius_plugin_t;
|
||||
|
||||
@@ -42,4 +43,11 @@ struct eap_radius_plugin_t {
|
||||
plugin_t plugin;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an enumerator over all loaded RADIUS servers.
|
||||
*
|
||||
* @return enumerator over radius_server_t
|
||||
*/
|
||||
enumerator_t *eap_radius_create_server_enumerator();
|
||||
|
||||
#endif /** EAP_RADIUS_PLUGIN_H_ @}*/
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
|
||||
#include "radius_client.h"
|
||||
|
||||
#include "eap_radius_plugin.h"
|
||||
#include "radius_server.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
@@ -24,42 +27,8 @@
|
||||
#include <threading/condvar.h>
|
||||
#include <threading/mutex.h>
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@@ -70,172 +39,22 @@ struct private_radius_client_t {
|
||||
*/
|
||||
radius_client_t public;
|
||||
|
||||
/**
|
||||
* Selected RADIUS server
|
||||
*/
|
||||
radius_server_t *server;
|
||||
|
||||
/**
|
||||
* RADIUS servers State attribute
|
||||
*/
|
||||
chunk_t state;
|
||||
|
||||
/**
|
||||
* EAP MSK, from MPPE keys
|
||||
*/
|
||||
chunk_t msk;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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", 1);
|
||||
|
||||
sockets = linked_list_create();
|
||||
mutex = mutex_create(MUTEX_TYPE_DEFAULT);
|
||||
condvar = condvar_create(CONDVAR_TYPE_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
|
||||
*/
|
||||
@@ -265,207 +84,51 @@ METHOD(radius_client_t, request, radius_message_t*,
|
||||
private_radius_client_t *this, radius_message_t *req)
|
||||
{
|
||||
char virtual[] = {0x00,0x00,0x00,0x05};
|
||||
entry_t *socket;
|
||||
chunk_t data;
|
||||
int i;
|
||||
radius_socket_t *socket;
|
||||
radius_message_t *res;
|
||||
|
||||
socket = get_socket();
|
||||
|
||||
/* set Message Identifier */
|
||||
req->set_identifier(req, 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);
|
||||
req->add(req, RAT_NAS_IDENTIFIER,
|
||||
this->server->get_nas_identifier(this->server));
|
||||
/* add State attribute, if server sent one */
|
||||
if (this->state.ptr)
|
||||
{
|
||||
req->add(req, RAT_STATE, this->state);
|
||||
}
|
||||
/* sign the request */
|
||||
req->sign(req, socket->rng, socket->signer);
|
||||
|
||||
data = req->get_encoding(req);
|
||||
/* timeout after 2, 3, 4, 5 seconds */
|
||||
for (i = 2; i <= 5; i++)
|
||||
socket = this->server->get_socket(this->server);
|
||||
DBG1(DBG_CFG, "sending RADIUS %N to %#H", radius_message_code_names,
|
||||
req->get_code(req), this->server->get_address(this->server));
|
||||
res = socket->request(socket, req);
|
||||
if (res)
|
||||
{
|
||||
radius_message_t *response;
|
||||
bool retransmit = FALSE;
|
||||
struct timeval tv;
|
||||
char buf[4096];
|
||||
fd_set fds;
|
||||
int res;
|
||||
|
||||
if (send(socket->fd, data.ptr, data.len, 0) != data.len)
|
||||
DBG1(DBG_CFG, "received RADIUS %N from %#H", radius_message_code_names,
|
||||
res->get_code(res), this->server->get_address(this->server));
|
||||
save_state(this, res);
|
||||
if (res->get_code(res) == RMC_ACCESS_ACCEPT)
|
||||
{
|
||||
DBG1(DBG_CFG, "sending RADIUS message failed: %s", strerror(errno));
|
||||
put_socket(socket);
|
||||
return NULL;
|
||||
}
|
||||
tv.tv_sec = i;
|
||||
tv.tv_usec = 0;
|
||||
|
||||
while (TRUE)
|
||||
{
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(socket->fd, &fds);
|
||||
res = select(socket->fd + 1, &fds, NULL, NULL, &tv);
|
||||
/* TODO: updated tv to time not waited. Linux does this for us. */
|
||||
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(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, socket->hasher, socket->signer))
|
||||
{
|
||||
save_state(this, response);
|
||||
put_socket(socket);
|
||||
return response;
|
||||
}
|
||||
response->destroy(response);
|
||||
}
|
||||
DBG1(DBG_CFG, "received invalid RADIUS message, ignored");
|
||||
}
|
||||
if (!retransmit)
|
||||
{
|
||||
break;
|
||||
chunk_clear(&this->msk);
|
||||
this->msk = socket->decrypt_msk(socket, req, res);
|
||||
}
|
||||
this->server->put_socket(this->server, socket, TRUE);
|
||||
return res;
|
||||
}
|
||||
DBG1(DBG_CFG, "RADIUS server is not responding");
|
||||
put_socket(socket);
|
||||
this->server->put_socket(this->server, socket, FALSE);
|
||||
charon->bus->alert(charon->bus, ALERT_RADIUS_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)
|
||||
METHOD(radius_client_t, get_msk, chunk_t,
|
||||
private_radius_client_t *this)
|
||||
{
|
||||
chunk_t A, R, P, seed;
|
||||
u_char *c, *p;
|
||||
hasher_t *hasher;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5);
|
||||
if (!hasher)
|
||||
{
|
||||
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)) */
|
||||
hasher->get_hash(hasher, secret, NULL);
|
||||
hasher->get_hash(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;
|
||||
}
|
||||
hasher->destroy(hasher);
|
||||
|
||||
/* 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));
|
||||
}
|
||||
|
||||
METHOD(radius_client_t, decrypt_msk, chunk_t,
|
||||
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;
|
||||
return this->msk;
|
||||
}
|
||||
|
||||
METHOD(radius_client_t, destroy, void,
|
||||
private_radius_client_t *this)
|
||||
{
|
||||
chunk_clear(&this->msk);
|
||||
free(this->state.ptr);
|
||||
free(this);
|
||||
}
|
||||
@@ -476,15 +139,45 @@ METHOD(radius_client_t, destroy, void,
|
||||
radius_client_t *radius_client_create()
|
||||
{
|
||||
private_radius_client_t *this;
|
||||
enumerator_t *enumerator;
|
||||
radius_server_t *server;
|
||||
int current, best = -1;
|
||||
|
||||
INIT(this,
|
||||
.public = {
|
||||
.request = _request,
|
||||
.decrypt_msk = _decrypt_msk,
|
||||
.get_msk = _get_msk,
|
||||
.destroy = _destroy,
|
||||
},
|
||||
);
|
||||
|
||||
enumerator = eap_radius_create_server_enumerator();
|
||||
while (enumerator->enumerate(enumerator, &server))
|
||||
{
|
||||
current = server->get_preference(server);
|
||||
if (current > best ||
|
||||
/* for two with equal preference, 50-50 chance */
|
||||
(current == best && random() % 2 == 0))
|
||||
{
|
||||
DBG2(DBG_CFG, "RADIUS server %H is candidate: %d",
|
||||
server->get_address(server), current);
|
||||
best = current;
|
||||
this->server = server;
|
||||
}
|
||||
else
|
||||
{
|
||||
DBG2(DBG_CFG, "RADIUS server %H skipped: %d",
|
||||
server->get_address(server), current);
|
||||
}
|
||||
}
|
||||
enumerator->destroy(enumerator);
|
||||
|
||||
if (!this->server)
|
||||
{
|
||||
free(this);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return &this->public;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,19 +29,14 @@ 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. The client reserves
|
||||
* a socket during request() and releases it afterwards.
|
||||
* it. The client allocates a socket from the best RADIUS server abailable.
|
||||
*/
|
||||
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.
|
||||
* The client fills in NAS-Identifier nad NAS-Port-Type
|
||||
*
|
||||
* @param msg RADIUS request message to send
|
||||
* @return response, NULL if timed out/verification failed
|
||||
@@ -49,14 +44,11 @@ struct radius_client_t {
|
||||
radius_message_t* (*request)(radius_client_t *this, radius_message_t *msg);
|
||||
|
||||
/**
|
||||
* Decrypt the MSK encoded in a messages MS-MPPE-Send/Recv-Key.
|
||||
* Get the EAP MSK after successful RADIUS authentication.
|
||||
*
|
||||
* @param response RADIUS response message containing attributes
|
||||
* @param request associated RADIUS request message
|
||||
* @return allocated MSK, empty chunk if none found
|
||||
* @return MSK, allocated
|
||||
*/
|
||||
chunk_t (*decrypt_msk)(radius_client_t *this, radius_message_t *response,
|
||||
radius_message_t *request);
|
||||
chunk_t (*get_msk)(radius_client_t *this);
|
||||
|
||||
/**
|
||||
* Destroy the client, release the socket.
|
||||
@@ -65,24 +57,10 @@ struct radius_client_t {
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a RADIUS client, acquire a socket.
|
||||
*
|
||||
* This call might block if the socket pool is empty.
|
||||
* Create a RADIUS client.
|
||||
*
|
||||
* @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_ @}*/
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Martin Willi
|
||||
* Copyright (C) 2010 revosec AG
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
#include "radius_server.h"
|
||||
|
||||
#include <threading/mutex.h>
|
||||
#include <threading/condvar.h>
|
||||
#include <utils/linked_list.h>
|
||||
|
||||
typedef struct private_radius_server_t private_radius_server_t;
|
||||
|
||||
/**
|
||||
* Private data of an radius_server_t object.
|
||||
*/
|
||||
struct private_radius_server_t {
|
||||
|
||||
/**
|
||||
* Public radius_server_t interface.
|
||||
*/
|
||||
radius_server_t public;
|
||||
|
||||
/**
|
||||
* RADIUS server address
|
||||
*/
|
||||
host_t *host;
|
||||
|
||||
/**
|
||||
* list of radius sockets, as radius_socket_t
|
||||
*/
|
||||
linked_list_t *sockets;
|
||||
|
||||
/**
|
||||
* Total number of sockets, in list + currently in use
|
||||
*/
|
||||
int socket_count;
|
||||
|
||||
/**
|
||||
* mutex to lock sockets list
|
||||
*/
|
||||
mutex_t *mutex;
|
||||
|
||||
/**
|
||||
* condvar to wait for sockets
|
||||
*/
|
||||
condvar_t *condvar;
|
||||
|
||||
/**
|
||||
* RADIUS secret
|
||||
*/
|
||||
chunk_t secret;
|
||||
|
||||
/**
|
||||
* NAS-Identifier
|
||||
*/
|
||||
chunk_t nas_identifier;
|
||||
|
||||
/**
|
||||
* Preference boost for this server
|
||||
*/
|
||||
int preference;
|
||||
|
||||
/**
|
||||
* Is the server currently reachable
|
||||
*/
|
||||
bool reachable;
|
||||
|
||||
/**
|
||||
* Retry counter for unreachable servers
|
||||
*/
|
||||
int retry;
|
||||
};
|
||||
|
||||
METHOD(radius_server_t, get_socket, radius_socket_t*,
|
||||
private_radius_server_t *this)
|
||||
{
|
||||
radius_socket_t *skt;
|
||||
|
||||
this->mutex->lock(this->mutex);
|
||||
while (this->sockets->remove_first(this->sockets, (void**)&skt) != SUCCESS)
|
||||
{
|
||||
this->condvar->wait(this->condvar, this->mutex);
|
||||
}
|
||||
this->mutex->unlock(this->mutex);
|
||||
return skt;
|
||||
}
|
||||
|
||||
METHOD(radius_server_t, put_socket, void,
|
||||
private_radius_server_t *this, radius_socket_t *skt, bool result)
|
||||
{
|
||||
this->mutex->lock(this->mutex);
|
||||
this->sockets->insert_last(this->sockets, skt);
|
||||
this->mutex->unlock(this->mutex);
|
||||
this->condvar->signal(this->condvar);
|
||||
this->reachable = result;
|
||||
}
|
||||
|
||||
METHOD(radius_server_t, get_nas_identifier, chunk_t,
|
||||
private_radius_server_t *this)
|
||||
{
|
||||
return this->nas_identifier;
|
||||
}
|
||||
|
||||
METHOD(radius_server_t, get_preference, int,
|
||||
private_radius_server_t *this)
|
||||
{
|
||||
int pref;
|
||||
|
||||
if (this->socket_count == 0)
|
||||
{ /* don't have sockets, huh? */
|
||||
return -1;
|
||||
}
|
||||
/* calculate preference between 0-100 + boost */
|
||||
pref = this->preference;
|
||||
pref += this->sockets->get_count(this->sockets) * 100 / this->socket_count;
|
||||
if (this->reachable)
|
||||
{ /* reachable server get a boost: pref = 110-210 + boost */
|
||||
return pref + 110;
|
||||
}
|
||||
/* Not reachable. Increase preference randomly to let it retry from
|
||||
* time to time, especially if other servers have high load. */
|
||||
this->retry++;
|
||||
if (this->retry % 128 == 0)
|
||||
{ /* every 64th request gets 210, same as unloaded reachable */
|
||||
return pref + 110;
|
||||
}
|
||||
if (this->retry % 32 == 0)
|
||||
{ /* every 32th request gets 190, wins against average loaded */
|
||||
return pref + 90;
|
||||
}
|
||||
if (this->retry % 8 == 0)
|
||||
{ /* every 8th request gets 110, same as server under load */
|
||||
return pref + 10;
|
||||
}
|
||||
/* other get ~100, less than fully loaded */
|
||||
return pref;
|
||||
}
|
||||
|
||||
METHOD(radius_server_t, get_address, host_t*,
|
||||
private_radius_server_t *this)
|
||||
{
|
||||
return this->host;
|
||||
}
|
||||
|
||||
METHOD(radius_server_t, destroy, void,
|
||||
private_radius_server_t *this)
|
||||
{
|
||||
DESTROY_IF(this->host);
|
||||
this->mutex->destroy(this->mutex);
|
||||
this->condvar->destroy(this->condvar);
|
||||
this->sockets->destroy_offset(this->sockets,
|
||||
offsetof(radius_socket_t, destroy));
|
||||
free(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* See header
|
||||
*/
|
||||
radius_server_t *radius_server_create(char *server, u_int16_t port,
|
||||
char *nas_identifier, char *secret, int sockets, int preference)
|
||||
{
|
||||
private_radius_server_t *this;
|
||||
radius_socket_t *socket;
|
||||
|
||||
INIT(this,
|
||||
.public = {
|
||||
.get_socket = _get_socket,
|
||||
.put_socket = _put_socket,
|
||||
.get_nas_identifier = _get_nas_identifier,
|
||||
.get_preference = _get_preference,
|
||||
.get_address = _get_address,
|
||||
.destroy = _destroy,
|
||||
},
|
||||
.reachable = TRUE,
|
||||
.nas_identifier = chunk_create(nas_identifier, strlen(nas_identifier)),
|
||||
.socket_count = sockets,
|
||||
.sockets = linked_list_create(),
|
||||
.mutex = mutex_create(MUTEX_TYPE_DEFAULT),
|
||||
.condvar = condvar_create(CONDVAR_TYPE_DEFAULT),
|
||||
.host = host_create_from_dns(server, 0, port),
|
||||
.preference = preference,
|
||||
);
|
||||
|
||||
if (!this->host)
|
||||
{
|
||||
destroy(this);
|
||||
return NULL;
|
||||
}
|
||||
while (sockets--)
|
||||
{
|
||||
socket = radius_socket_create(this->host,
|
||||
chunk_create(secret, strlen(secret)));
|
||||
if (!socket)
|
||||
{
|
||||
destroy(this);
|
||||
return NULL;
|
||||
}
|
||||
this->sockets->insert_last(this->sockets, socket);
|
||||
}
|
||||
return &this->public;
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Martin Willi
|
||||
* Copyright (C) 2010 revosec AG
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup radius_server radius_server
|
||||
* @{ @ingroup eap_radius
|
||||
*/
|
||||
|
||||
#ifndef RADIUS_SERVER_H_
|
||||
#define RADIUS_SERVER_H_
|
||||
|
||||
typedef struct radius_server_t radius_server_t;
|
||||
|
||||
#include "radius_socket.h"
|
||||
|
||||
/**
|
||||
* RADIUS server configuration.
|
||||
*/
|
||||
struct radius_server_t {
|
||||
|
||||
/**
|
||||
* Get a RADIUS socket from the pool to communicate with this server.
|
||||
*
|
||||
* @return RADIUS socket
|
||||
*/
|
||||
radius_socket_t* (*get_socket)(radius_server_t *this);
|
||||
|
||||
/**
|
||||
* Release a socket to the pool after use.
|
||||
*
|
||||
* @param skt RADIUS socket to release
|
||||
* @param result result of the socket use, TRUE for success
|
||||
*/
|
||||
void (*put_socket)(radius_server_t *this, radius_socket_t *skt, bool result);
|
||||
|
||||
/**
|
||||
* Get the NAS-Identifier to use with this server.
|
||||
*
|
||||
* @return NAS-Identifier, internal data
|
||||
*/
|
||||
chunk_t (*get_nas_identifier)(radius_server_t *this);
|
||||
|
||||
/**
|
||||
* Get the preference of this server.
|
||||
*
|
||||
* Based on the available sockets and the server reachability a preference
|
||||
* value is calculated: better servers return a higher value.
|
||||
*/
|
||||
int (*get_preference)(radius_server_t *this);
|
||||
|
||||
/**
|
||||
* Get the address of the RADIUS server.
|
||||
*
|
||||
* @return address, internal data
|
||||
*/
|
||||
host_t* (*get_address)(radius_server_t *this);
|
||||
|
||||
/**
|
||||
* Destroy a radius_server_t.
|
||||
*/
|
||||
void (*destroy)(radius_server_t *this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a radius_server instance.
|
||||
*
|
||||
* @param server server address
|
||||
* @param port server port
|
||||
* @param nas_identifier NAS-Identifier to use with this server
|
||||
* @param sockets number of sockets to create in pool
|
||||
* @param preference preference boost for this server
|
||||
*/
|
||||
radius_server_t *radius_server_create(char *server, u_int16_t port,
|
||||
char *nas_identifier, char *secret, int sockets, int preference);
|
||||
|
||||
#endif /** RADIUS_SERVER_H_ @}*/
|
||||
@@ -0,0 +1,309 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Martin Willi
|
||||
* Copyright (C) 2010 revosec AG
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
#include "radius_socket.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <debug.h>
|
||||
|
||||
/**
|
||||
* 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_socket_t private_radius_socket_t;
|
||||
|
||||
/**
|
||||
* Private data of an radius_socket_t object.
|
||||
*/
|
||||
struct private_radius_socket_t {
|
||||
|
||||
/**
|
||||
* Public radius_socket_t interface.
|
||||
*/
|
||||
radius_socket_t public;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* RADIUS secret
|
||||
*/
|
||||
chunk_t secret;
|
||||
};
|
||||
|
||||
METHOD(radius_socket_t, request, radius_message_t*,
|
||||
private_radius_socket_t *this, radius_message_t *request)
|
||||
{
|
||||
chunk_t data;
|
||||
int i;
|
||||
|
||||
/* set Message Identifier */
|
||||
request->set_identifier(request, this->identifier++);
|
||||
/* sign the request */
|
||||
request->sign(request, this->rng, this->signer);
|
||||
|
||||
data = request->get_encoding(request);
|
||||
/* timeout after 2, 3, 4, 5 seconds */
|
||||
for (i = 2; i <= 5; i++)
|
||||
{
|
||||
radius_message_t *response;
|
||||
bool retransmit = FALSE;
|
||||
struct timeval tv;
|
||||
char buf[4096];
|
||||
fd_set fds;
|
||||
int res;
|
||||
|
||||
if (send(this->fd, data.ptr, data.len, 0) != data.len)
|
||||
{
|
||||
DBG1(DBG_CFG, "sending RADIUS message failed: %s", strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
tv.tv_sec = i;
|
||||
tv.tv_usec = 0;
|
||||
|
||||
while (TRUE)
|
||||
{
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(this->fd, &fds);
|
||||
res = select(this->fd + 1, &fds, NULL, NULL, &tv);
|
||||
/* TODO: updated tv to time not waited. Linux does this for us. */
|
||||
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->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,
|
||||
request->get_authenticator(request), this->secret,
|
||||
this->hasher, this->signer))
|
||||
{
|
||||
return response;
|
||||
}
|
||||
response->destroy(response);
|
||||
}
|
||||
DBG1(DBG_CFG, "received invalid RADIUS message, ignored");
|
||||
}
|
||||
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_socket_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->hasher->get_hash(this->hasher, this->secret, NULL);
|
||||
this->hasher->get_hash(this->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));
|
||||
}
|
||||
|
||||
METHOD(radius_socket_t, decrypt_msk, chunk_t,
|
||||
private_radius_socket_t *this, radius_message_t *request,
|
||||
radius_message_t *response)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
METHOD(radius_socket_t, destroy, void,
|
||||
private_radius_socket_t *this)
|
||||
{
|
||||
DESTROY_IF(this->hasher);
|
||||
DESTROY_IF(this->signer);
|
||||
DESTROY_IF(this->rng);
|
||||
close(this->fd);
|
||||
free(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* See header
|
||||
*/
|
||||
radius_socket_t *radius_socket_create(host_t *host, chunk_t secret)
|
||||
{
|
||||
private_radius_socket_t *this;
|
||||
|
||||
INIT(this,
|
||||
.public = {
|
||||
.request = _request,
|
||||
.decrypt_msk = _decrypt_msk,
|
||||
.destroy = _destroy,
|
||||
},
|
||||
);
|
||||
|
||||
this->fd = socket(host->get_family(host), SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (this->fd < 0)
|
||||
{
|
||||
DBG1(DBG_CFG, "opening RADIUS socket failed: %s", strerror(errno));
|
||||
free(this);
|
||||
return NULL;
|
||||
}
|
||||
if (connect(this->fd, host->get_sockaddr(host),
|
||||
*host->get_sockaddr_len(host)) < 0)
|
||||
{
|
||||
DBG1(DBG_CFG, "connecting RADIUS socket failed");
|
||||
close(this->fd);
|
||||
free(this);
|
||||
return NULL;
|
||||
}
|
||||
this->hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5);
|
||||
this->signer = lib->crypto->create_signer(lib->crypto, AUTH_HMAC_MD5_128);
|
||||
this->rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK);
|
||||
if (!this->hasher || !this->signer || !this->rng)
|
||||
{
|
||||
DBG1(DBG_CFG, "RADIUS initialization failed, HMAC/MD5/RNG required");
|
||||
destroy(this);
|
||||
return NULL;
|
||||
}
|
||||
this->secret = secret;
|
||||
this->signer->set_key(this->signer, secret);
|
||||
/* we use a random identifier, helps if we restart often */
|
||||
this->identifier = random();
|
||||
|
||||
return &this->public;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Martin Willi
|
||||
* Copyright (C) 2010 revosec AG
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup radius_socket radius_socket
|
||||
* @{ @ingroup eap_radius
|
||||
*/
|
||||
|
||||
#ifndef RADIUS_SOCKET_H_
|
||||
#define RADIUS_SOCKET_H_
|
||||
|
||||
typedef struct radius_socket_t radius_socket_t;
|
||||
|
||||
#include "radius_message.h"
|
||||
|
||||
#include <utils/host.h>
|
||||
|
||||
/**
|
||||
* RADIUS socket to a server.
|
||||
*/
|
||||
struct radius_socket_t {
|
||||
|
||||
/**
|
||||
* Send a RADIUS request, wait for response.
|
||||
|
||||
* The socket fills in RADIUS Message identifier, 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 request request message
|
||||
* @return response message, NULL if timed out
|
||||
*/
|
||||
radius_message_t* (*request)(radius_socket_t *this,
|
||||
radius_message_t *request);
|
||||
|
||||
/**
|
||||
* Decrypt the MSK encoded in a messages MS-MPPE-Send/Recv-Key.
|
||||
*
|
||||
* @param request associated RADIUS request message
|
||||
* @param response RADIUS response message containing attributes
|
||||
* @return allocated MSK, empty chunk if none found
|
||||
*/
|
||||
chunk_t (*decrypt_msk)(radius_socket_t *this, radius_message_t *request,
|
||||
radius_message_t *response);
|
||||
|
||||
/**
|
||||
* Destroy a radius_socket_t.
|
||||
*/
|
||||
void (*destroy)(radius_socket_t *this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a radius_socket instance.
|
||||
*
|
||||
* @param host RADIUS server address to connect to
|
||||
* @param secret RADIUS secret
|
||||
*/
|
||||
radius_socket_t *radius_socket_create(host_t *host, chunk_t secret);
|
||||
|
||||
#endif /** RADIUS_SOCKET_H_ @}*/
|
||||
Reference in New Issue
Block a user