WIP: Volte support for outgoing SIP registration

This commit is contained in:
Andreas Eversberg 2024-04-05 17:12:27 +02:00
parent 82c6c3a9f8
commit e793f82e2e
16 changed files with 2272 additions and 22 deletions

View File

@ -58,6 +58,7 @@ SPEEX=@PBX_SPEEX@
SPEEXDSP=@PBX_SPEEXDSP@
SPEEX_PREPROCESS=@PBX_SPEEX_PREPROCESS@
VEVS=@PBX_VEVS@
LIBMNL=@PBX_LIBMNL@
SQLITE3=@PBX_SQLITE3@
SRTP=@PBX_SRTP@
SS7=@PBX_SS7@

View File

@ -582,6 +582,7 @@ AST_EXT_LIB_SETUP([BEANSTALK], [Beanstalk Job Queue], [beanstalk])
#FIXME
AST_EXT_LIB_SETUP([VEVS], [Vocal EVS Audio Decoder/Encoder], [bluetooth])
#AST_EXT_LIB_SETUP([VEVS], [Vocal EVS Audio Decoder/Encoder], [vocal-evs])
AST_EXT_LIB_SETUP([LIBMNL], [MNL library], [mnl])
if test "x${PBX_PJPROJECT}" != "x1" ; then
AST_EXT_LIB_SETUP([PJPROJECT], [PJPROJECT], [pjproject])
@ -2414,6 +2415,7 @@ AST_EXT_LIB_CHECK([BEANSTALK], [beanstalk], [bs_version], [beanstalk.h])
#FIXME
AST_EXT_LIB_CHECK([VEVS], [bluetooth], [ba2str], [bluetooth/bluetooth.h])
AST_EXT_LIB_CHECK([LIBMNL], [mnl], [mnl_socket_open], [libmnl/libmnl.h])
#AST_EXT_LIB_CHECK([VEVS], [vocal-evs], [vevs_enc], [vocal-evs/evs.h])
PG_CONFIG=":"

View File

@ -297,6 +297,8 @@ struct ast_sip_transport {
int allow_reload;
/*! Automatically send requests out the same transport requests have come in on */
int symmetric_transport;
/*! Local ports for client and server to use with IMS */
int ims_port_c, ims_port_s;
/*! This is a flow to another target */
int flow;
};
@ -4209,4 +4211,6 @@ const int ast_sip_hangup_sip2cause(int cause);
*/
int ast_sip_str2rc(const char *name);
extern unsigned char *volte_auth;
#endif /* _RES_PJSIP_H */

View File

@ -354,4 +354,7 @@ BEANSTALK_LIB=@BEANSTALK_LIB@
VEVS_INCLUDE=@VEVS_INCLUDE@
VEVS_LIB=@VEVS_LIB@
LIBMNL_INCLUDE=@LIBMNL_INCLUDE@
LIBMNL_LIB=@LIBMNL_LIB@
HAVE_SBIN_LAUNCHD=@PBX_LAUNCHD@

View File

