WIP: Volte support for outgoing SIP registration
This commit is contained in:
parent
82c6c3a9f8
commit
e793f82e2e
|
@ -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@
|
||||
|
|
|
@ -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=":"
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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@
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
*;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -33,7 +33,9 @@
|
|||
#include "asterisk/vector.h"
|
||||
|
||||
pj_str_t supported_digest_algorithms[] = {
|
||||
{ "MD5", 3}
|
||||
{ "MD5", 3},
|
||||
{ "AKAv1-MD5", 9},
|
||||
{ "AKAv2-MD5", 9}
|
||||
};
|
||||
|
||||
/*!
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
|
@ -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, ¶m->value, enc_auts);
|
||||
pj_list_insert_before(&auth_hdr->credential.digest.other_param, param);
|
||||
|
||||
return PJ_SUCCESS;
|
||||
}
|
|
@ -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);
|
Loading…
Reference in New Issue