start of osmo-epdg plugin

- simple gsup/ipa working
- strongswan is requesting tuples via GSUP.
- strongswan client can authenticate
- SWu-IKEv2 can't authenticate

ToDos:
- gsup: disconnect/reconnect
- gsup: failures cases
- blocking queue needs to be cleaned up
- fix coding style
This commit is contained in:
Alexander Couzens 2023-01-30 02:39:57 +01:00
parent e7e6a51fb1
commit 1c92c4d83e
20 changed files with 1858 additions and 0 deletions

View File

@ -212,6 +212,7 @@ ARG_ENABL_SET([eap-peap], [enable EAP PEAP authentication module.])
ARG_ENABL_SET([eap-tnc], [enable EAP TNC trusted network connect module.])
ARG_ENABL_SET([eap-dynamic], [enable dynamic EAP proxy module.])
ARG_ENABL_SET([eap-radius], [enable RADIUS proxy authentication module.])
ARG_ENABL_SET([osmo-epdg], [enable osmo-epdg plugin.])
ARG_ENABL_SET([ext-auth], [enable plugin calling an external authorization script.])
ARG_ENABL_SET([ipseckey], [enable IPSECKEY authentication plugin.])
ARG_ENABL_SET([keychain], [enables OS X Keychain Services credential set.])
@ -431,6 +432,11 @@ if test x$eap_aka_3gpp2 = xtrue; then
gmp=true;
fi
if test x$osmo_epdg = xtrue; then
simaka=true;
eap_aka=true;
fi
if test x$eap_aka = xtrue; then
fips_prf=true;
simaka=true;
@ -1513,6 +1519,7 @@ ADD_PLUGIN([kernel-pfkey], [c charon starter nm cmd])
ADD_PLUGIN([kernel-pfroute], [c charon starter nm cmd])
ADD_PLUGIN([kernel-netlink], [c charon starter nm cmd])
ADD_PLUGIN([resolve], [c charon cmd])
ADD_PLUGIN([osmo-epdg], [c charon])
ADD_PLUGIN([save-keys], [c])
ADD_PLUGIN([socket-default], [c charon nm cmd])
ADD_PLUGIN([socket-dynamic], [c charon cmd])
@ -1720,6 +1727,7 @@ AM_CONDITIONAL(USE_EAP_PEAP, test x$eap_peap = xtrue)
AM_CONDITIONAL(USE_EAP_TNC, test x$eap_tnc = xtrue)
AM_CONDITIONAL(USE_EAP_DYNAMIC, test x$eap_dynamic = xtrue)
AM_CONDITIONAL(USE_EAP_RADIUS, test x$eap_radius = xtrue)
AM_CONDITIONAL(USE_OSMO_EPDG, test x$osmo_epdg = xtrue)
AM_CONDITIONAL(USE_XAUTH_GENERIC, test x$xauth_generic = xtrue)
AM_CONDITIONAL(USE_XAUTH_EAP, test x$xauth_eap = xtrue)
AM_CONDITIONAL(USE_XAUTH_PAM, test x$xauth_pam = xtrue)
@ -2005,6 +2013,7 @@ AC_CONFIG_FILES([
src/libcharon/plugins/eap_peap/Makefile
src/libcharon/plugins/eap_tnc/Makefile
src/libcharon/plugins/eap_radius/Makefile
src/libcharon/plugins/osmo_epdg/Makefile
src/libcharon/plugins/xauth_generic/Makefile
src/libcharon/plugins/xauth_eap/Makefile
src/libcharon/plugins/xauth_pam/Makefile

View File

@ -467,6 +467,13 @@ if MONOLITHIC
endif
endif
if USE_OSMO_EPDG
SUBDIRS += plugins/osmo_epdg
if MONOLITHIC
libcharon_la_LIBADD += plugins/osmo_epdg/libstrongswan-osmo-epdg.la
endif
endif
if USE_TLS
if MONOLITHIC
# otherwise this library is linked to eap_tls

View File

@ -949,6 +949,46 @@ METHOD(bus_t, ike_reestablish_post, void,
this->mutex->unlock(this->mutex);
}
METHOD(bus_t, eap_authorize, bool,
private_bus_t *this, identification_t *id, bool final)
{
enumerator_t *enumerator;
ike_sa_t *ike_sa;
entry_t *entry;
bool keep, success = TRUE;
ike_sa = this->thread_sa->get(this->thread_sa);
this->mutex->lock(this->mutex);
enumerator = this->listeners->create_enumerator(this->listeners);
while (enumerator->enumerate(enumerator, &entry))
{
if (entry->calling || !entry->listener->eap_authorize)
{
continue;
}
entry->calling++;
keep = entry->listener->eap_authorize(entry->listener, ike_sa, id,
final, &success);
entry->calling--;
if (!keep)
{
unregister_listener(this, entry, enumerator);
}
if (!success)
{
break;
}
}
enumerator->destroy(enumerator);
this->mutex->unlock(this->mutex);
if (!success)
{
alert(this, ALERT_EAP_AUTHORIZATION_FAILED);
}
return success;
}
METHOD(bus_t, authorize, bool,
private_bus_t *this, bool final)
{
@ -1154,6 +1194,7 @@ bus_t *bus_create()
.child_rekey = _child_rekey,
.children_migrate = _children_migrate,
.authorize = _authorize,
.eap_authorize = _eap_authorize,
.narrow = _narrow,
.assign_vips = _assign_vips,
.handle_vips = _handle_vips,

View File

@ -157,6 +157,8 @@ enum alert_t {
ALERT_CERT_EXCEEDED_PATH_LEN,
/** Certificate rejected; other policy violation, certificate_t */
ALERT_CERT_POLICY_VIOLATION,
/** an eap_authorize() hook failed, no argument */
ALERT_EAP_AUTHORIZATION_FAILED,
};
/**
@ -332,6 +334,14 @@ struct bus_t {
*/
bool (*authorize)(bus_t *this, bool final);
/**
* EAP authorization hook.
*
* @param final TRUE if this is the final invocation
* @return TRUE to establish IKE_SA, FALSE to send AUTH_FAILED
*/
bool (*eap_authorize)(bus_t *this, identification_t *id, bool final);
/**
* CHILD_SA traffic selector narrowing hook.
*

View File

@ -238,6 +238,21 @@ struct listener_t {
bool (*children_migrate)(listener_t *this, ike_sa_t *ike_sa,
ike_sa_id_t *new, uint32_t unique);
/**
* Hook called to invoke additional eap authorization rules.
*
* An eap authorization hook gets invoked only before sending the EAP_SUCCESS.
*
* @param ike_sa IKE_SA to authorize
* @param id EAP identification
* @param final TRUE if this is the final hook invocation
* @param success set to TRUE to complete IKE_SA, FALSE abort
* @return TRUE to stay registered, FALSE to unregister
*/
bool (*eap_authorize)(listener_t *this, ike_sa_t *ike_sa,
identification_t *id,
bool final, bool *success);
/**
* Hook called to invoke additional authorization rules.
*

View File

@ -116,6 +116,11 @@ struct private_eap_aka_server_t {
* Did the client send a synchronize request?
*/
bool synchronized;
/**
* Auth data contains porperty on EAP_SUCCESS.
*/
auth_cfg_t *auth;
};
/**
@ -430,6 +435,8 @@ static status_t process_challenge(private_eap_aka_server_t *this,
DBG1(DBG_IKE, "received RES does not match XRES");
return FAILED;
}
this->auth->add(this->auth, AUTH_RULE_EAP_IDENTITY, this->permanent->clone(this->permanent));
return SUCCESS;
}
@ -643,6 +650,12 @@ METHOD(eap_method_t, get_type, eap_type_t,
return EAP_AKA;
}
METHOD(eap_method_t, get_auth, auth_cfg_t*,
private_eap_aka_server_t *this)
{
return this->auth;
}
METHOD(eap_method_t, get_msk, status_t,
private_eap_aka_server_t *this, chunk_t *msk)
{
@ -677,6 +690,7 @@ METHOD(eap_method_t, destroy, void,
{
this->crypto->destroy(this->crypto);
this->permanent->destroy(this->permanent);
this->auth->destroy(this->auth);
DESTROY_IF(this->pseudonym);
DESTROY_IF(this->reauth);
free(this->xres.ptr);
@ -703,12 +717,14 @@ eap_aka_server_t *eap_aka_server_create(identification_t *server,
.get_type = _get_type,
.is_mutual = _is_mutual,
.get_msk = _get_msk,
.get_auth = _get_auth,
.get_identifier = _get_identifier,
.set_identifier = _set_identifier,
.destroy = _destroy,
},
},
.crypto = simaka_crypto_create(EAP_AKA),
.auth = auth_cfg_create(),
.mgr = lib->get(lib, "aka-manager"),
);

View File

@ -0,0 +1,25 @@
AM_CPPFLAGS = \
-I$(top_srcdir)/src/libstrongswan \
-I$(top_srcdir)/src/libcharon \
-I$(top_srcdir)/src/libsimaka
AM_CFLAGS = \
$(PLUGIN_CFLAGS)
libstrongswan_osmo_epdg_la_LDFLAGS = -module -avoid-version
libstrongswan_osmo_epdg_la_LIBADD = -lgmp -losmogsm
if MONOLITHIC
noinst_LTLIBRARIES = libstrongswan-osmo-epdg.la
else
plugin_LTLIBRARIES = libstrongswan-osmo-epdg.la
libstrongswan_osmo_epdg_la_LIBADD += $(top_builddir)/src/libsimaka/libsimaka.la
endif
libstrongswan_osmo_epdg_la_SOURCES = \
osmo_epdg_plugin.h osmo_epdg_plugin.c \
osmo_epdg_provider.h osmo_epdg_provider.c \
osmo_epdg_listener.h osmo_epdg_listener.c \
gsup_client.h gsup_client.c \
ipa_client.h ipa_client.c \
osmo_epdg_utils.h osmo_epdg_utils.c

View File

@ -0,0 +1,507 @@
/*
* Copyright (C) 2023 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Alexander Couzens <acouzens@sysmocom.de>
*
* SPDX-License-Identifier: GPL-2.0+
*
* 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.
*
* 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.
*
*/
/* TODO: check license */
#include <collections/enumerator.h>
#include <collections/linked_list.h>
#include <collections/blocking_queue.h>
#include <processing/jobs/callback_job.h>
#include <threading/mutex.h>
#include <threading/thread.h>
#include <threading/condvar.h>
#include <osmocom/core/msgb.h>
#include <osmocom/gsm/gsup.h>
#include <osmocom/gsm/protocol/ipaccess.h>
#include <library.h>
#include "ipa_client.h"
#include "gsup_client.h"
#include "osmo_epdg_utils.h"
typedef struct gsup_request_t gsup_request_t;
struct gsup_request_t {
/**
* Mutex used to synchronize access to the condvar
*/
mutex_t *mutex;
/**
* Condvar used to wait for a response
*/
condvar_t *condvar;
struct msgb *msg;
enum osmo_gsup_message_type msg_type;
osmo_epdg_gsup_response_t *resp;
};
typedef struct private_osmo_epdg_gsup_client_t private_osmo_epdg_gsup_client_t;
struct private_osmo_epdg_gsup_client_t {
/**
* Public osmo_epdg_gsup_client_t
*/
osmo_epdg_gsup_client_t public;
osmo_epdg_ipa_client_t *ipa;
/**
* List of all pending requests
*/
blocking_queue_t *pending;
/**
* Current request which isn't part of linked list
*/
gsup_request_t *current_request;
/**
* Mutex to protect current_request
*/
mutex_t *mutex;
char *uri;
stream_t *stream;
};
static gsup_request_t *gsup_request_create(enum osmo_gsup_message_type msg_type, struct msgb *msg)
{
gsup_request_t *req = calloc(1, sizeof(gsup_request_t));
if (!req)
{
return NULL;
}
req->mutex = mutex_create(MUTEX_TYPE_DEFAULT);
req->condvar = condvar_create(CONDVAR_TYPE_DEFAULT);
req->msg_type = msg_type;
req->msg = msg;
return req;
}
static void gsup_request_destroy(private_osmo_epdg_gsup_client_t *this, gsup_request_t *req)
{
if (!req)
{
return;
}
if (req->mutex)
{
req->mutex->destroy(req->mutex);
}
if (req->condvar)
{
req->condvar->destroy(req->condvar);
}
if (req->msg)
{
free(req->msg);
}
if (req->resp)
{
free(req->resp);
}
free(req);
}
static struct msgb *encode_to_msgb(struct osmo_gsup_message *gsup_msg)
{
chunk_t msg_chunk;
struct msgb *msg;
int ret;
msg_chunk = chunk_alloc(4000);
if (msg_chunk.ptr == NULL)
{
return NULL;
}
msg = chunk_to_msgb(&msg_chunk);
if (!msg)
{
goto free_msg;
}
/* reserve headroom */
msgb_reserve(msg, 64);
ret = osmo_gsup_encode(msg, gsup_msg);
if (ret)
{
DBG1(DBG_NET, "GSUP: couldn't encode gsup message %d.", ret);
goto free_msg;
}
return msg;
free_msg:
chunk_free(&msg_chunk);
return NULL;
}
/**
* enqueue a message/request to be send out and wait for the response.
*
* when exiting enqueue, it must be guaranteed the req isn't referenced by anything
* @param timeout_ms A timeout in ms
* @return TRUE is the request timed out.
*/
static bool enqueue(private_osmo_epdg_gsup_client_t *this, gsup_request_t *req, u_int timeout_ms)
{
bool ret = FALSE;
DBG1(DBG_NET, "Enqueuing message. Waiting %d ms for an answer", timeout_ms);
req->mutex->lock(req->mutex);
this->pending->enqueue(this->pending, req);
ret = req->condvar->timed_wait(req->condvar, req->mutex, timeout_ms);
if (ret)
{
void *found = this->pending->remove(this->pending, req);
if (!found)
{
this->mutex->lock(this->mutex);
if (this->current_request == req)
{
this->current_request = NULL;
}
this->mutex->unlock(this->mutex);
}
DBG1(DBG_NET, "Message timedout!");
}
return ret;
}
METHOD(osmo_epdg_gsup_client_t, send_auth_request, osmo_epdg_gsup_response_t*,
private_osmo_epdg_gsup_client_t *this, char *imsi, uint8_t cn_domain, chunk_t *auts, chunk_t *auts_rand)
{
struct osmo_gsup_message gsup_msg = {0};
struct msgb *msg;
DBG1(DBG_NET, "Send Auth Request for %s", imsi);
gsup_msg.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_REQUEST;
gsup_msg.num_auth_vectors = 1;
gsup_msg.current_rat_type = OSMO_RAT_EUTRAN_SGS;
if (!imsi || strlen(imsi) == 0)
{
/* TODO: inval imsi! */
return NULL;
}
strncpy(gsup_msg.imsi, imsi, sizeof(gsup_msg.imsi));
switch (cn_domain)
{
case 0:
/* empty cn_domain */
break;
case OSMO_GSUP_CN_DOMAIN_PS:
case OSMO_GSUP_CN_DOMAIN_CS:
gsup_msg.cn_domain = cn_domain;
break;
default:
DBG1(DBG_NET, "GSUP: SAIR: Ignoring invalid cn_domain message.");
break;
}
if (auts && auts->ptr && auts->len != 0)
{
if (auts->len != 14)
{
/* TODO: inval auts */
return NULL;
}
gsup_msg.auts = auts->ptr;
}
/* TODO check for other sizes */
if (auts_rand && auts_rand->ptr && auts_rand->len != 0)
{
if (auts_rand->len != 16)
{
/* TODO: inval auts */
return NULL;
}
gsup_msg.rand = auts_rand->ptr;
}
msg = encode_to_msgb(&gsup_msg);
if (!msg)
{
DBG1(DBG_NET, "Couldn't alloc/encode gsup message.");
return NULL;
}
bool timedout = FALSE;
gsup_request_t *req = gsup_request_create(OSMO_GSUP_MSGT_SEND_AUTH_INFO_REQUEST, msg);
osmo_epdg_gsup_response_t *resp = NULL;
timedout = enqueue(this, req, 5000);
if (timedout)
{
gsup_request_destroy(this, req);
return NULL;
}
resp = req->resp;
req->resp = NULL;
gsup_request_destroy(this, req);
return resp;
}
METHOD(osmo_epdg_gsup_client_t, update_location, osmo_epdg_gsup_response_t *,
private_osmo_epdg_gsup_client_t *this, char *imsi, uint8_t cn_domain)
{
struct osmo_gsup_message gsup_msg = {0};
struct msgb *msg;
gsup_msg.message_type = OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST;
gsup_msg.current_rat_type = OSMO_RAT_EUTRAN_SGS;
if (!imsi || strlen(imsi) == 0)
{
DBG1(DBG_NET, "GSUP: ULR: Invalid IMSI!");
return NULL;
}
strncpy(gsup_msg.imsi, imsi, sizeof(gsup_msg.imsi));
switch (cn_domain)
{
case 0:
/* empty cn_domain */
break;
case OSMO_GSUP_CN_DOMAIN_PS:
case OSMO_GSUP_CN_DOMAIN_CS:
gsup_msg.cn_domain = cn_domain;
break;
default:
DBG1(DBG_NET, "GSUP: ULR: Ignoring invalid cn_domain message.");
break;
}
msg = encode_to_msgb(&gsup_msg);
if (!msg)
{
DBG1(DBG_NET, "GSUP: ULR: Couldn't alloc/encode gsup message.");
return NULL;
}
bool timedout = FALSE;
gsup_request_t *req = gsup_request_create(OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST, msg);
osmo_epdg_gsup_response_t *resp = NULL;
timedout = enqueue(this, req, 5000);
if (timedout)
{
gsup_request_destroy(this, req);
return NULL;
}
resp = req->resp;
req->resp = NULL;
gsup_request_destroy(this, req);
return resp;
}
METHOD(osmo_epdg_gsup_client_t, destroy, void,
private_osmo_epdg_gsup_client_t *this)
{
free(this->uri);
free(this);
}
void tx_insert_data_result(private_osmo_epdg_gsup_client_t *this, char *imsi, uint8_t cn_domain)
{
struct osmo_gsup_message gsup_msg = {0};
struct msgb *msg;
gsup_msg.message_type = OSMO_GSUP_MSGT_INSERT_DATA_RESULT;
if (!imsi || strlen(imsi) == 0)
{
DBG1(DBG_NET, "GSUP: ULR: Invalid IMSI!");
}
strncpy(gsup_msg.imsi, imsi, sizeof(gsup_msg.imsi));
switch (cn_domain)
{
case 0:
/* empty cn_domain */
break;
case OSMO_GSUP_CN_DOMAIN_PS:
case OSMO_GSUP_CN_DOMAIN_CS:
gsup_msg.cn_domain = cn_domain;
break;
default:
DBG1(DBG_NET, "GSUP: ULR: Ignoring invalid cn_domain message.");
break;
}
msg = encode_to_msgb(&gsup_msg);
if (!msg)
{
DBG1(DBG_NET, "GSUP: ULR: Couldn't alloc/encode gsup message.");
}
this->ipa->send(this->ipa, IPAC_PROTO_EXT_GSUP, msg);
}
static void signal_request(gsup_request_t *req, osmo_epdg_gsup_response_t *resp)
{
req->mutex->lock(req->mutex);
req->resp = resp;
req->condvar->signal(req->condvar);
req->mutex->unlock(req->mutex);
}
static bool on_recv_pdu(void *data, osmo_epdg_ipa_client_t *client, struct msgb *pdu)
{
private_osmo_epdg_gsup_client_t *this = data;
osmo_epdg_gsup_response_t *resp;
int ret;
resp = calloc(1, sizeof(*resp));
if (!resp)
{
return TRUE;
}
ret = osmo_gsup_decode(msgb_l2(pdu), msgb_l2len(pdu), &resp->gsup);
if (ret) {
// TODO: failed to decode response. Close connection
goto out;
}
switch (resp->gsup.message_type)
{
case OSMO_GSUP_MSGT_INSERT_DATA_REQUEST:
tx_insert_data_result(this, resp->gsup.imsi, resp->gsup.cn_domain);
goto out;
case OSMO_GSUP_MSGT_SEND_AUTH_INFO_ERROR:
case OSMO_GSUP_MSGT_SEND_AUTH_INFO_RESULT:
case OSMO_GSUP_MSGT_UPDATE_LOCATION_ERROR:
case OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT:
this->mutex->lock(this->mutex);
if ((this->current_request->msg_type & 0xfffffffc) != (resp->gsup.message_type & 0xfffffffc))
{
/* Request, Result, Error, Other are encoded in the last 2 bits */
DBG1(DBG_NET, "GSUP: received non matching Result. Requested %s but received %s",
osmo_gsup_message_type_name(this->current_request->msg_type),
osmo_gsup_message_type_name(resp->gsup.message_type));
goto out;
}
if (!this->current_request)
{
DBG2(DBG_NET, "GSUP: received response when no request waiting %02x. This might came too late.", resp->gsup.message_type);
this->mutex->unlock(this->mutex);
goto out;
}
signal_request(this->current_request, resp);
this->current_request = NULL;
this->mutex->unlock(this->mutex);
break;
default:
DBG1(DBG_NET, "GSUP received unknown message type %02x", resp->gsup.message_type);
goto out;
}
free(pdu);
return TRUE;
out:
free(resp);
free(pdu);
return TRUE;
}
static int disconnect_gsup(private_osmo_epdg_gsup_client_t *this)
{
this->ipa->disconnect(this->ipa);
return 0;
}
/* TODO: worker thread which sends out enqueue'd message ! */
static job_requeue_t queue_worker(private_osmo_epdg_gsup_client_t *this)
{
int ret;
gsup_request_t *req;
this->mutex->lock(this->mutex);
if (this->current_request)
{
/* TODO: should we join the signal? */
this->mutex->unlock(this->mutex);
return JOB_REQUEUE_FAIR;
}
this->mutex->unlock(this->mutex);
/* TODO: replace pending with a thread safe queue, but non-blocking */
req = this->pending->dequeue(this->pending);
this->mutex->lock(this->mutex);
if (this->current_request)
{
/* TODO: how could this happen? */
this->mutex->unlock(this->mutex);
signal_request(req, NULL);
return JOB_REQUEUE_FAIR;
}
this->current_request = req;
this->mutex->unlock(this->mutex);
ret = this->ipa->send(this->ipa, IPAC_PROTO_EXT_GSUP, req->msg);
req->msg = NULL;
if (ret < 0)
{
/* TODO: disconnect & reconnect, but request is lost for now */
/* TODO: wake up request */
signal_request(req, NULL);
}
return JOB_REQUEUE_FAIR;
}
osmo_epdg_gsup_client_t *osmo_epdg_gsup_client_create(char *uri)
{
private_osmo_epdg_gsup_client_t *this;
DBG1(DBG_NET, "Starting osmo-epdg");
INIT(this,
.public = {
.send_auth_request = _send_auth_request,
.update_location = _update_location,
.destroy = _destroy,
},
.uri = strdup(uri),
.pending = blocking_queue_create(),
.current_request = NULL,
.mutex = mutex_create(MUTEX_TYPE_DEFAULT),
.ipa = osmo_epdg_ipa_client_create(uri),
);
this->ipa->on_recv(this->ipa, IPAC_PROTO_EXT_GSUP, on_recv_pdu, this);
/* I would more like to have either an internal event which unblocks the queue. */
/* src/libipsec/ipsec_event_relay.c */
lib->processor->queue_job(lib->processor,
(job_t*)callback_job_create_with_prio((callback_job_cb_t)queue_worker,
this, NULL, (callback_job_cancel_t)return_false, JOB_PRIO_CRITICAL));
return &this->public;
}

View File

@ -0,0 +1,77 @@
/*
* Copyright (C) 2023 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Alexander Couzens <acouzens@sysmocom.de>
*
* SPDX-License-Identifier: GPL-2.0+
*
* 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.
*
* 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.
*
*/
// TODO: check license
/**
* @defgroup osmo_epdg_gsup_client osmo_epdg_gsup_client
* @{ @ingroup eap_simaka_sql
*/
#ifndef OSMO_EPDG_GSUP_CLIENT_H_
#define OSMO_EPDG_GSUP_CLIENT_H_
#include <utils/chunk.h>
#include <osmocom/gsm/gsup.h>
struct osmo_epdg_gsup_response_t {
struct osmo_gsup_message gsup;
};
typedef struct osmo_epdg_gsup_response_t osmo_epdg_gsup_response_t;
typedef struct osmo_epdg_gsup_client_t osmo_epdg_gsup_client_t;
/**
* foo
*/
struct osmo_epdg_gsup_client_t {
/**
* Send Authentication Info Request
*
* @param imsi IMSI encoded as human-readable string (IMSI MAX = 15)
* @param auts (optional)
* @param auts_rand (optional)
* @return NULL or the osmo_epdg_gsup_response_t
*/
osmo_epdg_gsup_response_t *(*send_auth_request)(osmo_epdg_gsup_client_t *this,
char *imsi, uint8_t cn_domain, chunk_t *auts, chunk_t *auts_rand);
/**
* Update Location Request
*
* @return NULL or the osmo_gsup_message
*/
osmo_epdg_gsup_response_t *(*update_location)(osmo_epdg_gsup_client_t *this,
char *imsi,
uint8_t cn_domain);
/**
* Destroy a osmo_epdg_gsup_client_t.
*/
void (*destroy)(osmo_epdg_gsup_client_t *this);
};
/**
* Create a osmo_epdg_gsup_client instance.
*
* @param address the address of the gsup server */
osmo_epdg_gsup_client_t *osmo_epdg_gsup_client_create(char *addr);
#endif /** OSMO_EPDG_GSUP_CLIENT_H_ @}*/

View File

@ -0,0 +1,398 @@
/*
* Copyright (C) 2023 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Alexander Couzens <acouzens@sysmocom.de>
*
* SPDX-License-Identifier: GPL-2.0+
*
* 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.
*
* 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.
*
*/
// TODO: check license
#include <collections/enumerator.h>
#include <collections/linked_list.h>
#include <collections/blocking_queue.h>
#include <threading/mutex.h>
#include <threading/thread.h>
#include <threading/condvar.h>
#include <osmocom/core/msgb.h>
#include <osmocom/gsm/protocol/ipaccess.h>
#include <osmocom/gsm/ipa.h>
#include <library.h>
#include <errno.h>
#include "ipa_client.h"
#include "osmo_epdg_utils.h"
typedef struct private_osmo_epdg_ipa_client_t private_osmo_epdg_ipa_client_t;
struct private_osmo_epdg_ipa_client_t {
/**
* Public osmo_epdg_ipa_client_t
*/
osmo_epdg_ipa_client_t public;
char *uri;
stream_t *stream;
ipa_cb_t osmo_cb;
void *osmo_cb_data;
};
METHOD(osmo_epdg_ipa_client_t, on_error, int,
private_osmo_epdg_ipa_client_t *this, uint8_t osmo_proto, ipa_cb_t cb, void *data)
{
/* TODO: on error ? */
return TRUE;
}
METHOD(osmo_epdg_ipa_client_t, on_recv, int,
private_osmo_epdg_ipa_client_t *this, uint8_t osmo_proto, ipa_cb_t cb, void *data)
{
/* TODO: protect it by mutex?! */
this->osmo_cb = NULL;
this->osmo_cb_data = data;
this->osmo_cb = cb;
return TRUE;
}
METHOD(osmo_epdg_ipa_client_t, destroy, void,
private_osmo_epdg_ipa_client_t *this)
{
free(this->uri);
free(this);
}
METHOD(osmo_epdg_ipa_client_t, disconnect, int,
private_osmo_epdg_ipa_client_t *this)
{
this->stream->destroy(this->stream);
this->stream = NULL;
return 0;
}
METHOD(osmo_epdg_ipa_client_t, send_pdu, ssize_t,
private_osmo_epdg_ipa_client_t *this, uint8_t osmo_proto, struct msgb *msg)
{
struct ipaccess_head *head;
int len;
head = (struct ipaccess_head *) msgb_push(msg, sizeof(struct ipaccess_head) + 1);
head->proto = IPAC_PROTO_OSMO;
head->len = htons(msgb_length(msg) - (sizeof(struct ipaccess_head)));
head->data[0] = osmo_proto;
len = msgb_length(msg);
if (!this->stream->write_all(this->stream, msgb_data(msg), msgb_length(msg)))
{
// TODO: write error
free(msg);
return -EINVAL;
}
free(msg);
return len;
}
static bool read_error(private_osmo_epdg_ipa_client_t *this, int read_err)
{
DBG1(DBG_NET, "IPA client failed to read with %d. Disconnecting", read_err);
this->public.disconnect(&this->public);
return FALSE;
}
/* send a simple IPA PDU with the base protocol */
static void ipa_pdu_base_send_simple(private_osmo_epdg_ipa_client_t *this, uint8_t msg_type)
{
struct ipaccess_head *head = calloc(1, sizeof(*head) + 1);
head->proto = IPAC_PROTO_IPACCESS;
head->len = htons(1);
head->data[0] = msg_type;
if (!this->stream->write_all(this->stream, head, sizeof(*head) + 1))
{
/* TODO: write error */
}
free(head);
return;
}
static inline void ipa_tag_put_str(struct msgb *resp, uint8_t tag, const char *value)
{
char *buf;
ssize_t len = strlen(value);
msgb_put_u16(resp, len + 1);
msgb_put_u8(resp, tag);
buf = msgb_put(resp, len);
memcpy(buf, value, len);
}
static void ipa_resp_tag_encode(private_osmo_epdg_ipa_client_t *this, struct msgb *resp, uint8_t tag)
{
switch (tag)
{
case IPAC_IDTAG_SERNR:
case IPAC_IDTAG_UNITNAME:
ipa_tag_put_str(resp, tag, "SWAN-00-00-00-00-00-00");
break;
case IPAC_IDTAG_LOCATION1:
case IPAC_IDTAG_LOCATION2:
case IPAC_IDTAG_EQUIPVERS:
case IPAC_IDTAG_SWVERSION:
ipa_tag_put_str(resp, tag, "00:00:00:00:00:00");
break;
case IPAC_IDTAG_UNIT:
ipa_tag_put_str(resp, tag, "0/0/0");
break;
}
}
static void protocol_error(private_osmo_epdg_ipa_client_t *this, char *error_msg)
{
/* TODO: protocol error */
DBG1(DBG_NET, error_msg);
return;
}
/* send a IPA PDU (base protocol) ID Response message */
static void ipa_pdu_base_send_id_resp(private_osmo_epdg_ipa_client_t *this, struct msgb *req)
{
struct msgb *resp;
struct ipaccess_head *resp_head;
chunk_t resp_pdu = chunk_alloc(IPA_ALLOC_SIZE);
if (!resp_pdu.ptr)
{
/* TODO: alloc err */
return;
}
resp = chunk_to_msgb(&resp_pdu);
/* remove the ipaccess header so we can use msg_pull on the message */
msgb_pull(req, sizeof(struct ipaccess_head));
if (msgb_length(req) < 1)
{
protocol_error(this, "Invalid IPA ID Request message.");
goto out;
}
/* prepare our response message */
msgb_reserve(resp, 128);
resp_head = (struct ipaccess_head *) msgb_put(resp, sizeof(struct ipaccess_head));
resp->l1h = (void *)resp_head;
resp->l2h = resp->tail;
resp_head->proto = IPAC_PROTO_IPACCESS;
msgb_put_u8(resp, IPAC_MSGT_ID_RESP);
/* remove IPA message type */
msgb_pull_u8(req);
/* ID Request contains: a list of [0x1, tag] */
while (msgb_length(req) >= 2)
{
uint8_t len = msgb_pull_u8(req);
if (len != 1)
{
if (msgb_length(req) < len)
{
protocol_error(this, "Invalid IPA ID Request message");
goto out;
}
/* ignoring this requested LValue */
DBG1(DBG_NET, "IPA ignoring IPA ID Request tag with size != 1");
msgb_pull(req, len);
continue;
}
uint8_t tag = msgb_pull_u8(req);
ipa_resp_tag_encode(this, resp, tag);
}
resp_head->len = htons(msgb_l2len(resp));
if (!this->stream->write_all(this->stream, msgb_l1(resp), msgb_l1len(resp)))
{
// TODO: write error
return;
}
ipa_pdu_base_send_simple(this, IPAC_MSGT_ID_ACK);
out:
chunk_free(&resp_pdu);
return;
}
static void on_recv_ipa_pdu(private_osmo_epdg_ipa_client_t *this, struct msgb *pdu)
{
if (msgb_length(pdu) < sizeof(struct ipaccess_head) + 1)
{
/* TODO: invalid package */
return;
}
struct ipaccess_head *head = (struct ipaccess_head *) msgb_data(pdu);
uint8_t msg_type = head->data[0];
switch (msg_type)
{
case IPAC_MSGT_PING:
ipa_pdu_base_send_simple(this, IPAC_MSGT_PONG);
break;
case IPAC_MSGT_PONG:
/* ignore. We don't implement an own PING/PONG timer */
break;
case IPAC_MSGT_ID_GET:
ipa_pdu_base_send_id_resp(this, pdu);
break;
case IPAC_MSGT_ID_ACK:
/* ignore. An ACK means everything the ID got accepted */
break;
default:
DBG1(DBG_NET, "IPA client Received an unknown IPA PDU %02x", msg_type);
break;
}
}
CALLBACK(on_stream_read, bool,
private_osmo_epdg_ipa_client_t *this, stream_t *stream)
{
uint16_t len;
ssize_t hlen;
chunk_t req_chunk;
struct ipaccess_head head;
struct msgb *req;
DBG2(DBG_NET, "on stream read!");
hlen = stream->read(stream, &head, sizeof(head), FALSE);
if (hlen <= 0)
{
if (errno == EWOULDBLOCK)
{
DBG2(DBG_NET, "on stream read EWOULDBLOCK!");
return TRUE;
}
DBG2(DBG_NET, "on stream errno not EWOULDBLOCK %d!", hlen);
return read_error(this, errno);
}
DBG2(DBG_NET, "on stream hlen %d!", hlen);
if (hlen < sizeof(head))
{
if (!stream->read_all(stream, ((void*)&head) + hlen, sizeof(head) - hlen))
{
return read_error(this, errno);
}
}
len = ntohs(head.len);
if ((len + sizeof(head)) > IPA_ALLOC_SIZE)
{
/* TODO: pkg too big */
return read_error(this, EINVAL);
}
req_chunk = chunk_alloc(IPA_ALLOC_SIZE + sizeof(struct msgb));
if (!req_chunk.ptr)
{
/* TODO: -ENOMEM; */
return FALSE;
}
req = chunk_to_msgb(&req_chunk);
memcpy(msgb_put(req, sizeof(head)), &head, sizeof(head));
/* TODO: either wait here with a timeout or don't care on this stream read */
if (!stream->read_all(stream, msgb_put(req, len), len))
{
chunk_free(&req_chunk);
return read_error(this, errno);
}
switch (head.proto)
{
case IPAC_PROTO_IPACCESS:
on_recv_ipa_pdu(this, req);
break;
case IPAC_PROTO_OSMO:
/* will take care of the response */
if (msgb_length(req) < sizeof(struct ipaccess_head) + 1)
{
/* TODO: inval pdu */
chunk_free(&req_chunk);
break;
}
req->l1h = req->head;
req->l2h = req->l1h + sizeof(struct ipaccess_head) + 1;
DBG2(DBG_NET, "IPA client: pushing osmo pdu");
if (this->osmo_cb)
{
this->osmo_cb(this->osmo_cb_data, &this->public, req);
}
else
{
chunk_free(&req_chunk);
}
break;
default:
DBG1(DBG_NET, "IPA client: ignoring unknown proto %02x", head.proto);
chunk_free(&req_chunk);
break;
}
return TRUE;
}
static int connect_ipa(private_osmo_epdg_ipa_client_t *this)
{
DBG1(DBG_NET, "IPA client connecting to %s", this->uri);
if (this->stream != NULL)
{
DBG1(DBG_NET, "closing old ipa conncetion %s", this->uri);
this->public.disconnect(&this->public);
}
this->stream = lib->streams->connect(lib->streams, this->uri);
if (!this->stream)
{
/* TODO: failed to connect */
/* TODO: re-schedule the connect */
DBG1(DBG_NET, "failed to connect the ipa %s", this->uri);
return -EINVAL;
}
DBG1(DBG_NET, "IPA client connected");
/* TODO: check if we need this
* ensure we get the first bytes after the connect
* on_stream_read(this, this->stream); */
/* TODO: can a race happen here when data arrives between those 2 calls? */
this->stream->on_read(this->stream, on_stream_read, this);
on_stream_read(this, this->stream);
return 0;
}
osmo_epdg_ipa_client_t *osmo_epdg_ipa_client_create(char *uri)
{
private_osmo_epdg_ipa_client_t *this;
INIT(this,
.public = {
.on_recv = _on_recv,
.on_error = _on_error,
.send = _send_pdu,
.disconnect = _disconnect,
.destroy = _destroy,
},
.uri = strdup(uri),
.stream = NULL,
);
connect_ipa(this);
return &this->public;
}

View File

@ -0,0 +1,76 @@
/*
* Copyright (C) 2023 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Alexander Couzens <acouzens@sysmocom.de>
*
* SPDX-License-Identifier: GPL-2.0+
*
* 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.
*
* 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.
*
*/
// TODO: check license
/**
* @defgroup osmo_epdg_ipa_client osmo_epdg_ipa_client
* @{ @ingroup eap_simaka_sql
*/
#ifndef OSMO_EPDG_IPA_CLIENT_H_
#define OSMO_EPDG_IPA_CLIENT_H_
#include <utils/chunk.h>
struct msgb;
typedef struct osmo_epdg_ipa_client_t osmo_epdg_ipa_client_t;
typedef bool (*ipa_cb_t)(void *data, osmo_epdg_ipa_client_t *client, struct msgb *pdu);
/**
* IP Access Protocol
*/
struct osmo_epdg_ipa_client_t {
/**
* Register a callback to invoke when a IPA PDU of proto arrived.
*
* Only called when a full PDU has arrived.
*
* @param cb callback function, NULL to unregister
* @param data data to pass to callback
*/
int (*on_recv)(osmo_epdg_ipa_client_t *this, uint8_t osmo_proto, ipa_cb_t cb, void *data);
/**
* Send a PDU over the IPA connection
*
* @param proto define the protocol
* @param buf data buffer to write
* @param len number of bytes to write
* @return number of bytes written, -1 on error
*/
ssize_t (*send)(osmo_epdg_ipa_client_t *this, uint8_t osmo_proto, struct msgb *msg);
int (*on_error)(osmo_epdg_ipa_client_t *this, uint8_t osmo_proto, ipa_cb_t cb, void *data);
// TODO: unsure if we need this int (*connect)(osmo_epdg_ipa_client_t *this);
int (*disconnect)(osmo_epdg_ipa_client_t *this);
/**
* Destroy a osmo_epdg_ipa_client_t.
*/
void (*destroy)(osmo_epdg_ipa_client_t *this);
};
/**
* Create a osmo_epdg_ipa_client instance.
*
* @param address the address of the gsup server */
osmo_epdg_ipa_client_t *osmo_epdg_ipa_client_create(char *addr);
#endif /** OSMO_EPDG_IPA_CLIENT_H_ @}*/

View File

@ -0,0 +1,124 @@
/*
* Copyright (C) 2023 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Alexander Couzens <acouzens@sysmocom.de>
*
* SPDX-License-Identifier: GPL-2.0+
*
* 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.
*
* 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.
*
*/
// TODO: check license
#include <daemon.h>
#include <plugins/plugin.h>
#include <unistd.h>
#include "osmo_epdg_plugin.h"
#include "osmo_epdg_listener.h"
#include "osmo_epdg_utils.h"
typedef struct private_osmo_epdg_listener_t private_osmo_epdg_listener_t;
/**
* Private data of an osmo_epdg_listener_t object.
*/
struct private_osmo_epdg_listener_t {
/**
* Public osmo_epdg_listener_t interface.
*/
osmo_epdg_listener_t public;
osmo_epdg_gsup_client_t *gsup;
};
METHOD(listener_t, eap_authorize, bool,
private_osmo_epdg_listener_t *this, ike_sa_t *ike_sa,
identification_t *id, bool final, bool *success)
{
char imsi[16] = {0};
if (!id)
{
DBG1(DBG_NET, "epdg: authorize: no id given. Failing.");
goto err;
}
if (get_imsi(id, imsi, sizeof(imsi) - 1))
{
DBG1(DBG_NET, "epdg: authorize: Can't find IMSI in EAP identity.");
goto err;
}
osmo_epdg_gsup_response_t *resp = this->gsup->update_location(this->gsup, imsi, OSMO_GSUP_CN_DOMAIN_PS);
if (!resp)
{
DBG1(DBG_NET, "epdg: GSUP: couldn't send Update Location.");
goto err;
}
if (resp->gsup.message_type != OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT)
{
DBG1(DBG_NET, "epdg_listener: Update Location Error! Cause: %02x", resp->gsup.cause);
goto err;
}
return TRUE;
err:
*success = FALSE;
/* keep still subscribed */
return TRUE;
}
METHOD(listener_t, authorize, bool,
private_osmo_epdg_listener_t *this, ike_sa_t *ike_sa,
bool final, bool *success)
{
DBG1(DBG_NET, "Authorized: uniq 0x%08x, name %s final: %d, eap: %d!",
ike_sa->get_unique_id(ike_sa),
ike_sa->get_name(ike_sa),
final,
ike_sa->has_condition(ike_sa, COND_EAP_AUTHENTICATED));
sleep(1);
if (final)
{
/* TODO: create new Tunnel and save Tunnel information locally */
}
return TRUE;
}
METHOD(osmo_epdg_listener_t, destroy, void,
private_osmo_epdg_listener_t *this)
{
free(this);
}
/**
* See header
*/
osmo_epdg_listener_t *osmo_epdg_listener_create(osmo_epdg_gsup_client_t *gsup)
{
private_osmo_epdg_listener_t *this;
INIT(this,
.public = {
.listener = {
.authorize = _authorize,
.eap_authorize = _eap_authorize,
},
.destroy = _destroy,
},
.gsup = gsup,
);
return &this->public;
}

View File

@ -0,0 +1,55 @@
/*
* Copyright (C) 2023 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Alexander Couzens <acouzens@sysmocom.de>
*
* SPDX-License-Identifier: GPL-2.0+
*
* 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.
*
* 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.
*
*/
// TODO: check license
/**
* @defgroup osmo_epdg_listener osmo_epdg_listener
* @{ @ingroup osmo_epdg
*/
#ifndef OSMO_EPDG_LISTENER_H_
#define OSMO_EPDG_LISTENER_H_
#include <bus/listeners/listener.h>
#include "gsup_client.h"
typedef struct osmo_epdg_listener_t osmo_epdg_listener_t;
/**
* SIM listener implementation using a set of AKA functions.
*/
struct osmo_epdg_listener_t {
/**
* Implements listener_t interface.
*/
listener_t listener;
/**
* Destroy a osmo_epdg_listener_t.
*/
void (*destroy)(osmo_epdg_listener_t *this);
};
/**
* Create a osmo_epdg_listener instance.
*/
osmo_epdg_listener_t *osmo_epdg_listener_create(osmo_epdg_gsup_client_t *gsup);
#endif /** OSMO_EPDG_LISTENER_H_ @}*/

View File

@ -0,0 +1,120 @@
/*
* Copyright (C) 2023 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Alexander Couzens <acouzens@sysmocom.de>
*
* SPDX-License-Identifier: GPL-2.0+
*
* 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.
*
* 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.
*
*/
// TODO: check license
#include <daemon.h>
#include <plugins/plugin.h>
#include "osmo_epdg_plugin.h"
#include "osmo_epdg_provider.h"
#include "osmo_epdg_listener.h"
typedef struct private_osmo_epdg_t private_osmo_epdg_t;
/**
* Private data of an eap_osmo_epdg_t object.
*/
struct private_osmo_epdg_t {
/**
* Public osmo_epdg_plugin_t interface.
*/
osmo_epdg_plugin_t public;
/**
* SIM AKA provider
*/
osmo_epdg_provider_t *provider;
osmo_epdg_listener_t *listener;
};
METHOD(plugin_t, get_name, char*,
private_osmo_epdg_t *this)
{
return "osmo-epdg";
}
static bool register_functions(private_osmo_epdg_t *this,
plugin_feature_t *feature, bool reg, void *data)
{
if (reg)
{
osmo_epdg_gsup_client_t *gsup = osmo_epdg_gsup_client_create("tcp://127.0.0.1:4222");
this->provider = osmo_epdg_provider_create(gsup);
this->listener = osmo_epdg_listener_create(gsup);
charon->bus->add_listener(charon->bus, &this->listener->listener);
return TRUE;
}
if (this->listener)
{
charon->bus->remove_listener(charon->bus, &this->listener->listener);
}
this->provider->destroy(this->provider);
this->provider = NULL;
return TRUE;
}
/**
* Callback providing our provider to register
*/
static simaka_provider_t* get_provider(private_osmo_epdg_t *this)
{
return &this->provider->provider;
}
METHOD(plugin_t, get_features, int,
private_osmo_epdg_t *this, plugin_feature_t *features[])
{
static plugin_feature_t f[] = {
PLUGIN_CALLBACK((void*)register_functions, NULL),
PLUGIN_PROVIDE(CUSTOM, "osmo-epdg"),
PLUGIN_CALLBACK(simaka_manager_register, get_provider),
PLUGIN_PROVIDE(CUSTOM, "aka-provider"),
PLUGIN_DEPENDS(CUSTOM, "aka-manager"),
};
*features = f;
return countof(f);
}
METHOD(plugin_t, destroy, void,
private_osmo_epdg_t *this)
{
free(this);
}
/**
* See header
*/
plugin_t *osmo_epdg_plugin_create()
{
private_osmo_epdg_t *this;
INIT(this,
.public = {
.plugin = {
.get_name = _get_name,
.get_features = _get_features,
.destroy = _destroy,
},
},
);
return &this->public.plugin;
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (C) 2023 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Alexander Couzens <acouzens@sysmocom.de>
*
* SPDX-License-Identifier: GPL-2.0+
*
* 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.
*
* 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.
*
*/
// TODO: check license
/**
* @defgroup eap_osmo_epdg eap_aka_3gpp2
* @ingroup cplugins
*
* @defgroup eap_osmo_epdg_plugin eap_aka_3gpp2_plugin
* @{ @ingroup eap_osmo_epdg
*/
#ifndef OSMO_EPDG_PLUGIN_H_
#define OSMO_EPDG_PLUGIN_H_
#include <plugins/plugin.h>
typedef struct osmo_epdg_plugin_t osmo_epdg_plugin_t;
struct osmo_epdg_plugin_t {
/**
* implements plugin interface
*/
plugin_t plugin;
};
#endif /** OSMO_EPDG_PLUGIN_H_ @}*/

View File

@ -0,0 +1,153 @@
/*
* Copyright (C) 2023 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Alexander Couzens <acouzens@sysmocom.de>
*
* SPDX-License-Identifier: GPL-2.0+
*
* 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.
*
* 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 "osmo_epdg_provider.h"
#include "osmo_epdg_utils.h"
#include "gsup_client.h"
#include <daemon.h>
#include <credentials/keys/shared_key.h>
#define AKA_SQN_LEN 6
#define AKA_K_LEN 16
#define AKA_OPC_LEN 16
#define AKA_MAC_LEN 8
#define AKA_AK_LEN 6
#define AKA_AMF_LEN 2
#define AKA_RES_LEN 8
typedef struct private_osmo_epdg_provider_t private_osmo_epdg_provider_t;
/**
* Private data of an osmo_epdg_provider_t object.
*/
struct private_osmo_epdg_provider_t {
/**
* Public osmo_epdg_provider_t interface.
*/
osmo_epdg_provider_t public;
osmo_epdg_gsup_client_t *gsup;
};
/**
* Get a shared key K from the credential database
*/
bool osmo_epdg_get_k(identification_t *id, char k[AKA_K_LEN])
{
shared_key_t *shared;
chunk_t key;
shared = lib->credmgr->get_shared(lib->credmgr, SHARED_EAP, id, NULL);
if (shared == NULL)
{
return FALSE;
}
key = shared->get_key(shared);
memset(k, '\0', AKA_K_LEN);
memcpy(k, key.ptr, min(key.len, AKA_K_LEN));
shared->destroy(shared);
return TRUE;
}
METHOD(simaka_provider_t, get_quintuplet, bool,
private_osmo_epdg_provider_t *this, identification_t *id,
char rand[AKA_RAND_LEN], char xres[AKA_RES_MAX], int *xres_len,
char ck[AKA_CK_LEN], char ik[AKA_IK_LEN], char autn[AKA_AUTN_LEN])
{
char imsi[17] = {0};
if (get_imsi(id, imsi, sizeof(imsi) - 1))
{
DBG1(DBG_NET, "epdg: get_quintuplet: Can't find IMSI in EAP identity.");
return FALSE;
}
/* TODO: check before if this is a null terminated string */
osmo_epdg_gsup_response_t *resp = this->gsup->send_auth_request(
this->gsup, imsi, OSMO_GSUP_CN_DOMAIN_PS, NULL, NULL);
if (!resp)
{
return FALSE;
}
if (resp->gsup.message_type != OSMO_GSUP_MSGT_SEND_AUTH_INFO_RESULT)
{
DBG1(DBG_NET, "epdg_provider: SendAuthInfo Error! Cause: %02x", resp->gsup.cause);
return FALSE;
}
struct osmo_auth_vector *auth = &resp->gsup.auth_vectors[0];
if (resp->gsup.num_auth_vectors == 0)
{
/* TODO: invalid auth data received */
return FALSE;
}
memcpy(rand, auth->rand, AKA_RAND_LEN);
memcpy(ck, auth->ck, AKA_CK_LEN);
memcpy(ik, auth->ik, AKA_IK_LEN);
memcpy(autn, auth->autn, AKA_AUTN_LEN);
memcpy(xres, auth->res, auth->res_len);
*xres_len = auth->res_len;
free(resp);
return TRUE;
}
METHOD(simaka_provider_t, resync, bool,
private_osmo_epdg_provider_t *this, identification_t *id,
char rand[AKA_RAND_LEN], char auts[AKA_AUTS_LEN])
{
/* TODO: invalid auth data received */
/* prepare and fill up the struct */
/* send pdu blocking */
return FALSE;
}
METHOD(osmo_epdg_provider_t, destroy, void,
private_osmo_epdg_provider_t *this)
{
free(this);
}
/**
* See header
*/
osmo_epdg_provider_t *osmo_epdg_provider_create(osmo_epdg_gsup_client_t *gsup)
{
private_osmo_epdg_provider_t *this;
INIT(this,
.public = {
.provider = {
.get_triplet = (void*)return_false,
.get_quintuplet = _get_quintuplet,
.resync = _resync,
.is_pseudonym = (void*)return_null,
.gen_pseudonym = (void*)return_null,
.is_reauth = (void*)return_null,
.gen_reauth = (void*)return_null,
},
.destroy = _destroy,
},
.gsup = gsup,
);
return &this->public;
}

View File

@ -0,0 +1,55 @@
/*
* Copyright (C) 2023 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Alexander Couzens <acouzens@sysmocom.de>
*
* SPDX-License-Identifier: GPL-2.0+
*
* 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.
*
* 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.
*
*/
// TODO: check license
/**
* @defgroup osmo_epdg_listener osmo_epdg_listener
* @{ @ingroup osmo_epdg
*/
#ifndef OSMO_EPDG_PROVIDER_H_
#define OSMO_EPDG_PROVIDER_H_
#include <simaka_provider.h>
#include "gsup_client.h"
typedef struct osmo_epdg_provider_t osmo_epdg_provider_t;
/**
* SIM provider implementation using a set of AKA functions.
*/
struct osmo_epdg_provider_t {
/**
* Implements simaka_provider_t interface.
*/
simaka_provider_t provider;
/**
* Destroy a osmo_epdg_provider_t.
*/
void (*destroy)(osmo_epdg_provider_t *this);
};
/**
* Create a osmo_epdg_provider instance.
*/
osmo_epdg_provider_t *osmo_epdg_provider_create(osmo_epdg_gsup_client_t *gsup);
#endif /** OSMO_EPDG_PROVIDER_H_ @}*/

View File

@ -0,0 +1,65 @@
/*
* Copyright (C) 2023 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Alexander Couzens <acouzens@sysmocom.de>
*
* SPDX-License-Identifier: GPL-2.0+
*
* 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.
*
* 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 <errno.h>
#include <osmocom/core/msgb.h>
#include <utils/utils.h>
#include <utils/debug.h>
#include "osmo_epdg_utils.h"
struct msgb *chunk_to_msgb(chunk_t *chunk)
{
struct msgb *msg;
if (chunk->len < sizeof(*msg))
{
return NULL;
}
msg = (struct msgb *) chunk->ptr;
memset(msg, 0x00, sizeof(*msg));
msg->data_len = chunk->len - sizeof(*msg);
msg->len = 0;
msg->data = msg->_data;
msg->head = msg->_data;
msg->tail = msg->_data;
return msg;
}
int get_imsi(identification_t *id, char *imsi, size_t imsi_len)
{
chunk_t nai = id->get_encoding(id);
/* TODO: maybe use regex? */
/* 099942123456789@mnc042.mcc999.3gpp... */
if (nai.len < 17)
{
DBG1(DBG_NET, "epdg: Invalid NAI %s.", nai);
return -EINVAL;
}
if (nai.ptr[0] != '0')
{
DBG1(DBG_NET, "epdg: Invalid NAI. Only support IMSI (starting with 0). %s.",
nai);
return -EINVAL;
}
strncpy(imsi, nai.ptr + 1, min(15, imsi_len));
return 0;
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (C) 2023 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Alexander Couzens <acouzens@sysmocom.de>
*
* SPDX-License-Identifier: GPL-2.0+
*
* 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.
*
* 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 <osmocom/core/msgb.h>
#include <utils/chunk.h>
#include <utils/identification.h>
#define IPA_ALLOC_SIZE 1200
struct msgb *chunk_to_msgb(chunk_t *chunk);
int get_imsi(identification_t *id, char *imsi, size_t imsi_len);

View File

@ -240,6 +240,32 @@ static void replace_eap_identity(private_eap_authenticator_t *this)
cfg->add(cfg, AUTH_RULE_EAP_IDENTITY, eap_identity);
}
static identification_t *get_identification(private_eap_authenticator_t *this)
{
identification_t *id;
auth_cfg_t *auth = NULL;
if (this->eap_identity)
{
return this->eap_identity;
}
if (this->method->get_auth)
{
auth = this->method->get_auth(this->method);
if (auth)
{
id = auth->get(auth, AUTH_RULE_EAP_IDENTITY);
if (id)
{
return id;
}
}
}
return NULL;
}
/**
* Handle EAP exchange as server
*/
@ -320,6 +346,14 @@ static eap_payload_t* server_process_eap(private_eap_authenticator_t *this,
DBG1(DBG_IKE, "EAP method %N succeeded, %sMSK established",
eap_type_names, type, this->msk.ptr ? "" : "no ");
}
if (!charon->bus->eap_authorize(charon->bus, get_identification(this), TRUE))
{
DBG1(DBG_IKE, "eap_authorization hook forbids EAP_SUCCESS, canceling");
this->eap_complete = FALSE;
return eap_payload_create_code(EAP_FAILURE, in->get_identifier(in));
}
this->ike_sa->set_condition(this->ike_sa, COND_EAP_AUTHENTICATED,
TRUE);
this->eap_complete = TRUE;