@ -61,6 +61,7 @@ $(call MOD_ADD_C,res_snmp,snmp/agent.c)
$(call MOD_ADD_C,res_parking,$(wildcard parking/*.c))
$(call MOD_ADD_C,res_pjsip,$(wildcard res_pjsip/*.c))
$(call MOD_ADD_C,res_pjsip_session,$(wildcard res_pjsip_session/*.c))
$(call MOD_ADD_C,res_pjsip_outbound_registration,$(wildcard res_pjsip_outbound_registration/*.c))
$(call MOD_ADD_C,res_prometheus,$(wildcard prometheus/*.c))
$(call MOD_ADD_C,res_ari,ari/cli.c ari/config.c ari/ari_websockets.c)
$(call MOD_ADD_C,res_ari_model,ari/ari_model_validators.c)

View File

@ -4095,3 +4095,5 @@ AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_
.requires = "dnsmgr,res_pjproject,res_sorcery_config,res_sorcery_memory,res_sorcery_astdb",
.optional_modules = "res_geolocation,res_statsd",
);
unsigned char *volte_auth = NULL;

View File

@ -6,6 +6,7 @@
LINKER_SYMBOL_PREFIXast_copy_pj_str;
LINKER_SYMBOL_PREFIXast_copy_pj_str2;
LINKER_SYMBOL_PREFIXast_pjsip_rdata_get_endpoint;
LINKER_SYMBOL_PREFIXvolte_auth;
local:
*;
};

View File

@ -1769,6 +1769,8 @@ int ast_sip_initialize_sorcery_transport(void)
ast_sorcery_object_field_register(sorcery, "transport", "websocket_write_timeout", AST_DEFAULT_WEBSOCKET_WRITE_TIMEOUT_STR, OPT_INT_T, PARSE_IN_RANGE, FLDSET(struct ast_sip_transport, write_timeout), 1, INT_MAX);
ast_sorcery_object_field_register(sorcery, "transport", "allow_reload", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_transport, allow_reload));
ast_sorcery_object_field_register(sorcery, "transport", "symmetric_transport", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_transport, symmetric_transport));
ast_sorcery_object_field_register(sorcery, "transport", "ims_port_c", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_transport, ims_port_c));
ast_sorcery_object_field_register(sorcery, "transport", "ims_port_s", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_transport, ims_port_s));
ast_sip_register_endpoint_formatter(&endpoint_transport_formatter);

View File

@ -33,7 +33,9 @@
#include "asterisk/vector.h"
pj_str_t supported_digest_algorithms[] = {
{ "MD5", 3}
{ "MD5", 3},
{ "AKAv1-MD5", 9},
{ "AKAv2-MD5", 9}
};
/*!

View File

@ -17,6 +17,7 @@
*/
/*** MODULEINFO
<depend>libmnl</depend>
<depend>pjproject</depend>
<depend>res_pjsip</depend>
<use type="module">res_statsd</use>
@ -40,6 +41,7 @@
#include "res_pjsip/include/res_pjsip_private.h"
#include "asterisk/vector.h"
#include "asterisk/pbx.h"
#include "res_pjsip_outbound_registration/volte.h"
/*** DOCUMENTATION
<configInfo name="res_pjsip_outbound_registration" language="en_US">
@ -214,6 +216,8 @@
</configOption>
<configOption name="manual_register">
<synopsis>Perform registration only upon request over AMI interface.</synopsis>
<configOption name="ims_aka">
<synopsis>Perform Voice over LTE SIP registration process.</synopsis>
</configOption>
</configObject>
</configFile>
@ -271,6 +275,8 @@
/* forward declarations */
static int set_outbound_initial_authentication_credentials(pjsip_regc *regc,
const struct ast_sip_auth_vector *auth_vector);
static int volte_add_outbound_initial_authorization(pjsip_tx_data *tdata, const char *fromdomain,
const struct ast_sip_auth_vector *auth_vector);
/*! \brief Some thread local storage used to determine if the running thread invoked the callback */
AST_THREADSTORAGE(register_callback_invoked);
@ -378,6 +384,28 @@ struct sip_outbound_registration {
unsigned int support_outbound;
/*! \brief Do not trigger registration automatically. */
unsigned int manual_register;
/*! \brief VoLTE support */
unsigned int ims_aka;
};
/*! \brief States of the VoLTE registration process */
enum volte_state {
/* !\brief Send first registration. */
VOLTE_STATE_REGISTER,
/* !\brief Wait for SIM keys to be provided. */
VOLTE_STATE_SIM_REQUEST,
/* !\brief SIM responded with keys. */
VOLTE_STATE_SIM_RESPONSE,
/* !\brief SIM responded with resync token. */
VOLTE_STATE_SIM_RESYNC,
/* !\brief SIM responded with failure. */
VOLTE_STATE_SIM_FAILED,
/* !\brief Send registration with authentication response. */
VOLTE_STATE_RESPONSE,
/* !\brief Send registration with resync token. */
VOLTE_STATE_RESYNC,
/* !\breif IMS registration process failed. */
VOLTE_STATE_FAILED,
};
/*! \brief Outbound registration client state information (persists for lifetime of regc) */
@ -445,6 +473,16 @@ struct sip_outbound_registration_client_state {
unsigned int registration_expires;
/*! \brief The value for the User-Agent header sent in requests */
char *user_agent;
/*! \brief VoLTE support */
unsigned int ims_aka;
/*! \brief Current state of registration process */
enum volte_state volte_state;
/*! \brief Current pending respones, while waiting for USIM data */
struct registration_response *volte_response;
/*! \brief Current states related to VoLTE process */
struct volte_states volte;
/*! \brief Non-zero if we have attempted sending a REGISTER with resync */
unsigned int resync_attempted:1;
};
/*! \brief Outbound registration state information (persists for lifetime that registration should exist) */
@ -728,6 +766,76 @@ static void add_security_headers(struct sip_outbound_registration_client_state *
ao2_cleanup(reg);
}
static pj_status_t volte_registration_client(struct sip_outbound_registration_client_state *client_state,
pjsip_tx_data *tdata)
{
struct sip_outbound_registration *reg = NULL;
struct ast_sip_endpoint *endpt = NULL;
struct ast_sip_transport *transp = NULL;
int rc = -1;
reg = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "registration",
client_state->registration_name);
if (!reg) {
ast_log(LOG_ERROR, "Internal error\n");
goto out;
}
if (!reg->endpoint ||
(!(endpt = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", reg->endpoint)))) {
ast_log(LOG_ERROR, "No endpoint configured in registration config for '%s'\n",
client_state->registration_name);
goto out;
}
if (!reg->transport ||
(!(transp = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "transport", reg->transport)))) {
ast_log(LOG_ERROR, "No transport configured in registration config for '%s'\n",
client_state->registration_name);
goto out;
}
if (!endpt->fromdomain) {
ast_log(LOG_ERROR, "No from_domain defined in endpoint config for '%s'\n", reg->endpoint);
goto out;
}
if (!transp->ims_port_c || !transp->ims_port_s) {
ast_log(LOG_ERROR, "No ims_port_c or ims_port_s defined in transport config for '%s'\n",
reg->transport);
goto out;
}
if (volte_del_authorization(tdata)) {
ast_log(LOG_ERROR, "Failed to remove authorization header.\n");
goto out;
}
if (volte_add_outbound_initial_authorization(tdata, endpt->fromdomain, &client_state->outbound_auths)) {
ast_log(LOG_ERROR, "Failed to add initial authorization header.\n");
goto out;
}
if (volte_add_sec_agree(tdata)) {
ast_log(LOG_ERROR, "Failed to add sec agree header.\n");
goto out;
}
if (volte_reset_transport(&client_state->volte)) {
ast_log(LOG_ERROR, "Failed to reset transport. Ignoring!\n");
goto out;
}
if (volte_add_security_client(&client_state->volte, tdata, transp->ims_port_c, transp->ims_port_s)) {
ast_log(LOG_ERROR, "Failed to add security client header.\n");
goto out;
}
rc = 0;
out:
ao2_cleanup(reg);
ao2_cleanup(endpt);
ao2_cleanup(transp);
return rc;
}
/*! \brief Helper function which sends a message and cleans up, if needed, on failure */
static pj_status_t registration_client_send(struct sip_outbound_registration_client_state *client_state,
pjsip_tx_data *tdata)
@ -785,6 +893,15 @@ static pj_status_t registration_client_send(struct sip_outbound_registration_cli
pjsip_regc_set_transport(client_state->client, &selector);
ast_sip_tpselector_unref(&selector);
/* Create initial IMS headers and reset transport. */
if (client_state->ims_aka && client_state->volte_state == VOLTE_STATE_REGISTER) {
if (volte_registration_client(client_state, tdata)) {
pjsip_tx_data_dec_ref(tdata);
ao2_ref(client_state, -1);
return -1;
}
}
status = pjsip_regc_send(client_state->client, tdata);
/*
@ -893,29 +1010,13 @@ static int handle_client_registration(void *data)
return -1;
}
client_state->volte_state = VOLTE_STATE_REGISTER;
registration_client_send(client_state, tdata);
return 0;
}
/*! \brief Timer callback function, used just for registrations */
static void sip_outbound_registration_timer_cb(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry)
{
struct sip_outbound_registration_client_state *client_state = entry->user_data;
entry->id = 0;
/*
* Transfer client_state reference to serializer task so the
* nominal path will not dec the client_state ref in this
* pjproject callback thread.
*/
if (ast_sip_push_task(client_state->serializer, handle_client_registration, client_state)) {
ast_log(LOG_WARNING, "Scheduled outbound registration could not be executed.\n");
ao2_ref(client_state, -1);
}
}
/*! \brief Helper function which sets up the timer to re-register in a specific amount of time */
static void schedule_registration(struct sip_outbound_registration_client_state *client_state, unsigned int seconds)
{
@ -975,6 +1076,9 @@ static int handle_client_state_destruction(void *data)
cancel_registration(client_state);
/* Cleanup IPSec translation. */
volte_cleanup_xfrm(&client_state->volte);
if (client_state->client) {
pjsip_regc_info info;
pjsip_tx_data *tdata;
@ -1046,8 +1150,23 @@ struct registration_response {
pjsip_tx_data *old_request;
/*! \brief Key for the reliable transport in use */
char transport_key[IP6ADDR_COLON_PORT_BUFLEN];
/*! \breif USIM authentication response */
uint8_t sim_res[8];
uint8_t sim_ik[16];
uint8_t sim_ck[16];
uint8_t sim_auts[14];
/*! \brief Timer for USIM reply timeout */
pj_timer_entry sim_timer;
};
/*! \brief Helper function which cancels the timer on registration response */
static void cancel_sim_timer(struct registration_response *response)
{
if (pj_timer_heap_cancel_if_active(pjsip_endpt_get_timer_heap(ast_sip_get_pjsip_endpoint()),
&response->sim_timer, response->sim_timer.id)) {
}
}
/*! \brief Registration response structure destructor */
static void registration_response_destroy(void *obj)
{
@ -1061,9 +1180,30 @@ static void registration_response_destroy(void *obj)
pjsip_tx_data_dec_ref(response->old_request);
}
if (response == response->client_state->volte_response)
response->client_state->volte_response = NULL;
cancel_sim_timer(response);
ao2_cleanup(response->client_state);
}
/*! \brief Timer callback function, used just for registrations */
static void sim_timeout_cb(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry)
{
struct registration_response *response = entry->user_data;
ast_log(LOG_ERROR, "Sim did not respond, authentication failed.\n");
if (response->client_state->destroy) {
/* We have a pending deferred destruction to complete now. */
ao2_ref(response->client_state, +1);
handle_client_state_destruction(response->client_state);
}
ao2_ref(response, -1);
}
/*! \brief Helper function which determines if a response code is temporal or not */
static int sip_outbound_registration_is_temporal(unsigned int code,
struct sip_outbound_registration_client_state *client_state)
@ -1250,6 +1390,153 @@ static void save_response_fields_to_transport(struct registration_response *resp
}
}
static struct ast_sip_auth *volte_get_sip_auth(const struct ast_sip_auth_vector *auth_vector)
{
size_t auth_size = AST_VECTOR_SIZE(auth_vector);
struct ast_sip_auth *auths[auth_size], *auth = NULL;
int idx;
memset(auths, 0, sizeof(auths));
if (ast_sip_retrieve_auths(auth_vector, auths)) {
ast_log(LOG_ERROR, "No authentication vector found. Please configure authentication for IMS\n");
goto cleanup;
}
for (idx = 0; idx < auth_size; ++idx) {
if (auths[idx]->type == AST_SIP_AUTH_TYPE_IMS_AKA)
break;
}
if (idx == auth_size) {
ast_log(LOG_ERROR, "No authentication vector found with type=ims_aka. Please fix config.\n");
goto cleanup;
}
auth = auths[idx];
cleanup:
ast_sip_cleanup_auths(auths, auth_size);
return auth;
}
static int handle_volte_unauthorized(struct registration_response *response, uint8_t *out_auts)
{
struct security_server sec;
struct ast_sip_auth *auth;
pj_str_t algo;
uint8_t rand[16], autn[16], out_ik[16], out_ck[16];
int rc;
if (!(auth = volte_get_sip_auth(&response->client_state->outbound_auths)))
return -1;
switch (response->client_state->volte_state) {
case VOLTE_STATE_SIM_RESPONSE:
ast_debug(1, "Processing Authentication response from SIM\n");
/* Registration response from SIM */
memcpy(auth->ims_res, response->sim_res, 8);
memcpy(out_ik, response->sim_ik, 16);
memcpy(out_ck, response->sim_ck, 16);
response->client_state->volte_response = NULL;
rc = 0;
break;
case VOLTE_STATE_SIM_RESYNC:
ast_debug(1, "Processing resync response from SIM\n");
response->client_state->volte_response = NULL;
memcpy(out_auts, response->sim_auts, 14);
rc = -EAGAIN;
break;
case VOLTE_STATE_SIM_FAILED:
ast_debug(1, "Processing failure response from SIM\n");
response->client_state->volte_response = NULL;
rc = -EINVAL;
break;
default:
ast_debug(1, "Processing REGISTER response from IMS\n");
/* Remove existing autorization header. */
if (volte_del_authorization(response->old_request)) {
ast_log(LOG_ERROR, "Failed to remove authorization header.\n");
return -1;
}
/* Get security server */
if (volte_get_security_server(&response->client_state->volte, response->rdata, &sec)) {
ast_log(LOG_ERROR, "Failed to parse the security server header.\n");
return -1;
}
if (volte_get_auth(&response->client_state->volte, response->rdata,
(response->code == 401) ? PJSIP_H_WWW_AUTHENTICATE : PJSIP_H_PROXY_AUTHENTICATE,
&algo, rand, autn)) {
ast_log(LOG_ERROR, "Failed to parse the authenticate header.\n");
return -1;
}
if (auth->usim_ami) {
pj_time_val delay = { .sec = 1, };
ast_debug(1, "Asking SIM card via AMI to authenticate with the callenge.\n");
volte_send_authrequest(response->client_state->registration_name, &algo, rand, autn);
response->client_state->volte_response = response;
response->client_state->volte_state = VOLTE_STATE_SIM_REQUEST;
if (pjsip_endpt_schedule_timer(ast_sip_get_pjsip_endpoint(), &response->sim_timer, &delay) != PJ_SUCCESS) {
ast_log(LOG_WARNING, "Failed to schedule SIM response timer\n");
return -1;
}
return 0;
}
rc = volte_authenticate(&response->client_state->volte, auth->usim_opc, auth->usim_k,
auth->usim_sqn, rand, autn, (uint8_t *)auth->ims_res,
out_ik, out_ck, out_auts);
}
if (rc == -EAGAIN) {
if (response->client_state->resync_attempted) {
ast_log(LOG_ERROR, "SQN out of sequence again, aborting.\n");
return -1;
}
ast_log(LOG_WARNING, "SQN out of sequence, syncing.\n");
auth->ims_res_len = 0;
response->client_state->volte_state = VOLTE_STATE_RESYNC;
response->client_state->auth_attempted = 0;
response->client_state->resync_attempted = 1;
return 0;
}
if (rc) {
ast_log(LOG_ERROR, "Authentication failed.\n");
return -1;
}
auth->ims_res_len = 8;
if (volte_set_transport(&response->client_state->volte, response->old_request, &sec.alg, &sec.ealg,
out_ik, pj_strtoul(&sec.spi_c), pj_strtoul(&sec.spi_s),
pj_strtoul(&sec.port_c), pj_strtoul(&sec.port_s))) {
ast_log(LOG_ERROR, "Failed to set transport.\n");
return -1;
}
if (volte_add_security_verify(&response->client_state->volte, response->old_request)) {
ast_log(LOG_ERROR, "Failed to add security verify.\n");
return -1;
}
response->client_state->volte_state = VOLTE_STATE_RESPONSE;
return 0;
}
/*! \brief Timer callback function, used just for registrations */
static void sip_outbound_registration_timer_cb(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry)
{
struct sip_outbound_registration_client_state *client_state = entry->user_data;
entry->id = 0;
/*
* Transfer client_state reference to serializer task so the
* nominal path will not dec the client_state ref in this
* pjproject callback thread.
*/
if (ast_sip_push_task(client_state->serializer, handle_client_registration, client_state)) {
ast_log(LOG_WARNING, "Scheduled outbound registration could not be executed.\n");
ao2_ref(client_state, -1);
}
}
/*! \brief Callback function for handling a response to a registration attempt */
static int handle_registration_response(void *data)
@ -1258,6 +1545,7 @@ static int handle_registration_response(void *data)
pjsip_regc_info info;
char server_uri[PJSIP_MAX_URL_SIZE];
char client_uri[PJSIP_MAX_URL_SIZE];
uint8_t auts[14];
if (response->client_state->status == SIP_REGISTRATION_STOPPED) {
ao2_ref(response, -1);
@ -1272,6 +1560,18 @@ static int handle_registration_response(void *data)
ast_debug(1, "Processing REGISTER response %d from server '%s' for client '%s'\n",
response->code, server_uri, client_uri);
if ((response->code == 401 || response->code == 407) && response->client_state->ims_aka) {
if (handle_volte_unauthorized(response, auts)) {
response->client_state->volte_state = VOLTE_STATE_FAILED;
goto volte_failed;
}
/* Wait for the SIM to respond. Store registration_response to client state. */
if (response->client_state->volte_state == VOLTE_STATE_SIM_REQUEST) {
response->client_state->volte_response = response;
return 0;
}
}
if (response->code == 408 || response->code == 503) {
if ((ast_sip_failover_request(response->old_request))) {
int res = registration_client_send(response->client_state, response->old_request);
@ -1331,7 +1631,14 @@ static int handle_registration_response(void *data)
return 0;
} else if (!ast_sip_create_request_with_auth(&response->client_state->outbound_auths,
response->rdata, response->old_request, &tdata)) {
response->client_state->auth_attempted = 1;
if (response->client_state->volte_state == VOLTE_STATE_RESYNC) {
if (volte_add_auts(&response->client_state->volte, tdata, auts)) {
ast_log(LOG_ERROR, "Failed to add authentication token.\n");
goto volte_failed;
}
}
if (!response->client_state->resync_attempted)
response->client_state->auth_attempted = 1;
ast_debug(1, "Sending authenticated REGISTER to server '%s' from client '%s'\n",
server_uri, client_uri);
pjsip_tx_data_add_ref(tdata);
@ -1353,8 +1660,11 @@ static int handle_registration_response(void *data)
}
/* Otherwise, fall through so the failure is processed appropriately */
}
volte_failed:
response->client_state->volte_state = VOLTE_STATE_REGISTER;
response->client_state->auth_attempted = 0;
response->client_state->resync_attempted = 0;
if (PJSIP_IS_STATUS_IN_CLASS(response->code, 200)) {
/* Check if this is in regards to registering or unregistering */
@ -1448,6 +1758,17 @@ static int handle_registration_response(void *data)
return 0;
}
static int queue_authresponse(struct sip_outbound_registration_state *state)
{
if (ast_sip_push_task(state->client_state->serializer, handle_registration_response, state->client_state->volte_response)) {
ast_log(LOG_WARNING, "Failed to pass incoming registration response to threadpool\n");
ao2_cleanup(state->client_state->volte_response);
return -1;
}
return 0;
}
/*! \brief Callback function for outbound registration client */
static void sip_outbound_registration_response_cb(struct pjsip_regc_cbparam *param)
{
@ -1462,6 +1783,12 @@ static void sip_outbound_registration_response_cb(struct pjsip_regc_cbparam *par
*callback_invoked = 1;
/* Cleanup pending ims response. */
if (client_state->volte_response) {
ao2_cleanup(client_state->volte_response);
client_state->volte_response = NULL;
}
response = ao2_alloc(sizeof(*response), registration_response_destroy);
if (!response) {
ao2_ref(client_state, -1);
@ -1475,6 +1802,7 @@ static void sip_outbound_registration_response_cb(struct pjsip_regc_cbparam *par
* pjproject callback thread.
*/
response->client_state = client_state;
pj_timer_entry_init(&response->sim_timer, 0, response, sim_timeout_cb);
ast_debug(1, "Received REGISTER response %d(%.*s)\n",
param->code, (int) param->reason.slen, param->reason.ptr);
@ -1550,6 +1878,10 @@ static void sip_outbound_registration_client_state_destroy(void *obj)
ast_statsd_log_string_va("PJSIP.registrations.state.%s", AST_STATSD_GAUGE, "-1", 1.0,
sip_outbound_registration_status_str(client_state->status));
/* In case there is an unfinished response, destroy it. */
if (client_state->volte_response)
ao2_cleanup(client_state->volte_response);
ast_taskprocessor_unreference(client_state->serializer);
ast_free(client_state->transport_name);
ast_free(client_state->registration_name);
@ -1583,6 +1915,7 @@ static struct sip_outbound_registration_state *sip_outbound_registration_state_a
state->client_state->registration_name =
ast_strdup(ast_sorcery_object_get_id(registration));
state->client_state->user_agent = ast_strdup(registration->user_agent);
state->client_state->ims_aka = registration->ims_aka;
ast_statsd_log_string("PJSIP.registrations.count", AST_STATSD_GAUGE, "+1", 1.0);
ast_statsd_log_string_va("PJSIP.registrations.state.%s", AST_STATSD_GAUGE, "+1", 1.0,
@ -1850,6 +2183,20 @@ cleanup:
return res;
}
/* Add intial authorization header for IMS AKA */
static int volte_add_outbound_initial_authorization(pjsip_tx_data *tdata, const char *fromdomain,
const struct ast_sip_auth_vector *auth_vector)
{
struct ast_sip_auth *auth;
if (!(auth = volte_get_sip_auth(auth_vector)))
return -1;
volte_init_authorization(tdata, fromdomain, auth->auth_user);
return 0;
}
/*! \brief Helper function that allocates a pjsip registration client and configures it */
static int sip_outbound_registration_regc_alloc(void *data)
{
@ -2023,8 +2370,12 @@ static int sip_outbound_registration_apply(const struct ast_sorcery *sorcery, vo
ast_log(LOG_ERROR, "Line support has been enabled on outbound registration '%s' without providing an endpoint\n",
ast_sorcery_object_get_id(applied));
return -1;
} else if (!ast_strlen_zero(applied->endpoint) && !applied->line) {
ast_log(LOG_ERROR, "An endpoint has been specified on outbound registration '%s' without enabling line support\n",
} else if (applied->ims_aka && ast_strlen_zero(applied->endpoint)) {
ast_log(LOG_ERROR, "IMS AKA support has been enabled on outbound registration '%s' without providing an endpoint\n",
ast_sorcery_object_get_id(applied));
return -1;
} else if (!ast_strlen_zero(applied->endpoint) && !applied->line && !applied->ims_aka) {
ast_log(LOG_ERROR, "An endpoint has been specified on outbound registration '%s' without enabling line or IMS AKA support\n",
ast_sorcery_object_get_id(applied));
return -1;
}
@ -2410,6 +2761,90 @@ static int ami_register(struct mansession *s, const struct message *m)
return 0;
}
static int ami_authresponse(struct mansession *s, const struct message *m)
{
const char *registration_name = astman_get_header(m, "Registration");
const char *res_str = astman_get_header(m, "RES");
const char *ik_str = astman_get_header(m, "IK");
const char *ck_str = astman_get_header(m, "CK");
const char *auts_str = astman_get_header(m, "AUTS");
struct sip_outbound_registration_state *state;
struct registration_response *response;
if (ast_strlen_zero(registration_name)) {
ast_log(LOG_ERROR, "SIM card responded: Registration parameter missing.\n");
astman_send_error(s, m, "Registration parameter missing");
return 0;
}
state = get_state(registration_name);
if (!state) {
ast_log(LOG_ERROR, "SIM card responded: Unable to retrieve registration entry.\n");
astman_send_error(s, m, "Unable to retrieve registration entry\n");
return 0;
}
if (!state->client_state || !state->client_state->volte_response) {
ast_debug(1, "SIM card responded: No pending AuthRequest.\n");
astman_send_error(s, m, "No pending AuthRequest\n");
ao2_ref(state, -1);
return 0;
}
response = state->client_state->volte_response;
ast_debug(1, "SIM card responded. RES=%s IK=%s CK=%s AUTS=%s\n", res_str, ik_str, ck_str, auts_str);
cancel_sim_timer(response);
if (res_str[0] && ik_str[0] && ck_str[0] && !auts_str[0]) {
if (volte_hex_to_octet_string("RES", res_str, response->sim_res, sizeof(response->sim_res))) {
ast_log(LOG_ERROR, "SIM card responded: RES value invalid.\n");
astman_send_error(s, m, "RES value invalid\n");
ao2_ref(state, -1);
return 0;
}
if (volte_hex_to_octet_string("IK", ik_str, response->sim_ik, sizeof(response->sim_ik))) {
ast_log(LOG_ERROR, "SIM card responded: IK value invalid.\n");
astman_send_error(s, m, "IK value invalid\n");
ao2_ref(state, -1);
return 0;
}
if (volte_hex_to_octet_string("CK", ck_str, response->sim_ck, sizeof(response->sim_ck))) {
ast_log(LOG_ERROR, "SIM card responded: CK value invalid.\n");
astman_send_error(s, m, "CK value invalid\n");
ao2_ref(state, -1);
return 0;
}
response->client_state->volte_state = VOLTE_STATE_SIM_RESPONSE;
} else if (!res_str[0] && !ik_str[0] && !ck_str[0] && auts_str[0]) {
if (volte_hex_to_octet_string("AUTS", auts_str, response->sim_auts, sizeof(response->sim_auts))) {
ast_log(LOG_ERROR, "SIM card responded: AUTS value invalid.\n");
astman_send_error(s, m, "AUTS value invalid\n");
ao2_ref(state, -1);
return 0;
}
response->client_state->volte_state = VOLTE_STATE_SIM_RESYNC;
} else if (!res_str[0] && !ik_str[0] && !ck_str[0] && !auts_str[0]) {
response->client_state->volte_state = VOLTE_STATE_SIM_FAILED;
} else {
ast_log(LOG_ERROR, "SIM card responded: Missing or too many AuthResponse values.\n");
astman_send_error(s, m, "Missing or too many AuthResponse values\n");
ao2_ref(state, -1);
return 0;
}
/* We need to serialize the unregister and register so they need
* to be queued as separate tasks.
*/
if (queue_authresponse(state)) {
astman_send_ack(s, m, "Failed to queue AuthResponse");
} else {
astman_send_ack(s, m, "AuthResponse sent");
}
ao2_ref(state, -1);
return 0;
}
struct sip_ami_outbound {
struct ast_sip_ami *ami;
int registered;
@ -2753,6 +3188,7 @@ static int unload_module(void)
ast_manager_unregister("PJSIPShowRegistrationsOutbound");
ast_manager_unregister("PJSIPUnregister");
ast_manager_unregister("PJSIPRegister");
ast_manager_unregister("AuthResponse");
ast_cli_unregister_multiple(cli_outbound_registration, ARRAY_LEN(cli_outbound_registration));
ast_sip_unregister_cli_formatter(cli_formatter);
@ -2788,6 +3224,8 @@ static int unload_module(void)
ao2_cleanup(shutdown_group);
shutdown_group = NULL;
g_volte_exit();
return 0;
}
@ -2845,6 +3283,7 @@ static int load_module(void)
ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "endpoint", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct sip_outbound_registration, endpoint));
ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "user_agent", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct sip_outbound_registration, user_agent));
ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "manual_register", "no", OPT_BOOL_T, 1, FLDSET(struct sip_outbound_registration, manual_register));
ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "ims_aka", "no", OPT_BOOL_T, 1, FLDSET(struct sip_outbound_registration, ims_aka));
/*
* Register sorcery observers.
@ -2880,10 +3319,17 @@ static int load_module(void)
ast_sip_register_cli_formatter(cli_formatter);
ast_cli_register_multiple(cli_outbound_registration, ARRAY_LEN(cli_outbound_registration));
/* Init VoLTE process. */
if (g_volte_init()) {
unload_module();
return AST_MODULE_LOAD_DECLINE;
}
/* Register AMI actions. */
ast_manager_register_xml("PJSIPUnregister", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, ami_unregister);
ast_manager_register_xml("PJSIPRegister", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, ami_register);
ast_manager_register_xml("PJSIPShowRegistrationsOutbound", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, ami_show_outbound_registrations);
ast_manager_register_xml_core("AuthResponse", 0, ami_authresponse);
/* Clear any previous statsd gauges in case we weren't shutdown cleanly */
ast_statsd_log("PJSIP.registrations.count", AST_STATSD_GAUGE, 0);

View File

@ -0,0 +1,368 @@
/*
* 3GPP AKA - Milenage algorithm (3GPP TS 35.205, .206, .207, .208)
* Copyright (c) 2006-2007 <j@w1.fi>
*
* This software may be distributed under the terms of the BSD license.
* See README for more details.
*
* This file implements an example authentication algorithm defined for 3GPP
* AKA. This can be used to implement a simple HLR/AuC into hlr_auc_gw to allow
* EAP-AKA to be tested properly with real USIM cards.
*
* This implementations assumes that the r1..r5 and c1..c5 constants defined in
* TS 35.206 are used, i.e., r1=64, r2=0, r3=32, r4=64, r5=96, c1=00..00,
* c2=00..01, c3=00..02, c4=00..04, c5=00..08. The block cipher is assumed to
* be AES (Rijndael).
*/
#include "milenage.h"
#include "asterisk.h"
#include "asterisk/utils.h"
#include "asterisk/crypto.h"
static int aes_128_encrypt_block(const u8 *key, const u8 *plain, u8 *encr)
{
ast_aes_encrypt_key aes_key;
ast_aes_set_encrypt_key(key, &aes_key);
if (ast_aes_encrypt(plain, encr, &aes_key) <= 0) {
ast_log(LOG_ERROR, "Failed to ecrypt AES 128.");
return -1;
}
return 0;
}
#if 0
void hexdump(int level, const char *file, int line, const char *func, const char *text, const uint8_t *data, int len)
{
char s[3 * len + 2], *p;
int f;
for (p = s, f = 0; f < len; f++, p += 3) {
sprintf(p, "%02hhX ", (unsigned char)data[f]);
}
ast_log(level, file, line, func, "%s: %s\n", text, s);
}
#endif
/**
* milenage_f1 - Milenage f1 and f1* algorithms
* @opc: OPc = 128-bit value derived from OP and K
* @k: K = 128-bit subscriber key
* @_rand: RAND = 128-bit random challenge
* @sqn: SQN = 48-bit sequence number
* @amf: AMF = 16-bit authentication management field
* @mac_a: Buffer for MAC-A = 64-bit network authentication code, or %NULL
* @mac_s: Buffer for MAC-S = 64-bit resync authentication code, or %NULL
* Returns: 0 on success, -1 on failure
*/
int milenage_f1(const u8 *opc, const u8 *k, const u8 *_rand,
const u8 *sqn, const u8 *amf, u8 *mac_a, u8 *mac_s)
{
u8 tmp1[16], tmp2[16], tmp3[16];
int i;
/* tmp1 = TEMP = E_K(RAND XOR OP_C) */
for (i = 0; i < 16; i++)
tmp1[i] = _rand[i] ^ opc[i];
if (aes_128_encrypt_block(k, tmp1, tmp1))
return -1;
/* tmp2 = IN1 = SQN || AMF || SQN || AMF */
memcpy(tmp2, sqn, 6);
memcpy(tmp2 + 6, amf, 2);
memcpy(tmp2 + 8, tmp2, 8);
/* OUT1 = E_K(TEMP XOR rot(IN1 XOR OP_C, r1) XOR c1) XOR OP_C */
/* rotate (tmp2 XOR OP_C) by r1 (= 0x40 = 8 bytes) */
for (i = 0; i < 16; i++)
tmp3[(i + 8) % 16] = tmp2[i] ^ opc[i];
/* XOR with TEMP = E_K(RAND XOR OP_C) */
for (i = 0; i < 16; i++)
tmp3[i] ^= tmp1[i];
/* XOR with c1 (= ..00, i.e., NOP) */
/* f1 || f1* = E_K(tmp3) XOR OP_c */
if (aes_128_encrypt_block(k, tmp3, tmp1))
return -1;
for (i = 0; i < 16; i++)
tmp1[i] ^= opc[i];
if (mac_a)
memcpy(mac_a, tmp1, 8); /* f1 */
if (mac_s)
memcpy(mac_s, tmp1 + 8, 8); /* f1* */
return 0;
}
/**
* milenage_f2345 - Milenage f2, f3, f4, f5, f5* algorithms
* @opc: OPc = 128-bit value derived from OP and K
* @k: K = 128-bit subscriber key
* @_rand: RAND = 128-bit random challenge
* @res: Buffer for RES = 64-bit signed response (f2), or %NULL
* @ck: Buffer for CK = 128-bit confidentiality key (f3), or %NULL
* @ik: Buffer for IK = 128-bit integrity key (f4), or %NULL
* @ak: Buffer for AK = 48-bit anonymity key (f5), or %NULL
* @akstar: Buffer for AK = 48-bit anonymity key (f5*), or %NULL
* Returns: 0 on success, -1 on failure
*/
int milenage_f2345(const u8 *opc, const u8 *k, const u8 *_rand,
u8 *res, u8 *ck, u8 *ik, u8 *ak, u8 *akstar)
{
u8 tmp1[16], tmp2[16], tmp3[16];
int i;
/* tmp2 = TEMP = E_K(RAND XOR OP_C) */
for (i = 0; i < 16; i++)
tmp1[i] = _rand[i] ^ opc[i];
if (aes_128_encrypt_block(k, tmp1, tmp2))
return -1;
/* OUT2 = E_K(rot(TEMP XOR OP_C, r2) XOR c2) XOR OP_C */
/* OUT3 = E_K(rot(TEMP XOR OP_C, r3) XOR c3) XOR OP_C */
/* OUT4 = E_K(rot(TEMP XOR OP_C, r4) XOR c4) XOR OP_C */
/* OUT5 = E_K(rot(TEMP XOR OP_C, r5) XOR c5) XOR OP_C */
/* f2 and f5 */
/* rotate by r2 (= 0, i.e., NOP) */
for (i = 0; i < 16; i++)
tmp1[i] = tmp2[i] ^ opc[i];
tmp1[15] ^= 1; /* XOR c2 (= ..01) */
/* f5 || f2 = E_K(tmp1) XOR OP_c */
if (aes_128_encrypt_block(k, tmp1, tmp3))
return -1;
for (i = 0; i < 16; i++)
tmp3[i] ^= opc[i];
if (res)
memcpy(res, tmp3 + 8, 8); /* f2 */
if (ak)
memcpy(ak, tmp3, 6); /* f5 */
/* f3 */
if (ck) {
/* rotate by r3 = 0x20 = 4 bytes */
for (i = 0; i < 16; i++)
tmp1[(i + 12) % 16] = tmp2[i] ^ opc[i];
tmp1[15] ^= 2; /* XOR c3 (= ..02) */
if (aes_128_encrypt_block(k, tmp1, ck))
return -1;
for (i = 0; i < 16; i++)
ck[i] ^= opc[i];
}
/* f4 */
if (ik) {
/* rotate by r4 = 0x40 = 8 bytes */
for (i = 0; i < 16; i++)
tmp1[(i + 8) % 16] = tmp2[i] ^ opc[i];
tmp1[15] ^= 4; /* XOR c4 (= ..04) */
if (aes_128_encrypt_block(k, tmp1, ik))
return -1;
for (i = 0; i < 16; i++)
ik[i] ^= opc[i];
}
/* f5* */
if (akstar) {
/* rotate by r5 = 0x60 = 12 bytes */
for (i = 0; i < 16; i++)
tmp1[(i + 4) % 16] = tmp2[i] ^ opc[i];
tmp1[15] ^= 8; /* XOR c5 (= ..08) */
if (aes_128_encrypt_block(k, tmp1, tmp1))
return -1;
for (i = 0; i < 6; i++)
akstar[i] = tmp1[i] ^ opc[i];
}
return 0;
}
/**
* milenage_generate - Generate AKA AUTN,IK,CK,RES
* @opc: OPc = 128-bit operator variant algorithm configuration field (encr.)
* @amf: AMF = 16-bit authentication management field
* @k: K = 128-bit subscriber key
* @sqn: SQN = 48-bit sequence number
* @_rand: RAND = 128-bit random challenge
* @autn: Buffer for AUTN = 128-bit authentication token
* @ik: Buffer for IK = 128-bit integrity key (f4), or %NULL
* @ck: Buffer for CK = 128-bit confidentiality key (f3), or %NULL
* @res: Buffer for RES = 64-bit signed response (f2), or %NULL
* @res_len: Max length for res; set to used length or 0 on failure
*/
void milenage_generate(const u8 *opc, const u8 *amf, const u8 *k,
const u8 *sqn, const u8 *_rand, u8 *autn, u8 *ik,
u8 *ck, u8 *res, size_t *res_len)
{
int i;
u8 mac_a[8], ak[6];
if (*res_len < 8) {
*res_len = 0;
return;
}
if (milenage_f1(opc, k, _rand, sqn, amf, mac_a, NULL) ||
milenage_f2345(opc, k, _rand, res, ck, ik, ak, NULL)) {
*res_len = 0;
return;
}
*res_len = 8;
/* AUTN = (SQN ^ AK) || AMF || MAC */
for (i = 0; i < 6; i++)
autn[i] = sqn[i] ^ ak[i];
memcpy(autn + 6, amf, 2);
memcpy(autn + 8, mac_a, 8);
}
/**
* milenage_auts - Milenage AUTS validation
* @opc: OPc = 128-bit operator variant algorithm configuration field (encr.)
* @k: K = 128-bit subscriber key
* @_rand: RAND = 128-bit random challenge
* @auts: AUTS = 112-bit authentication token from client
* @sqn: Buffer for SQN = 48-bit sequence number
* Returns: 0 = success (sqn filled), -1 on failure
*/
int milenage_auts(const u8 *opc, const u8 *k, const u8 *_rand, const u8 *auts,
u8 *sqn)
{
u8 amf[2] = { 0x00, 0x00 }; /* TS 33.102 v7.0.0, 6.3.3 */
u8 ak[6], mac_s[8];
int i;
if (milenage_f2345(opc, k, _rand, NULL, NULL, NULL, NULL, ak))
return -1;
for (i = 0; i < 6; i++)
sqn[i] = auts[i] ^ ak[i];
if (milenage_f1(opc, k, _rand, sqn, amf, NULL, mac_s) ||
memcmp(mac_s, auts + 6, 8) != 0)
return -1;
return 0;
}
/**
* gsm_milenage - Generate GSM-Milenage (3GPP TS 55.205) authentication triplet
* @opc: OPc = 128-bit operator variant algorithm configuration field (encr.)
* @k: K = 128-bit subscriber key
* @_rand: RAND = 128-bit random challenge
* @sres: Buffer for SRES = 32-bit SRES
* @kc: Buffer for Kc = 64-bit Kc
* Returns: 0 on success, -1 on failure
*/
int gsm_milenage(const u8 *opc, const u8 *k, const u8 *_rand, u8 *sres, u8 *kc)
{
u8 res[8], ck[16], ik[16];
int i;
if (milenage_f2345(opc, k, _rand, res, ck, ik, NULL, NULL))
return -1;
for (i = 0; i < 8; i++)
kc[i] = ck[i] ^ ck[i + 8] ^ ik[i] ^ ik[i + 8];
#ifdef GSM_MILENAGE_ALT_SRES
memcpy(sres, res, 4);
#else /* GSM_MILENAGE_ALT_SRES */
for (i = 0; i < 4; i++)
sres[i] = res[i] ^ res[i + 4];
#endif /* GSM_MILENAGE_ALT_SRES */
return 0;
}
/**
* milenage_generate - Generate AKA AUTN,IK,CK,RES
* @opc: OPc = 128-bit operator variant algorithm configuration field (encr.)
* @k: K = 128-bit subscriber key
* @sqn: SQN = 48-bit sequence number
* @_rand: RAND = 128-bit random challenge
* @autn: AUTN = 128-bit authentication token
* @ik: Buffer for IK = 128-bit integrity key (f4), or %NULL
* @ck: Buffer for CK = 128-bit confidentiality key (f3), or %NULL
* @res: Buffer for RES = 64-bit signed response (f2), or %NULL
* @res_len: Variable that will be set to RES length
* @auts: 112-bit buffer for AUTS
* Returns: 0 on success, -1 on failure, or -2 on synchronization failure
*/
int milenage_check(const u8 *opc, const u8 *k, const u8 *sqn, const u8 *_rand,
const u8 *autn, u8 *ik, u8 *ck, u8 *res, size_t *res_len,
u8 *auts)
{
int i;
u8 mac_a[8], ak[6], rx_sqn[6];
const u8 *amf;
#ifdef hexdump
hexdump(LOG_DEBUG, "Milenage: OPC", opc, 16);
hexdump(LOG_DEBUG, "Milenage: K", k, 16);
hexdump(LOG_DEBUG, "Milenage: AUTN", autn, 16);
hexdump(LOG_DEBUG, "Milenage: RAND", _rand, 16);
#endif
if (milenage_f2345(opc, k, _rand, res, ck, ik, ak, NULL))
return -1;
*res_len = 8;
#ifdef hexdump
hexdump(LOG_DEBUG, "Milenage: RES", res, *res_len);
hexdump(LOG_DEBUG, "Milenage: CK", ck, 16);
hexdump(LOG_DEBUG, "Milenage: IK", ik, 16);
hexdump(LOG_DEBUG, "Milenage: AK", ak, 6);
#endif
/* AUTN = (SQN ^ AK) || AMF || MAC */
for (i = 0; i < 6; i++)
rx_sqn[i] = autn[i] ^ ak[i];
#ifdef hexdump
hexdump(LOG_DEBUG, "Milenage: RX SQN", rx_sqn, 6);
hexdump(LOG_DEBUG, "Milenage: SQN", sqn, 6);
#endif
if (memcmp(rx_sqn, sqn, 6) <= 0) {
u8 auts_amf[2] = { 0x00, 0x00 }; /* TS 33.102 v7.0.0, 6.3.3 */
if (milenage_f2345(opc, k, _rand, NULL, NULL, NULL, NULL, ak))
return -1;
#ifdef hexdump
hexdump(LOG_DEBUG, "Milenage: AK*", ak, 6);
#endif
for (i = 0; i < 6; i++)
auts[i] = sqn[i] ^ ak[i];
if (milenage_f1(opc, k, _rand, sqn, auts_amf, NULL, auts + 6))
return -1;
#ifdef hexdump
hexdump(LOG_DEBUG, "Milenage: AUTS", auts, 14);
#endif
return -2;
}
amf = autn + 6;
#ifdef hexdump
hexdump(LOG_DEBUG, "Milenage: AMF", amf, 2);
#endif
if (milenage_f1(opc, k, _rand, rx_sqn, amf, mac_a, NULL))
return -1;
#ifdef hexdump
hexdump(LOG_DEBUG, "Milenage: MAC_A", mac_a, 8);
#endif
if (memcmp(mac_a, autn + 8, 8) != 0) {
#ifdef hexdump
ast_log(LOG_DEBUG, "Milenage: MAC mismatch");
hexdump(LOG_DEBUG, "Milenage: Received MAC_A",
autn + 8, 8);
#endif
return -1;
}
return 0;
}

View File

@ -0,0 +1,22 @@
#include <stdint.h>
#include <stddef.h>
#include <string.h>
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
int milenage_f1(const u8 *opc, const u8 *k, const u8 *_rand,
const u8 *sqn, const u8 *amf, u8 *mac_a, u8 *mac_s);
int milenage_f2345(const u8 *opc, const u8 *k, const u8 *_rand,
u8 *res, u8 *ck, u8 *ik, u8 *ak, u8 *akstar);
void milenage_generate(const u8 *opc, const u8 *amf, const u8 *k,
const u8 *sqn, const u8 *_rand, u8 *autn, u8 *ik,
u8 *ck, u8 *res, size_t *res_len);
int milenage_auts(const u8 *opc, const u8 *k, const u8 *_rand, const u8 *auts,
u8 *sqn);
int gsm_milenage(const u8 *opc, const u8 *k, const u8 *_rand, u8 *sres, u8 *kc);
int milenage_check(const u8 *opc, const u8 *k, const u8 *sqn, const u8 *_rand,
const u8 *autn, u8 *ik, u8 *ck, u8 *res, size_t *res_len,
u8 *auts);

View File

@ -0,0 +1,435 @@
/* Linux kernel IPsec interfacing via netlink XFRM
*
* Copyright (C) 2021 Harald Welte <laforge@osmocom.org>
*
* DOUBANGO 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.
*
* DOUBANGO 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.
*
* You should have received a copy of the GNU General Public License
* along with DOUBANGO.
*/
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <netinet/in.h>
#include <libmnl/libmnl.h>
#include <linux/xfrm.h>
#include <arpa/inet.h>
#include "netlink_xfrm.h"
#define XFRM_USER_ID 0x240299 /* some random number; let's use TS 24.299 */
struct mnl_socket *xfrm_init_mnl_socket(void)
{
struct mnl_socket *mnl_socket = mnl_socket_open(NETLINK_XFRM);
if (!mnl_socket) {
fprintf(stderr, "ERR: Could not open XFRM netlink socket: %s", strerror(errno));
return NULL;
}
if (mnl_socket_bind(mnl_socket, 0, MNL_SOCKET_AUTOPID) < 0) {
fprintf(stderr, "ERR: Could not open XFRM netlink socket: %s", strerror(errno));
mnl_socket_close(mnl_socket);
return NULL;
}
return mnl_socket;
}
void xfrm_exit_mnl_socket(struct mnl_socket *mnl_socket)
{
mnl_socket_close(mnl_socket);
}
static unsigned int get_next_nlmsg_seq(void)
{
static unsigned int next_seq;
return next_seq++;
}
/* this is just a simple call-back which returns the nlmsghdr via 'data' */
static int data_cb(const struct nlmsghdr *nlh, void *data)
{
const struct nlmsghdr **rx = data;
*rx = nlh;
/* FIXME: is there a situation in which we'd want to return OK and not STOP? */
return MNL_CB_STOP;
}
/* send 'tx' via 'mnl_sock' and receive messages from kernel, using caller-provided
* rx_buf/rx_buf_size as temporary storage buffer; return response nlmsghdr in 'rx' */
static int transceive_mnl(struct mnl_socket *mnl_sock, const struct nlmsghdr *tx,
uint8_t *rx_buf, size_t rx_buf_size, struct nlmsghdr **rx)
{
int rc;
rc = mnl_socket_sendto(mnl_sock, tx, tx->nlmsg_len);
if (rc < 0) {
fprintf(stderr, "ERR: cannot create IPsec SA: %s\n", strerror(errno));
return -1;
}
/* iterate until it is our answer, handing to mnl_cb_run, ... */
while (1) {
rc = mnl_socket_recvfrom(mnl_sock, rx_buf, rx_buf_size);
if (rc == -1) {
perror("mnl_socket_recvfrom");
return -EIO;
}
rc = mnl_cb_run(rx_buf, rc, tx->nlmsg_seq, mnl_socket_get_portid(mnl_sock), data_cb, rx);
if (rc == -1) {
perror("mnl_cb_run");
return -EIO;
} else if (rc <= MNL_CB_STOP)
break;
}
return 0;
}
static int sockaddrs2xfrm_sel(struct xfrm_selector *sel, const struct sockaddr *src,
const struct sockaddr *dst)
{
const struct sockaddr_in *sin;
const struct sockaddr_in6 *sin6;
switch (src->sa_family) {
case AF_INET:
sin = (const struct sockaddr_in *) src;
sel->saddr.a4 = sin->sin_addr.s_addr;
sel->prefixlen_s = 32;
sel->sport = sin->sin_port;
sin = (const struct sockaddr_in *) dst;
sel->daddr.a4 = sin->sin_addr.s_addr;
sel->prefixlen_d = 32;
sel->dport = sin->sin_port;
break;
case AF_INET6:
sin6 = (const struct sockaddr_in6 *) src;
memcpy(sel->saddr.a6, &sin6->sin6_addr, sizeof(sel->saddr.a6));
sel->prefixlen_s = 128;
sel->sport = sin6->sin6_port;
sin6 = (const struct sockaddr_in6 *) dst;
memcpy(sel->daddr.a6, &sin6->sin6_addr, sizeof(sel->daddr.a6));
sel->prefixlen_d = 128;
sel->dport = sin6->sin6_port;
break;
default:
return -EINVAL;
}
sel->dport_mask = 0xffff;
sel->sport_mask = 0xffff;
sel->family = src->sa_family;
return 0;
}
/***********************************************************************
* SPI Allocation
***********************************************************************/
/* allocate a local SPI for ESP between given src+dst address */
int xfrm_spi_alloc(struct mnl_socket *mnl_sock, uint32_t reqid, uint32_t *spi_out,
const struct sockaddr *src, const struct sockaddr *dst)
{
uint8_t msg_buf[MNL_SOCKET_BUFFER_SIZE];
uint8_t rx_buf[MNL_SOCKET_BUFFER_SIZE];
struct xfrm_userspi_info *xui, *rx_xui;
struct nlmsghdr *nlh, *rx_nlh = NULL;
const struct sockaddr_in *sin;
const struct sockaddr_in6 *sin6;
int rc;
memset(msg_buf, 0, sizeof(msg_buf));
if (src->sa_family != dst->sa_family)
return -EINVAL;
nlh = mnl_nlmsg_put_header(msg_buf);
nlh->nlmsg_flags = NLM_F_REQUEST,
nlh->nlmsg_type = XFRM_MSG_ALLOCSPI,
nlh->nlmsg_seq = get_next_nlmsg_seq();
//nlh->nlmsg_pid = reqid; //FIXME
xui = (struct xfrm_userspi_info *) mnl_nlmsg_put_extra_header(nlh, sizeof(*xui));
xui->info.family = src->sa_family;
/* RFC4303 reserves 0..255 */
xui->min = 0x100;
xui->max = 0xffffffff;
/* ID src, dst, proto */
switch (src->sa_family) {
case AF_INET:
sin = (const struct sockaddr_in *) src;
xui->info.saddr.a4 = sin->sin_addr.s_addr;
sin = (const struct sockaddr_in *) dst;
xui->info.id.daddr.a4 = sin->sin_addr.s_addr;
//xui->info.sel.prefixlen_d = 32;
break;
case AF_INET6:
sin6 = (const struct sockaddr_in6 *) src;
memcpy(xui->info.saddr.a6, &sin6->sin6_addr, sizeof(xui->info.saddr.a6));
//xui->info.sel.prefixlen_s = 128;
sin6 = (const struct sockaddr_in6 *) dst;
memcpy(xui->info.id.daddr.a6, &sin6->sin6_addr, sizeof(xui->info.id.daddr.a6));
//xui->info.sel.prefixlen_d = 128;
break;
default:
fprintf(stderr, "ERR: unsupported address family %u\n", src->sa_family);
return -1;
}
xui->info.id.proto = IPPROTO_ESP;
xui->info.reqid = reqid;
xui->info.mode = XFRM_MODE_TRANSPORT;
//xui->info.replay_window = 32; // TODO: check spec
rc = transceive_mnl(mnl_sock, nlh, rx_buf, MNL_SOCKET_BUFFER_SIZE, &rx_nlh);
if (rc < 0) {
fprintf(stderr, "ERR: cannot create IPsec SA: %s\n", strerror(errno));
return -1;
}
/* parse response */
rx_xui = (void *)rx_nlh + sizeof(*rx_nlh);
//printf("Allocated SPI=0x%08x\n", ntohl(xui->info.id.spi));
*spi_out = ntohl(rx_xui->info.id.spi);
return 0;
}
/***********************************************************************
* SA (Security Association)
***********************************************************************/
int xfrm_sa_del(struct mnl_socket *mnl_sock,
const struct sockaddr *src, const struct sockaddr *dst, uint32_t spi)
{
uint8_t msg_buf[MNL_SOCKET_BUFFER_SIZE];
uint8_t rx_buf[MNL_SOCKET_BUFFER_SIZE];
struct xfrm_usersa_id *said;
struct nlmsghdr *nlh, *rx_nlh;
const struct sockaddr_in *sin;
const struct sockaddr_in6 *sin6;
xfrm_address_t saddr;
int rc;
memset(&saddr, 0, sizeof(saddr));
nlh = mnl_nlmsg_put_header(msg_buf);
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
nlh->nlmsg_type = XFRM_MSG_DELSA;
nlh->nlmsg_seq = get_next_nlmsg_seq();
//nlh->nlmsg_pid = reqid; //FIXME
said = (struct xfrm_usersa_id *) mnl_nlmsg_put_extra_header(nlh, sizeof(*said));
said->spi = htonl(spi);
said->proto = IPPROTO_ESP;
said->family = src->sa_family;
switch (src->sa_family) {
case AF_INET:
sin = (const struct sockaddr_in *) src;
saddr.a4 = sin->sin_addr.s_addr;
sin = (const struct sockaddr_in *) dst;
said->daddr.a4 = sin->sin_addr.s_addr;
break;
case AF_INET6:
sin6 = (const struct sockaddr_in6 *) src;
memcpy(saddr.a6, &sin6->sin6_addr, sizeof(saddr.a6));
sin6 = (const struct sockaddr_in6 *) dst;
memcpy(said->daddr.a6, &sin6->sin6_addr, sizeof(said->daddr.a6));
break;
default:
fprintf(stderr, "ERR: unsupported address family %u\n", src->sa_family);
return -1;
}
mnl_attr_put(nlh, XFRMA_SRCADDR, sizeof(saddr), (void *)&saddr);
rc = transceive_mnl(mnl_sock, nlh, rx_buf, MNL_SOCKET_BUFFER_SIZE, &rx_nlh);
if (rc < 0) {
fprintf(stderr, "ERR: cannot delete IPsec SA: %s\n", strerror(errno));
return -1;
}
/* FIXME: parse response */
return 0;
}
int xfrm_sa_add(struct mnl_socket *mnl_sock, uint32_t reqid,
const struct sockaddr *src, const struct sockaddr *dst, uint32_t spi,
const struct xfrm_algo *auth_algo, const struct xfrm_algo *ciph_algo)
{
uint8_t msg_buf[MNL_SOCKET_BUFFER_SIZE];
uint8_t rx_buf[MNL_SOCKET_BUFFER_SIZE];
struct xfrm_usersa_info *sainfo;
struct nlmsghdr *nlh, *rx_nlh;
int rc;
nlh = mnl_nlmsg_put_header(msg_buf);
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK;
nlh->nlmsg_type = XFRM_MSG_NEWSA;
nlh->nlmsg_seq = get_next_nlmsg_seq();
//nlh->nlmsg_pid = reqid; //FIXME
sainfo = (struct xfrm_usersa_info *) mnl_nlmsg_put_extra_header(nlh, sizeof(*sainfo));
sainfo->sel.family = src->sa_family;
rc = sockaddrs2xfrm_sel(&sainfo->sel, src, dst);
if (rc < 0)
return -EINVAL;
sainfo->sel.user = htonl(XFRM_USER_ID);
sainfo->saddr = sainfo->sel.saddr;
sainfo->id.daddr = sainfo->sel.daddr;
sainfo->id.spi = htonl(spi);
sainfo->id.proto = IPPROTO_ESP;
sainfo->lft.soft_byte_limit = XFRM_INF;
sainfo->lft.hard_byte_limit = XFRM_INF;
sainfo->lft.soft_packet_limit = XFRM_INF;
sainfo->lft.hard_packet_limit = XFRM_INF;
sainfo->reqid = reqid;
sainfo->family = src->sa_family;
sainfo->mode = XFRM_MODE_TRANSPORT;
sainfo->replay_window = 32;
mnl_attr_put(nlh, XFRMA_ALG_AUTH, sizeof(struct xfrm_algo) + auth_algo->alg_key_len, auth_algo);
mnl_attr_put(nlh, XFRMA_ALG_CRYPT, sizeof(struct xfrm_algo) + ciph_algo->alg_key_len, ciph_algo);
rc = transceive_mnl(mnl_sock, nlh, rx_buf, MNL_SOCKET_BUFFER_SIZE, &rx_nlh);
if (rc < 0) {
fprintf(stderr, "ERR: cannot create IPsec SA: %s\n", strerror(errno));
return -1;
}
/* FIXME: parse response */
return 0;
}
/***********************************************************************
* Security Policy
***********************************************************************/
int xfrm_policy_add(struct mnl_socket *mnl_sock,
const struct sockaddr *src, const struct sockaddr *dst, uint32_t spi, bool dir_in)
{
uint8_t msg_buf[MNL_SOCKET_BUFFER_SIZE];
uint8_t rx_buf[MNL_SOCKET_BUFFER_SIZE];
struct xfrm_userpolicy_info *pinfo;
struct xfrm_user_tmpl tmpl;
struct nlmsghdr *nlh, *rx_nlh;
int rc;
memset(&tmpl, 0, sizeof(tmpl));
nlh = mnl_nlmsg_put_header(msg_buf);
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK;
nlh->nlmsg_type = XFRM_MSG_NEWPOLICY;
nlh->nlmsg_seq = get_next_nlmsg_seq();
//nlh->nlmsg_pid = reqid; //FIXME
pinfo = (struct xfrm_userpolicy_info *) mnl_nlmsg_put_extra_header(nlh, sizeof(*pinfo));
rc = sockaddrs2xfrm_sel(&pinfo->sel, src, dst);
if (rc < 0)
return -EINVAL;
pinfo->sel.user = htonl(XFRM_USER_ID);
pinfo->lft.soft_byte_limit = XFRM_INF;
pinfo->lft.hard_byte_limit = XFRM_INF;
pinfo->lft.soft_packet_limit = XFRM_INF;
pinfo->lft.hard_packet_limit = XFRM_INF;
pinfo->priority = 2342; // FIXME
pinfo->action = XFRM_POLICY_ALLOW;
pinfo->share = XFRM_SHARE_ANY;
if (dir_in)
pinfo->dir = XFRM_POLICY_IN;
else
pinfo->dir = XFRM_POLICY_OUT;
tmpl.id.proto = IPPROTO_ESP;
tmpl.id.daddr = pinfo->sel.daddr;
tmpl.saddr = pinfo->sel.saddr;
tmpl.family = pinfo->sel.family;
tmpl.reqid = spi;
tmpl.mode = XFRM_MODE_TRANSPORT;
tmpl.aalgos = 0xffffffff;
tmpl.ealgos = 0xffffffff;
tmpl.calgos = 0xffffffff;
mnl_attr_put(nlh, XFRMA_TMPL, sizeof(tmpl), &tmpl);
rc = transceive_mnl(mnl_sock, nlh, rx_buf, MNL_SOCKET_BUFFER_SIZE, &rx_nlh);
if (rc < 0) {
fprintf(stderr, "ERR: cannot create IPsec policy: %s\n", strerror(errno));
return -1;
}
/* FIXME: parse response */
return 0;
}
int xfrm_policy_del(struct mnl_socket *mnl_sock,
const struct sockaddr *src, const struct sockaddr *dst, bool dir_in)
{
uint8_t msg_buf[MNL_SOCKET_BUFFER_SIZE];
uint8_t rx_buf[MNL_SOCKET_BUFFER_SIZE];
struct xfrm_userpolicy_id *pid;
struct nlmsghdr *nlh, *rx_nlh;
int rc;
nlh = mnl_nlmsg_put_header(msg_buf);
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
nlh->nlmsg_type = XFRM_MSG_DELPOLICY;
nlh->nlmsg_seq = get_next_nlmsg_seq();
//nlh->nlmsg_pid = reqid; //FIXME
pid = (struct xfrm_userpolicy_id *) mnl_nlmsg_put_extra_header(nlh, sizeof(*pid));
rc = sockaddrs2xfrm_sel(&pid->sel, src, dst);
if (rc < 0)
return -EINVAL;
pid->sel.user = htonl(XFRM_USER_ID);
if (dir_in)
pid->dir = XFRM_POLICY_IN;
else
pid->dir = XFRM_POLICY_OUT;
rc = transceive_mnl(mnl_sock, nlh, rx_buf, MNL_SOCKET_BUFFER_SIZE, &rx_nlh);
if (rc < 0) {
fprintf(stderr, "ERR: cannot delete IPsec policy: %s\n", strerror(errno));
return -1;
}
/* FIXME: parse response */
return 0;
}

View File

@ -0,0 +1,33 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <sys/socket.h>
#include <linux/xfrm.h>
struct mnl_socket;
struct xfrm_algobuf {
struct xfrm_algo algo;
uint8_t buf[128];
};
struct mnl_socket *xfrm_init_mnl_socket(void);
void xfrm_exit_mnl_socket(struct mnl_socket *mnl_socket);
int xfrm_spi_alloc(struct mnl_socket *mnl_sock, uint32_t reqid, uint32_t *spi_out,
const struct sockaddr *src, const struct sockaddr *dst);
int xfrm_sa_add(struct mnl_socket *mnl_sock, uint32_t reqid,
const struct sockaddr *src, const struct sockaddr *dst, uint32_t spi,
const struct xfrm_algo *auth_algo, const struct xfrm_algo *ciph_algo);
int xfrm_sa_del(struct mnl_socket *mnl_sock,
const struct sockaddr *src, const struct sockaddr *dst, uint32_t spi);
int xfrm_policy_add(struct mnl_socket *mnl_sock,
const struct sockaddr *src, const struct sockaddr *dst, uint32_t spi, bool dir_in);
int xfrm_policy_del(struct mnl_socket *mnl_sock,
const struct sockaddr *src, const struct sockaddr *dst, bool dir_in);

View File

@ -0,0 +1,872 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* (C) 2024 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
*
* Author: Andreas Eversberg
*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/
#include "asterisk.h"
#include "asterisk/utils.h"
#include "asterisk/manager.h"
#include <pjsip.h>
#include "volte.h"
#include "milenage.h"
#define fmt_str(str) (int)(str).slen, (str).ptr
#define fmt_strp(strp) (int)(strp)->slen, (strp)->ptr
/* Socket for transform configuration */
static struct mnl_socket *g_mnl_socket = NULL;
/* Supported authentication and encryption algorithms. */
struct ipsec_alg {
const char *sip_name;
const char *kernel_name;
};
const struct ipsec_alg g_ipsec_alg[] = {
{ "hmac-md5-96", "md5" },
{ "hmac-sha-1-96", "sha1" },
{ NULL, NULL }
};
const struct ipsec_alg g_ipsec_ealg[] = {
{ "null", "cipher_null" },
{ NULL, NULL }
};
/* Global init function. Must be called before the first registration is made. May be called again. */
pj_status_t g_volte_init(void)
{
if (!g_mnl_socket)
g_mnl_socket = xfrm_init_mnl_socket();
if (!g_mnl_socket) {
ast_log(LOG_ERROR, "Failed to init mnl socket to admin xfrm rules. "
"Please make sure that the user running asterisk has the rights to do so. "
"(E.g use \"setcap 'cap_net_admin,cap_sys_resource=ep' /usr/sbin/asterisk\")\n");
return -EPERM;
}
return PJ_SUCCESS;
}
/* Global exit function. Must be called when module is unloaded. */
void g_volte_exit(void)
{
if (g_mnl_socket)
xfrm_exit_mnl_socket(g_mnl_socket);
}
/* Convert from pj_sockaddr to sockaddr_storage. */
static void copy_pj_sockaddr_to_sockaddr_storage(const pj_sockaddr *src, struct sockaddr_storage *dst)
{
memset(dst, 0, sizeof(struct sockaddr_storage));
dst->ss_family = (src->addr.sa_family == PJ_AF_INET) ? AF_INET : AF_INET6;
if (dst->ss_family == AF_INET) {
struct sockaddr_in *dst_in = (struct sockaddr_in *)dst;
const pj_sockaddr_in *src_in = (const pj_sockaddr_in *)src;
dst_in->sin_port = src_in->sin_port;
memcpy(&dst_in->sin_addr, &src_in->sin_addr, sizeof(struct in_addr));
} else if (dst->ss_family == AF_INET6) {
struct sockaddr_in6 *dst_in6 = (struct sockaddr_in6 *)dst;
const pj_sockaddr_in6 *src_in6 = (const pj_sockaddr_in6 *)src;
dst_in6->sin6_port = src_in6->sin6_port;
memcpy(&dst_in6->sin6_addr, &src_in6->sin6_addr, sizeof(struct in6_addr));
}
}
/* Convert sockaddr_storage IP to string. */
static char *sockaddr_storage_to_string(const struct sockaddr_storage *addr, char *ip_string, size_t ip_string_len)
{
if (addr->ss_family == AF_INET) {
const struct sockaddr_in *addr_in = (const struct sockaddr_in *)addr;
inet_ntop(AF_INET, &addr_in->sin_addr, ip_string, ip_string_len);
} else if (addr->ss_family == AF_INET6) {
const struct sockaddr_in6 *addr_in6 = (const struct sockaddr_in6 *)addr;
inet_ntop(AF_INET6, &addr_in6->sin6_addr, ip_string, ip_string_len);
} else {
snprintf(ip_string, ip_string_len, "<Unknown AF>");
}
return ip_string;
}
/* Delete old SA and SP entries upon new registration or module exit. */
void volte_cleanup_xfrm(struct volte_states *volte)
{
struct sockaddr_storage local_addr_c, local_addr_s;
struct sockaddr_storage remote_addr_c, remote_addr_s;
/* Convert from pj_sockaddr to sockaddr_storage. */
copy_pj_sockaddr_to_sockaddr_storage(&volte->local_addr_c, &local_addr_c);
copy_pj_sockaddr_to_sockaddr_storage(&volte->remote_addr_c, &remote_addr_c);
copy_pj_sockaddr_to_sockaddr_storage(&volte->local_addr_s, &local_addr_s);
copy_pj_sockaddr_to_sockaddr_storage(&volte->remote_addr_s, &remote_addr_s);
if (volte->local_sa_c_set || volte->local_sa_s_set || volte->remote_sa_c_set || volte->remote_sa_s_set ||
volte->local_sp_c_set || volte->local_sp_s_set || volte->remote_sp_c_set || volte->remote_sp_s_set)
ast_debug(1, "Remove old security associations/policies\n");
/* Remove current security associations and policies. */
if (volte->local_sa_c_set) {
xfrm_sa_del(g_mnl_socket, (const struct sockaddr *)&local_addr_c,
(const struct sockaddr *)&remote_addr_s, volte->remote_spi_s);
volte->local_sa_c_set = PJ_FALSE;
}
if (volte->local_sa_s_set) {
xfrm_sa_del(g_mnl_socket, (const struct sockaddr *)&local_addr_s,
(const struct sockaddr *)&remote_addr_c, volte->remote_spi_c);
volte->local_sa_s_set = PJ_FALSE;
}
if (volte->remote_sa_c_set) {
xfrm_sa_del(g_mnl_socket, (const struct sockaddr *)&remote_addr_c,
(const struct sockaddr *)&local_addr_s, volte->local_spi_s);
volte->remote_sa_c_set = PJ_FALSE;
}
if (volte->remote_sa_s_set) {
xfrm_sa_del(g_mnl_socket, (const struct sockaddr *)&remote_addr_s,
(const struct sockaddr *)&local_addr_c, volte->local_spi_c);
volte->remote_sa_s_set = PJ_FALSE;
}
if (volte->local_sp_c_set) {
xfrm_policy_del(g_mnl_socket, (const struct sockaddr *)&local_addr_c,
(const struct sockaddr *)&remote_addr_s, false);
volte->local_sp_c_set = PJ_FALSE;
}
if (volte->local_sp_s_set) {
xfrm_policy_del(g_mnl_socket, (const struct sockaddr *)&local_addr_s,
(const struct sockaddr *)&remote_addr_c, false);
volte->local_sp_s_set = PJ_FALSE;
}
if (volte->remote_sp_c_set) {
xfrm_policy_del(g_mnl_socket, (const struct sockaddr *)&remote_addr_c,
(const struct sockaddr *)&local_addr_s, true);
volte->remote_sp_c_set = PJ_FALSE;
}
if (volte->remote_sp_s_set) {
xfrm_policy_del(g_mnl_socket, (const struct sockaddr *)&remote_addr_s,
(const struct sockaddr *)&local_addr_c, true);
volte->remote_sp_s_set = PJ_FALSE;
}
}
pj_status_t volte_alloc_spi(struct volte_states *volte)
{
struct sockaddr_storage local_addr_c, local_addr_s;
struct sockaddr_storage remote_addr_c, remote_addr_s;
// char src_str[64], dst_str[64];
pj_status_t status;
/* Convert from pj_sockaddr to sockaddr_storage. */
copy_pj_sockaddr_to_sockaddr_storage(&volte->local_addr_c, &local_addr_c);
copy_pj_sockaddr_to_sockaddr_storage(&volte->remote_addr_c, &remote_addr_c);
copy_pj_sockaddr_to_sockaddr_storage(&volte->local_addr_s, &local_addr_s);
copy_pj_sockaddr_to_sockaddr_storage(&volte->remote_addr_s, &remote_addr_s);
#if 0
ast_debug(1, "SPI allocation: local client: %s:%d remote server: %s:%d\n",
sockaddr_storage_to_string(&local_addr_c, src_str, sizeof(src_str)),
pj_sockaddr_get_port(&volte->local_addr_c),
sockaddr_storage_to_string(&remote_addr_s, dst_str, sizeof(dst_str)),
pj_sockaddr_get_port(&volte->remote_addr_s));
ast_debug(1, "SPI allocation: remote client: %s:%d local server: %s:%d\n",
sockaddr_storage_to_string(&remote_addr_c, src_str, sizeof(src_str)),
pj_sockaddr_get_port(&volte->remote_addr_s),
sockaddr_storage_to_string(&local_addr_c, dst_str, sizeof(dst_str)),
pj_sockaddr_get_port(&volte->local_addr_s));
#endif
/* Allocate SPI-C and SPI-S towards remote peer. */
status = xfrm_spi_alloc(g_mnl_socket, 2342, &volte->local_spi_c, (const struct sockaddr *)&local_addr_c,
(const struct sockaddr *)&remote_addr_c);
if (status) {
spi_alloc_failed:
ast_log(LOG_ERROR, "Failed to allocate SPI.\n");
return status;
}
status = xfrm_spi_alloc(g_mnl_socket, 2342, &volte->local_spi_s, (const struct sockaddr *)&local_addr_s,
(const struct sockaddr *)&remote_addr_s);
if (status)
goto spi_alloc_failed;
ast_debug(1, "SPI allocation: SPI-C=0x%08x SPI-S=0x%08x\n", volte->local_spi_s, volte->local_spi_c);
return PJ_SUCCESS;
}
/* Set new SA and SP entries upon secuirty handshake. */
static pj_status_t volte_set_xfrm(struct volte_states *volte, const pj_str_t *alg, const pj_str_t *ealg, uint8_t *ik)
{
struct xfrm_algobuf auth_algo, ciph_algo;
int i, j;
struct sockaddr_storage local_addr_c, local_addr_s;
struct sockaddr_storage remote_addr_c, remote_addr_s;
char src_str[64], dst_str[64];
pj_status_t sa_add_failed = PJ_SUCCESS, sp_add_failed = PJ_SUCCESS;
pj_status_t status;
/* Set authentication and encryption algorithms and key. */
for (i = 0; g_ipsec_alg[i].sip_name; i++) {
if (!pj_strncmp2(alg, g_ipsec_alg[i].sip_name, strlen(g_ipsec_alg[i].sip_name)))
break;
}
if (!g_ipsec_alg[i].kernel_name) {
ast_log(LOG_ERROR, "Given 'alg' not supported.\n");
return -EINVAL;
}
for (j = 0; g_ipsec_ealg[j].sip_name; j++) {
if (!pj_strncmp2(ealg, g_ipsec_ealg[j].sip_name, strlen(g_ipsec_ealg[j].sip_name)))
break;
}
if (!g_ipsec_ealg[j].kernel_name) {
ast_log(LOG_ERROR, "Given 'ealg' not supported.\n");
return -EINVAL;
}
strcpy(auth_algo.algo.alg_name, g_ipsec_alg[i].kernel_name);
switch (i) {
case 0:
memcpy(auth_algo.algo.alg_key, ik, 16);
auth_algo.algo.alg_key_len = 128;
break;
case 1:
memcpy(auth_algo.algo.alg_key, ik, 16);
memset(auth_algo.algo.alg_key + 16, 0x00, 4);
auth_algo.algo.alg_key_len = 160;
break;
}
strcpy(ciph_algo.algo.alg_name, g_ipsec_ealg[j].kernel_name);
switch (j) {
case 0:
ciph_algo.algo.alg_key_len = 0;
break;
}
copy_pj_sockaddr_to_sockaddr_storage(&volte->local_addr_c, &local_addr_c);
copy_pj_sockaddr_to_sockaddr_storage(&volte->remote_addr_c, &remote_addr_c);
copy_pj_sockaddr_to_sockaddr_storage(&volte->local_addr_s, &local_addr_s);
copy_pj_sockaddr_to_sockaddr_storage(&volte->remote_addr_s, &remote_addr_s);
ast_debug(1, "xfrm: local client: %s:%d (SPI=0x%08x) remote server: %s:%d (SPI=0x%08x)\n",
sockaddr_storage_to_string(&local_addr_c, src_str, sizeof(src_str)),
pj_sockaddr_get_port(&volte->local_addr_c), volte->local_spi_c,
sockaddr_storage_to_string(&remote_addr_s, dst_str, sizeof(dst_str)),
pj_sockaddr_get_port(&volte->remote_addr_s), volte->remote_spi_s);
ast_debug(1, "xfrm: remote client: %s:%d (SPI=0x%08x) local server: %s:%d (SPI=0x%08x)\n",
sockaddr_storage_to_string(&remote_addr_c, src_str, sizeof(src_str)),
pj_sockaddr_get_port(&volte->remote_addr_s), volte->remote_spi_c,
sockaddr_storage_to_string(&local_addr_c, dst_str, sizeof(dst_str)),
pj_sockaddr_get_port(&volte->local_addr_s), volte->local_spi_s);
ast_debug(1, "xfrm: alg: %s ealg: %s\n", auth_algo.algo.alg_name, ciph_algo.algo.alg_name);
status = xfrm_sa_add(g_mnl_socket, volte->remote_spi_s, (const struct sockaddr *)&local_addr_c,
(const struct sockaddr *)&remote_addr_s, volte->remote_spi_s,
&auth_algo.algo, &ciph_algo.algo);
if (status)
sa_add_failed = status;
else
volte->local_sa_c_set = PJ_TRUE;
status = xfrm_sa_add(g_mnl_socket, volte->remote_spi_c, (const struct sockaddr *)&local_addr_s,
(const struct sockaddr *)&remote_addr_c, volte->remote_spi_c,
&auth_algo.algo, &ciph_algo.algo);
if (status)
sa_add_failed = status;
else
volte->local_sa_s_set = PJ_TRUE;
status = xfrm_sa_add(g_mnl_socket, volte->local_spi_s, (const struct sockaddr *)&remote_addr_c,
(const struct sockaddr *)&local_addr_s, volte->local_spi_s,
&auth_algo.algo, &ciph_algo.algo);
if (status)
sa_add_failed = status;
else
volte->remote_sa_c_set = PJ_TRUE;
status = xfrm_sa_add(g_mnl_socket, volte->local_spi_c, (const struct sockaddr *)&remote_addr_s,
(const struct sockaddr *)&local_addr_c, volte->local_spi_c,
&auth_algo.algo, &ciph_algo.algo);
if (status)
sa_add_failed = status;
else
volte->remote_sa_s_set = PJ_TRUE;
status = xfrm_policy_add(g_mnl_socket, (const struct sockaddr *)&local_addr_c,
(const struct sockaddr *)&remote_addr_s, volte->remote_spi_s, false);
if (status)
sp_add_failed = status;
else
volte->local_sp_c_set = PJ_TRUE;
status = xfrm_policy_add(g_mnl_socket, (const struct sockaddr *)&local_addr_s,
(const struct sockaddr *)&remote_addr_c, volte->remote_spi_c, false);
if (status)
sp_add_failed = status;
else
volte->local_sp_s_set = PJ_TRUE;
status = xfrm_policy_add(g_mnl_socket, (const struct sockaddr *)&remote_addr_c,
(const struct sockaddr *)&local_addr_s, volte->local_spi_s, true);
if (status)
sp_add_failed = status;
else
volte->remote_sp_c_set = PJ_TRUE;
status = xfrm_policy_add(g_mnl_socket, (const struct sockaddr *)&remote_addr_s,
(const struct sockaddr *)&local_addr_c, volte->local_spi_c, true);
if (status)
sp_add_failed = status;
else
volte->remote_sp_s_set = PJ_TRUE;
if (sa_add_failed)
ast_log(LOG_ERROR, "Failed to add IPSec SA.\n");
if (sp_add_failed)
ast_log(LOG_ERROR, "Failed to add IPSec SP.\n");
return (sa_add_failed) ? sa_add_failed : sp_add_failed;
}
/* Header field names */
const pj_str_t STR_SUPPORTED = { "Supported", 9 };
const pj_str_t STR_REQUIRE = { "Require", 7 };
const pj_str_t STR_PROXY_REQUIRE = { "Proxy-Require", 13 };
const pj_str_t STR_PATH = { "path", 4 };
const pj_str_t STR_SEC_AGREE = { "sec-agree", 9 };
const pj_str_t STR_AUTHORIZATION = { "Authorization", 13 };
const pj_str_t STR_AUTS = { "auts", 4 };
const pj_str_t STR_SECURITY_CLIENT = { "Security-Client", 15 };
const pj_str_t STR_SECURITY_SERVER = { "Security-Server", 15 };
const pj_str_t STR_SECURITY_VERIFY = { "Security-Verify", 15 };
const pj_str_t STR_Q = { "q", 1 };
const pj_str_t STR_PROT = { "prot", 4 };
const pj_str_t STR_MOD = { "mod", 3 };
const pj_str_t STR_SPI_C = { "spi-c", 5 };
const pj_str_t STR_SPI_S = { "spi-s", 5 };
const pj_str_t STR_PORT_C = { "port-c", 6 };
const pj_str_t STR_PORT_S = { "port-s", 6 };
const pj_str_t STR_ALG = { "alg", 3 };
const pj_str_t STR_EALG = { "ealg", 4 };
/* Create string header and add given value. */
static pj_status_t add_value_string_hdr(pjsip_tx_data *tdata, const pj_str_t *name, const pj_str_t *value)
{
pjsip_generic_string_hdr *hdr;
/* Add header. */
hdr = pjsip_generic_string_hdr_create(tdata->pool, name, value);
if (!hdr) {
ast_log(LOG_ERROR, "Failed to create string header.");
return -ENOMEM;
}
/* Append header */
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *)hdr);
return PJ_SUCCESS;
}
/* Add a value to an array header. If the header does not exist, it is created. */
static pj_status_t add_value_array_hdr(pjsip_tx_data *tdata, const pj_str_t *name, const pj_str_t *value)
{
pjsip_generic_array_hdr *hdr;
pj_bool_t created = PJ_FALSE;
/* Create header, if not yet created. */
hdr = pjsip_msg_find_hdr_by_name(tdata->msg, name, NULL);
if (!hdr) {
hdr = pjsip_generic_array_hdr_create(tdata->pool, name);
created = PJ_TRUE;
}
if (!hdr) {
ast_log(LOG_ERROR, "Failed to create array header.");
return -ENOMEM;
}
if (hdr->count == PJSIP_GENERIC_ARRAY_MAX_COUNT) {
ast_log(LOG_ERROR, "Too many evalue in array, skipping %s.", value->ptr);
return -E2BIG;
}
pj_strdup(tdata->pool, &hdr->values[hdr->count++], value);
/* Append header, if created. */
if (created)
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *)hdr);
return PJ_SUCCESS;
}
/* Add security client header to SIP message. */
static pj_status_t add_securety_client_hdr(pjsip_tx_data *tdata, const struct ipsec_alg alg[],
const struct ipsec_alg ealg[], uint32_t spi_c, uint32_t spi_s,
uint16_t port_c, uint16_t port_s)
{
pjsip_generic_array_hdr *hdr;
char str[256];
int i, j;
/* Add Security-Client header. */
hdr = pjsip_generic_array_hdr_create(tdata->pool, &STR_SECURITY_CLIENT);
if (!hdr) {
ast_log(LOG_ERROR, "Failed to create header.");
return -ENOMEM;
}
/* Create tupple for given algorithms. */
for (i = 0; alg[i].sip_name; i++) {
for (j = 0; ealg[j].sip_name; j++) {
snprintf(str, sizeof(str), "ipsec-3gpp; alg=%s; ealg=%s; spi-c=%u; spi-s=%u; "
"port-c=%u; port-s=%u", alg[i].sip_name, ealg[j].sip_name, spi_c, spi_s,
port_c, port_s);
if (hdr->count == PJSIP_GENERIC_ARRAY_MAX_COUNT) {
ast_log(LOG_ERROR, "Too many evalue in array, skipping '%s'.", str);
continue;
}
pj_strdup2(tdata->pool, &hdr->values[hdr->count++], str);
}
}
/* Append header */
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *)hdr);
return PJ_SUCCESS;
}
/* Add Sec-Agree to header. */
pj_status_t volte_add_sec_agree(pjsip_tx_data *tdata)
{
pj_status_t status;
/* "Require: sec-agree" */
status = add_value_array_hdr(tdata, &STR_REQUIRE, &STR_SEC_AGREE);
if (status)
return status;
/* "Proxy-Require: sec-agree" */
status = add_value_string_hdr(tdata, &STR_PROXY_REQUIRE, &STR_SEC_AGREE);
if (status)
return status;
/* "Supported: path,sec-agree" */
status = add_value_array_hdr(tdata, &STR_SUPPORTED, &STR_PATH);
if (status)
return status;
status = add_value_array_hdr(tdata, &STR_SUPPORTED, &STR_SEC_AGREE);
if (status)
return status;
return PJ_SUCCESS;
}
/* Add initial Authorization header. */
pj_status_t volte_init_authorization(pjsip_tx_data *tdata, const char *fromdomain, const char *username)
{
char authorization[1024];
pj_status_t status;
snprintf(authorization, sizeof(authorization),
"Digest uri=\"sip:%s\",usernmame=\"%s\",response=\"\",realm=\"%s\",nonce=\"\"",
fromdomain, username, fromdomain);
const pj_str_t authorization_str = {authorization, strlen(authorization)};
status = add_value_string_hdr(tdata, &STR_AUTHORIZATION, &authorization_str);
if (status)
return status;
return PJ_SUCCESS;
}
/* Remove initial Authorization header. */
pj_status_t volte_del_authorization(pjsip_tx_data *tdata)
{
pjsip_authorization_hdr *auth_hdr;
/* remove double authentication header */
auth_hdr = pjsip_msg_find_hdr_by_name(tdata->msg, &STR_AUTHORIZATION, NULL);
if (auth_hdr) {
pj_list_erase(auth_hdr);
}
return PJ_SUCCESS;
}
/* Reset old transport and clear IPSec transformations */
pj_status_t volte_reset_transport(struct volte_states *volte)
{
pj_status_t status;
int old_port_c;
/* Cleanup IPSec transform. */
volte_cleanup_xfrm(volte);
/* Cleanup old transport. */
old_port_c = pj_sockaddr_get_port(&volte->local_addr_c);
if (old_port_c > 0 && old_port_c < 65535 && volte->transport) {
/* Create socket with default transport port. */
status = volte->transport->create_new_sock(volte->transport, NULL);
if (status != PJ_SUCCESS) {
ast_log(LOG_ERROR, "Failed to get connection addresses (errno=%d).\n", errno);
return status;
}
status = volte->transport->connect_new_sock(volte->transport,
&volte->local_addr_c, &volte->remote_addr_orig);
if (status != PJ_SUCCESS) {
ast_log(LOG_ERROR, "Failed to change connection addresses (errno=%d).\n", errno);
return status;
}
}
return PJ_SUCCESS;
}
/* Add security client header. */
pj_status_t volte_add_security_client(struct volte_states *volte, pjsip_tx_data *tdata, int port_c, int port_s)
{
pj_status_t status;
/* Reset addresses. */
memset(&volte->local_addr_c, 0, sizeof(pj_sockaddr));
volte->local_addr_c.addr.sa_family = PJ_AF_INET;
memset(&volte->local_addr_s, 0, sizeof(pj_sockaddr));
volte->local_addr_s.addr.sa_family = PJ_AF_INET;
memset(&volte->remote_addr_c, 0, sizeof(pj_sockaddr));
volte->remote_addr_c.addr.sa_family = PJ_AF_INET;
memset(&volte->remote_addr_s, 0, sizeof(pj_sockaddr));
volte->remote_addr_s.addr.sa_family = PJ_AF_INET;
/* Set new local ports */
pj_sockaddr_set_port(&volte->local_addr_c, port_c);
pj_sockaddr_set_port(&volte->local_addr_s, port_s);
/* Allocate SPI */
status = volte_alloc_spi(volte);
if (status)
return status;
status = add_securety_client_hdr(tdata, g_ipsec_alg, g_ipsec_ealg, volte->local_spi_c, volte->local_spi_s,
port_c, port_s);
if (status)
return status;
return PJ_SUCCESS;
}
/* Set new transport and set IPSec transformations */
pj_status_t volte_set_transport(struct volte_states *volte, pjsip_tx_data *tdata, const pj_str_t *alg,
const pj_str_t *ealg, uint8_t *ik, uint32_t remote_spi_c, uint32_t remote_spi_s,
uint16_t remote_port_c, uint16_t remote_port_s)
{
int local_port_c, local_port_s;
pj_status_t status;
/* Cleanup IPSec transform. */
volte_cleanup_xfrm(volte);
/* Get addresses from current transport and replace the local ports. */
local_port_c = pj_sockaddr_get_port(&volte->local_addr_c);
local_port_s = pj_sockaddr_get_port(&volte->local_addr_s);
memcpy(&volte->local_addr_c, &tdata->tp_info.transport->local_addr, sizeof(pj_sockaddr));
memcpy(&volte->local_addr_s, &tdata->tp_info.transport->factory->local_addr, sizeof(pj_sockaddr));
memcpy(&volte->remote_addr_c, &tdata->tp_info.dst_addr, sizeof(pj_sockaddr));
memcpy(&volte->remote_addr_s, &tdata->tp_info.dst_addr, sizeof(pj_sockaddr));
memcpy(&volte->remote_addr_orig, &tdata->tp_info.dst_addr, sizeof(pj_sockaddr));
pj_sockaddr_set_port(&volte->local_addr_c, local_port_c);
pj_sockaddr_set_port(&volte->local_addr_s, local_port_s);
pj_sockaddr_set_port(&volte->remote_addr_c, remote_port_c);
pj_sockaddr_set_port(&volte->remote_addr_s, remote_port_s);
/* Set IPSec transform. */
volte->remote_spi_c = remote_spi_c;
volte->remote_spi_s = remote_spi_s;
volte_set_xfrm(volte, alg, ealg, ik);
/* Create socket with new transport port. */
if (!tdata->tp_info.transport) {
ast_log(LOG_ERROR, "The Message has no transport. Please fix!\n");
return -ENOTSUP;
}
if (!tdata->tp_info.transport->create_new_sock || !tdata->tp_info.transport->connect_new_sock) {
ast_log(LOG_ERROR, "The transport protocol does not support socket change. Please fix!\n");
return -ENOTSUP;
}
status = tdata->tp_info.transport->create_new_sock(tdata->tp_info.transport, &volte->local_addr_c);
if (status != PJ_SUCCESS) {
ast_log(LOG_ERROR, "Failed to get connection addresses (errno=%d).\n", errno);
return status;
}
status = tdata->tp_info.transport->connect_new_sock(tdata->tp_info.transport,
&volte->local_addr_c, &volte->remote_addr_s);
if (status != PJ_SUCCESS) {
ast_log(LOG_ERROR, "Failed to change connection addresses (errno=%d).\n", errno);
return status;
}
volte->transport = tdata->tp_info.transport;
return PJ_SUCCESS;
}
static void on_syntax_error(pj_scanner *scanner)
{
PJ_UNUSED_ARG(scanner);
}
/* Store and parse security server from SIP header and fill a structure. */
pj_status_t volte_get_security_server(struct volte_states *volte, pjsip_rx_data *rdata, struct security_server *sec)
{
pjsip_generic_string_hdr *sec_hdr;
pj_scanner scanner;
int i, j;
/* Get Security-Server from header. */
sec_hdr = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &STR_SECURITY_SERVER, NULL);
if (!sec_hdr || !sec_hdr->hvalue.ptr) {
ast_log(LOG_ERROR, "Missing 'Security-Server' in REGISTER response.");
return -EINVAL;
}
/* Store for security verify. */
if (sec_hdr->hvalue.slen < sizeof(volte->security_server)) {
memcpy(volte->security_server, sec_hdr->hvalue.ptr, sec_hdr->hvalue.slen);
volte->security_server[sec_hdr->hvalue.slen] = '\0';
} else {
ast_log(LOG_ERROR, "Security-Server' too large.");
return -EINVAL;
}
memset(sec, 0, sizeof(*sec));
pj_scan_init(&scanner, sec_hdr->hvalue.ptr, sec_hdr->hvalue.slen, 0, &on_syntax_error);
for (;;) {
pj_str_t name, value;
pjsip_parse_param_imp(&scanner, rdata->tp_info.pool, &name, &value, 0);
if (!pj_stricmp(&name, &STR_Q)) {
sec->q = value;
} else
if (!pj_stricmp(&name, &STR_PROT)) {
sec->prot = value;
} else
if (!pj_stricmp(&name, &STR_MOD)) {
sec->mod = value;
} else
if (!pj_stricmp(&name, &STR_SPI_C)) {
sec->spi_c = value;
} else
if (!pj_stricmp(&name, &STR_SPI_S)) {
sec->spi_s = value;
} else
if (!pj_stricmp(&name, &STR_PORT_C)) {
sec->port_c = value;
} else
if (!pj_stricmp(&name, &STR_PORT_S)) {
sec->port_s = value;
} else
if (!pj_stricmp(&name, &STR_ALG)) {
sec->alg = value;
} else
if (!pj_stricmp(&name, &STR_EALG)) {
sec->ealg = value;
}
if (pj_scan_is_eof(&scanner))
break;
/* Eat semicolon */
if (*scanner.curptr == ';')
pj_scan_get_char(&scanner);
}
pj_scan_fini(&scanner);
if (!sec->prot.ptr || !sec->spi_c.ptr || !sec->spi_s.ptr || !sec->port_c.ptr || !sec->port_s.ptr ||
!sec->alg.ptr || !sec->ealg.ptr) {
ast_log(LOG_ERROR, "Missing 'Security-Server' elements in REGISTER response. header=\"%.*s\", "
"prot=%.*s, spi-c=%.*s, spi-s=%.*s, port-c=%.*s, port-s=%.*s, alg=%.*s, ealg=%.*s",
fmt_str(sec_hdr->hvalue), fmt_str(sec->prot), fmt_str(sec->spi_c), fmt_str(sec->spi_s),
fmt_str(sec->port_c), fmt_str(sec->port_s), fmt_str(sec->alg), fmt_str(sec->ealg));
return -EINVAL;
}
for (i = 0; g_ipsec_alg[i].sip_name; i++) {
if (!pj_strncmp2(&sec->alg, g_ipsec_alg[i].sip_name, strlen(g_ipsec_alg[i].sip_name)))
break;
}
if (!g_ipsec_alg[i].kernel_name) {
ast_log(LOG_ERROR, "Given 'alg=%.*s' in Security-Server header not found.\n", fmt_str(sec->alg));
return -EINVAL;
}
for (j = 0; g_ipsec_ealg[j].sip_name; j++) {
if (!pj_strncmp2(&sec->ealg, g_ipsec_ealg[j].sip_name, strlen(g_ipsec_ealg[j].sip_name)))
break;
}
if (!g_ipsec_ealg[j].kernel_name) {
ast_log(LOG_ERROR, "Given 'ealg=%.*s' in Security-Server header not found.\n", fmt_str(sec->ealg));
return -EINVAL;
}
return PJ_SUCCESS;
}
pj_status_t volte_add_security_verify(struct volte_states *volte, pjsip_tx_data *tdata)
{
const pj_str_t security = { volte->security_server, strlen(volte->security_server)};
pj_status_t status;
status = add_value_string_hdr(tdata, &STR_SECURITY_VERIFY, &security);
if (status)
return status;
return PJ_SUCCESS;
}
pj_status_t volte_hex_to_octet_string(const char *name, const char *input, uint8_t *output, size_t output_size)
{
int i, n;
if (!input || !input[0]) {
ast_log(LOG_ERROR, "Missing value for hex string '%s'.\n", name);
return -EINVAL;
}
i = n = 0;
while (*input) {
if (i == output_size) {
ast_log(LOG_ERROR, "Value for hex string '%s' too long, expecting %zu bytes.\n", name,
output_size);
return -EINVAL;
}
if (*input >= '0' && *input <= '9')
output[i] = (output[i] << 4) | (*input - '0');
else if (*input >= 'a' && *input <= 'f')
output[i] = (output[i] << 4) | (*input - 'a' + 10);
else if (*input >= 'A' && *input <= 'F')
output[i] = (output[i] << 4) | (*input - 'A' + 10);
else {
ast_log(LOG_ERROR, "Value for hex string '%s' has invalid character '%c'.\n", name, *input);
return -EINVAL;
}
if (++n == 2) {
n = 0;
i++;
}
input++;
}
if (i < output_size) {
ast_log(LOG_ERROR, "Value for hex string '%s' too short, expecting %zu bytes.\n", name, output_size);
return -EINVAL;
}
return PJ_SUCCESS;
}
pj_status_t volte_get_auth(struct volte_states *volte, pjsip_rx_data *rdata, pjsip_hdr_e auth_type, pj_str_t *algo,
uint8_t *rand, uint8_t *autn)
{
pjsip_www_authenticate_hdr *auth_hdr;
uint8_t rand_autn[32];
auth_hdr = pjsip_msg_find_hdr(rdata->msg_info.msg, auth_type, NULL);
if (!auth_hdr || !auth_hdr->challenge.digest.nonce.ptr || !auth_hdr->challenge.digest.algorithm.ptr) {
ast_log(LOG_ERROR, "Authentication header missing or incomplete in REGISTER response.\n");
return -EINVAL;
}
if (!pj_strncmp2(&auth_hdr->challenge.digest.algorithm, "AKAv2-MD5", 9)) {
ast_log(LOG_ERROR, "Authentication algorithm not supported. Please fix!\n");
return -EINVAL;
}
if (!!pj_strncmp2(&auth_hdr->challenge.digest.algorithm, "AKAv1-MD5", 9)) {
ast_log(LOG_ERROR, "Authentication algorithm not supported.\n");
return -EINVAL;
}
*algo = auth_hdr->challenge.digest.algorithm;
ast_base64decode(rand_autn, auth_hdr->challenge.digest.nonce.ptr, sizeof(rand_autn));
memcpy(rand, rand_autn, 16);
memcpy(autn, rand_autn + 16, 16);
return PJ_SUCCESS;
}
pj_status_t volte_send_authrequest(const char *registration_name, pj_str_t *algo, uint8_t *rand, uint8_t *autn)
{
char rand_str[33] = " ", autn_str[33] = " ";
int i;
for (i = 0; i < 16; i++) {
sprintf(rand_str + 2 * i, "%02x", rand[i]);
sprintf(autn_str + 2 * i, "%02x", autn[i]);
}
manager_event(0, "AuthRequest", "Registration: %s\r\nAlgorithm: %.*s\r\nRAND: %s\r\nAUTN: %s\r\n",
registration_name, fmt_strp(algo), rand_str, autn_str);
return PJ_SUCCESS;
}
pj_status_t volte_authenticate(struct volte_states *volte, const char *opc_str, const char *k_str, const char *sqn_str,
uint8_t *rand, uint8_t *autn, uint8_t *out_res, uint8_t *out_ik, uint8_t *out_ck,
uint8_t *out_auts)
{
uint8_t opc[16], k[16], sqn[6];
size_t out_res_len = 8;
int rc;
pj_status_t status;
status = volte_hex_to_octet_string("OPC", opc_str, opc, sizeof(opc));
if (status)
return status;
status = volte_hex_to_octet_string("K", k_str, k, sizeof(k));
if (status)
return status;
status = volte_hex_to_octet_string("SQN", sqn_str, sqn, sizeof(sqn));
if (status)
return status;
rc = milenage_check(opc, k, sqn, rand, autn, out_ik, out_ck, out_res, &out_res_len, out_auts);
if (rc == -1) {
ast_log(LOG_ERROR, "Milenage authentication failed.\n");
return -EINVAL;
}
if (rc == -2) {
/* Tell the caller to perform resync process. */
return -EAGAIN;
}
return PJ_SUCCESS;
}
pj_status_t volte_add_auts(struct volte_states *volte, pjsip_tx_data *tdata, uint8_t *auts)
{
pjsip_authorization_hdr *auth_hdr;
char enc_auts[23] = "\" \"";
pjsip_param *param;
auth_hdr = pjsip_msg_find_hdr_by_name(tdata->msg, &STR_AUTHORIZATION, NULL);
if (!auth_hdr) {
ast_log(LOG_ERROR, "Authorization header not found in TX message. Please fix!\n");
return -EINVAL;
}
/* Encode base64. */
ast_base64encode(enc_auts + 1, auts, 14, 21);
enc_auts[21] = '"';
/* Append 'auts' to Authorization header and replace that header. */
param = PJ_POOL_ALLOC_T(tdata->pool, pjsip_param);
if (!param)
return -ENOMEM;
param->name = STR_AUTS;
pj_strdup2(tdata->pool, &param->value, enc_auts);
pj_list_insert_before(&auth_hdr->credential.digest.other_param, param);
return PJ_SUCCESS;
}

View File

@ -0,0 +1,56 @@
#pragma once
#include "netlink_xfrm.h"
struct volte_states {
pj_sockaddr local_addr_c, remote_addr_s;
pj_sockaddr remote_addr_c, local_addr_s;
uint32_t local_spi_c, remote_spi_s;
uint32_t remote_spi_c, local_spi_s;
pj_bool_t local_sa_c_set, remote_sa_s_set;
pj_bool_t remote_sa_c_set, local_sa_s_set;
pj_bool_t local_sp_c_set, remote_sp_s_set;
pj_bool_t remote_sp_c_set, local_sp_s_set;
pjsip_transport *transport;
pj_sockaddr remote_addr_orig;
char security_server[1024];
};
struct security_server {
pj_str_t q;
pj_str_t prot;
pj_str_t mod;
pj_str_t spi_c;
pj_str_t spi_s;
pj_str_t port_c;
pj_str_t port_s;
pj_str_t alg;
pj_str_t ealg;
};
pj_status_t g_volte_init(void);
void g_volte_exit(void);
void volte_cleanup_xfrm(struct volte_states *volte);
pj_status_t volte_alloc_spi(struct volte_states *volte);
pj_status_t volte_add_sec_agree(pjsip_tx_data *tdata);
pj_status_t volte_init_authorization(pjsip_tx_data *tdata, const char *fromdomain, const char *username);
pj_status_t volte_del_authorization(pjsip_tx_data *tdata);
pj_status_t volte_reset_transport(struct volte_states *volte);
pj_status_t volte_add_security_client(struct volte_states *volte, pjsip_tx_data *tdata, int port_c, int port_s);
pj_status_t volte_set_transport(struct volte_states *volte, pjsip_tx_data *tdata, const pj_str_t *alg,
const pj_str_t *ealg, uint8_t *ik, uint32_t remote_spi_c, uint32_t remote_spi_s,
uint16_t remote_port_c, uint16_t remote_port_s);
pj_status_t volte_get_security_server(struct volte_states *volte, pjsip_rx_data *rdata, struct security_server *sec);
pj_status_t volte_add_security_verify(struct volte_states *volte, pjsip_tx_data *tdata);
pj_status_t volte_hex_to_octet_string(const char *name, const char *input, uint8_t *output, size_t output_size);
pj_status_t volte_get_auth(struct volte_states *volte, pjsip_rx_data *rdata, pjsip_hdr_e auth_type, pj_str_t *algo,
uint8_t *rand, uint8_t *autn);
pj_status_t volte_send_authrequest(const char *registration_name, pj_str_t *algo, uint8_t *rand, uint8_t *autn);
pj_status_t volte_authenticate(struct volte_states *volte, const char *opc_str, const char *k_str, const char *sqn_str,
uint8_t *rand, uint8_t *autn, uint8_t *out_res, uint8_t *out_ik, uint8_t *out_ck,
uint8_t *out_auts);
pj_status_t volte_add_auts(struct volte_states *volte, pjsip_tx_data *tdata, uint8_t *auts);