diff --git a/configure.ac b/configure.ac index a74edfbc8b..73c260f311 100644 --- a/configure.ac +++ b/configure.ac @@ -838,6 +838,10 @@ PKG_CHECK_MODULES([AMRWB], [opencore-amrwb >= 0.1.0 vo-amrwbenc >= 0.1.0],[ AM_CONDITIONAL([HAVE_AMRWB],[true])],[ AC_MSG_RESULT([no]); AM_CONDITIONAL([HAVE_AMRWB],[false])]) +PKG_CHECK_MODULES([STIRSHAKEN], [stirshaken],[ + AM_CONDITIONAL([HAVE_STIRSHAKEN],[true])],[ + AC_MSG_RESULT([no]); AM_CONDITIONAL([HAVE_STIRSHAKEN],[false])]) + AC_CHECK_LIB(apr-1, apr_pool_mutex_set, use_system_apr=yes, use_system_apr=no) AM_CONDITIONAL([SYSTEM_APR],[test "${use_system_apr}" = "yes"]) AC_CHECK_LIB(aprutil-1, apr_queue_pop_timeout, use_system_aprutil=yes, use_system_aprutil=no) diff --git a/src/include/switch_types.h b/src/include/switch_types.h index 58f708729d..fafc508823 100644 --- a/src/include/switch_types.h +++ b/src/include/switch_types.h @@ -2235,7 +2235,12 @@ typedef enum { SWITCH_CAUSE_DECLINE = 616, SWITCH_CAUSE_DOES_NOT_EXIST_ANYWHERE = 617, SWITCH_CAUSE_NOT_ACCEPTABLE = 618, - SWITCH_CAUSE_UNWANTED = 619 + SWITCH_CAUSE_UNWANTED = 619, + SWITCH_CAUSE_NO_IDENTITY = 620, + SWITCH_CAUSE_BAD_IDENTITY_INFO = 621, + SWITCH_CAUSE_UNSUPPORTED_CERTIFICATE = 622, + SWITCH_CAUSE_INVALID_IDENTITY = 623, + SWITCH_CAUSE_STALE_DATE = 624 } switch_call_cause_t; typedef enum { diff --git a/src/mod/endpoints/mod_sofia/Makefile.am b/src/mod/endpoints/mod_sofia/Makefile.am index 0e4346e237..4bd1749175 100644 --- a/src/mod/endpoints/mod_sofia/Makefile.am +++ b/src/mod/endpoints/mod_sofia/Makefile.am @@ -5,21 +5,27 @@ MODNAME=mod_sofia noinst_LTLIBRARIES = libsofiamod.la libsofiamod_la_SOURCES = mod_sofia.c sofia.c sofia_json_api.c sofia_glue.c sofia_presence.c sofia_reg.c sofia_media.c sip-dig.c rtp.c mod_sofia.h libsofiamod_la_LDFLAGS = -static -libsofiamod_la_CFLAGS = $(AM_CFLAGS) -I. $(SOFIA_SIP_CFLAGS) +libsofiamod_la_CFLAGS = $(AM_CFLAGS) -I. $(SOFIA_SIP_CFLAGS) $(STIRSHAKEN_CFLAGS) +if HAVE_STIRSHAKEN +libsofiamod_la_CFLAGS += -DHAVE_STIRSHAKEN +endif mod_LTLIBRARIES = mod_sofia.la mod_sofia_la_SOURCES = mod_sofia_la_LIBADD = $(switch_builddir)/libfreeswitch.la libsofiamod.la -mod_sofia_la_LDFLAGS = -avoid-version -module -no-undefined -shared $(SOFIA_SIP_LIBS) +mod_sofia_la_LDFLAGS = -avoid-version -module -no-undefined -shared $(SOFIA_SIP_LIBS) $(STIRSHAKEN_LIBS) noinst_PROGRAMS = test/test_sofia_funcs test_test_sofia_funcs_SOURCES = test/test_sofia_funcs.c -test_test_sofia_funcs_CFLAGS = $(AM_CFLAGS) $(SOFIA_SIP_CFLAGS) -DSWITCH_TEST_BASE_DIR_FOR_CONF=\"${abs_builddir}/test\" -DSWITCH_TEST_BASE_DIR_OVERRIDE=\"${abs_builddir}/test\" -test_test_sofia_funcs_LDFLAGS = $(AM_LDFLAGS) -avoid-version -no-undefined $(freeswitch_LDFLAGS) $(switch_builddir)/libfreeswitch.la $(CORE_LIBS) $(APR_LIBS) +test_test_sofia_funcs_CFLAGS = $(AM_CFLAGS) $(SOFIA_SIP_CFLAGS) $(STIRSHAKEN_CFLAGS) -DSWITCH_TEST_BASE_DIR_FOR_CONF=\"${abs_builddir}/test\" -DSWITCH_TEST_BASE_DIR_OVERRIDE=\"${abs_builddir}/test\" +if HAVE_STIRSHAKEN +test_test_sofia_funcs_CFLAGS += -DHAVE_STIRSHAKEN +endif +test_test_sofia_funcs_LDFLAGS = $(AM_LDFLAGS) -avoid-version -no-undefined $(freeswitch_LDFLAGS) $(switch_builddir)/libfreeswitch.la $(CORE_LIBS) $(APR_LIBS) $(STIRSHAKEN_LIBS) test_test_sofia_funcs_LDADD = libsofiamod.la $(SOFIA_SIP_LIBS) -TESTS = $(noinst_PROGRAMS) +TESTS = test/test_sofia_funcs.sh if ISMAC mod_sofia_la_LDFLAGS += -framework CoreFoundation -framework SystemConfiguration diff --git a/src/mod/endpoints/mod_sofia/mod_sofia.c b/src/mod/endpoints/mod_sofia/mod_sofia.c index 8c1b7fd325..ce9d520ba8 100644 --- a/src/mod/endpoints/mod_sofia/mod_sofia.c +++ b/src/mod/endpoints/mod_sofia/mod_sofia.c @@ -1,6 +1,6 @@ /* * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application - * Copyright (C) 2005-2014, Anthony Minessale II + * Copyright (C) 2005-2021, Anthony Minessale II * * Version: MPL 1.1 * @@ -41,6 +41,10 @@ #include "mod_sofia.h" #include "sofia-sip/sip_extra.h" +#if HAVE_STIRSHAKEN +#include +#endif + SWITCH_MODULE_LOAD_FUNCTION(mod_sofia_load); SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_sofia_shutdown); SWITCH_MODULE_DEFINITION(mod_sofia, mod_sofia_load, mod_sofia_shutdown, NULL); @@ -351,6 +355,17 @@ static int hangup_cause_to_sip(switch_call_cause_t cause) return 606; case SWITCH_CAUSE_UNWANTED: return 607; + /* STIR/SHAKEN */ + case SWITCH_CAUSE_NO_IDENTITY: + return 428; + case SWITCH_CAUSE_BAD_IDENTITY_INFO: + return 429; + case SWITCH_CAUSE_UNSUPPORTED_CERTIFICATE: + return 437; + case SWITCH_CAUSE_INVALID_IDENTITY: + return 438; + case SWITCH_CAUSE_STALE_DATE: + return 403; default: return 480; } @@ -6109,6 +6124,409 @@ SWITCH_STANDARD_APP(sofia_sla_function) switch_ivr_eavesdrop_session(session, data, NULL, ED_MUX_READ | ED_MUX_WRITE | ED_COPY_DISPLAY); } +#if HAVE_STIRSHAKEN +static stir_shaken_as_t *sofia_stir_shaken_as = NULL; +static stir_shaken_vs_t *sofia_stir_shaken_vs = NULL; + +static switch_status_t sofia_stir_shaken_vs_create(stir_shaken_context_t *context) +{ + sofia_stir_shaken_vs = stir_shaken_vs_create(context); + if (!sofia_stir_shaken_vs) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to create Identity verification service!\n"); + return SWITCH_STATUS_FALSE; + } + if (mod_sofia_globals.stir_shaken_vs_ca_dir) { + stir_shaken_vs_load_ca_dir(context, sofia_stir_shaken_vs, mod_sofia_globals.stir_shaken_vs_ca_dir); + } + stir_shaken_vs_set_x509_cert_path_check(context, sofia_stir_shaken_vs, mod_sofia_globals.stir_shaken_vs_cert_path_check); + stir_shaken_vs_set_connect_timeout(context, sofia_stir_shaken_vs, 3); + //stir_shaken_vs_set_callback(context, sofia_stir_shaken_vs, shaken_callback); + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t sofia_stir_shaken_as_create(stir_shaken_context_t *context) +{ + if (mod_sofia_globals.stir_shaken_as_key && mod_sofia_globals.stir_shaken_as_url) { + sofia_stir_shaken_as = stir_shaken_as_create(context); + if (!sofia_stir_shaken_as) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to create Identity authentication service!\n"); + return SWITCH_STATUS_FALSE; + } + if (stir_shaken_as_load_private_key(context, sofia_stir_shaken_as, mod_sofia_globals.stir_shaken_as_key) != STIR_SHAKEN_STATUS_OK) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to load key for Identity authentication service: %s", mod_sofia_globals.stir_shaken_as_key); + stir_shaken_as_destroy(&sofia_stir_shaken_as); + return SWITCH_STATUS_FALSE; + } + } + return SWITCH_STATUS_SUCCESS; +} +#endif + +static void sofia_stir_shaken_create_services(void) +{ +#if HAVE_STIRSHAKEN + stir_shaken_context_t context = { 0 }; + if (stir_shaken_init(&context, STIR_SHAKEN_LOGLEVEL_NOTHING) != STIR_SHAKEN_STATUS_OK) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Failed to initialize stirshaken library!\n"); + return; + } + sofia_stir_shaken_vs_create(&context); + sofia_stir_shaken_as_create(&context); +#endif +} + +static void sofia_stir_shaken_destroy_services(void) +{ +#if HAVE_STIRSHAKEN + stir_shaken_vs_destroy(&sofia_stir_shaken_vs); + stir_shaken_as_destroy(&sofia_stir_shaken_as); + stir_shaken_deinit(); +#endif +} + +#if HAVE_STIRSHAKEN +static char *canonicalize_phone_number(const char *number) +{ + // remove all characters except for digits, *, and # from the phone number + // TODO determine if dial number and remove dial codes or add country code + char *canonicalized_number = strdup(number ? number : ""); + size_t i = 0, j = 0; + size_t number_len = strlen(canonicalized_number); + for (i = 0; i < number_len; i++) { + if (isdigit(canonicalized_number[i]) || canonicalized_number[i] == '#' || canonicalized_number[i] == '*') { + canonicalized_number[j] = canonicalized_number[i]; + j++; + } + } + canonicalized_number[j] = '\0'; + return canonicalized_number; +} + +static switch_status_t sofia_stir_shaken_validate_passport_claims(switch_core_session_t *session, long iat, const char *orig, int orig_is_tn, const char *dest, int dest_is_tn) +{ + switch_channel_t *channel = switch_core_session_get_channel(session); + const char *from = NULL; + const char *to = NULL; + char *canonicalized_from = NULL; + char *canonicalized_to = NULL; + switch_status_t status = SWITCH_STATUS_FALSE; + switch_time_t now = switch_epoch_time_now(NULL); + + if (iat > now) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "PASSporT iat is in the future\n"); + return SWITCH_STATUS_FALSE; + } else if (now - iat > 60) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "PASSporT iat is too old\n"); + return SWITCH_STATUS_FALSE; + } + + if (mod_sofia_globals.stir_shaken_vs_require_date || switch_true(switch_channel_get_variable(channel, "sip_stir_shaken_vs_require_date"))) { + const char *sip_epoch_time_var = switch_channel_get_variable(channel, "sip_date_epoch_time"); + switch_time_t sip_epoch_time; + + if (zstr(sip_epoch_time_var)) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "Missing required SIP Date\n"); + return SWITCH_STATUS_FALSE; + } + sip_epoch_time = strtol(sip_epoch_time_var, NULL, 10); + if (sip_epoch_time > now) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "SIP Date %s is in the future\n", sip_epoch_time_var); + return SWITCH_STATUS_FALSE; + } + if (now - sip_epoch_time > 60) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "SIP Date %s is too old\n", sip_epoch_time_var); + return SWITCH_STATUS_FALSE; + } + if ((iat > sip_epoch_time && iat - sip_epoch_time > 60) || (iat < sip_epoch_time && sip_epoch_time - iat > 60)) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "SIP Date %s is too far from PASSporT iat %ld\n", sip_epoch_time_var, iat); + return SWITCH_STATUS_FALSE; + } + // Date is within 60 seconds of now and within 60 seconds of iat + } + + if (orig_is_tn) { + from = switch_channel_get_variable(channel, "sip_from_user"); + from = canonicalized_from = canonicalize_phone_number(from); + } else { + from = switch_channel_get_variable(channel, "sip_from_uri"); + } + if (dest_is_tn) { + to = switch_channel_get_variable(channel, "sip_to_user"); + to = canonicalized_to = canonicalize_phone_number(to); + } else { + to = switch_channel_get_variable(channel, "sip_to_uri"); + } + + if (zstr(from) || zstr(to) || zstr(orig) || zstr(dest)) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "Missing data to verify SIP From/To matches PASSporT claims. From=%s, To=%s, orig=%s, dest=%s\n", from, to, orig, dest); + status = SWITCH_STATUS_FALSE; + } else if (strcmp(orig, from) || strcmp(dest, to)) { + status = SWITCH_STATUS_FALSE; + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "SIP From/To does not match PASSporT claims. From=%s, To=%s, orig=%s, dest=%s\n", from, to, orig, dest); + } else { + status = SWITCH_STATUS_SUCCESS; + } + switch_safe_free(canonicalized_from); + switch_safe_free(canonicalized_to); + return status; +} + +/** + * Returns first dest if found. Must be freed by caller. + */ +static char* sofia_stir_shaken_passport_get_dest(stir_shaken_passport_t *passport, int *is_tn) +{ + char *id = NULL; + char *dest = NULL; + int tn_form = 0; + int id_int = 0; + cJSON *item = NULL; + cJSON *destjson = NULL; + stir_shaken_context_t ss = { 0 }; + + if (!passport) return NULL; + + dest = stir_shaken_passport_get_grants_json(&ss, passport, "dest"); + if (!dest) { + return NULL; + } + + destjson = cJSON_Parse(dest); + if (!destjson) { + free(dest); + return NULL; + } + + if ((item = cJSON_GetObjectItem(destjson, "tn"))) { + tn_form = 1; + } else if ((item = cJSON_GetObjectItem(destjson, "uri"))) { + tn_form = 0; + } else { + cJSON_Delete(destjson); + free(dest); + return NULL; + } + + if (cJSON_IsArray(item)) { + item = cJSON_GetArrayItem(item, 0); + if (!item) { + cJSON_Delete(destjson); + free(dest); + return NULL; + } + } else { + item = destjson; + } + + if (cJSON_IsString(item)) { + id = strdup(item->valuestring); + } else if (cJSON_IsNumber(item)) { + id_int = item->valueint; + id = malloc(20); + if (!id) { + cJSON_Delete(destjson); + free(dest); + return NULL; + } + snprintf(id, 20, "%d", id_int); + } else { + cJSON_Delete(destjson); + free(dest); + return NULL; + } + + if (is_tn) *is_tn = tn_form; + cJSON_Delete(destjson); + free(dest); + return id; +} + + +#endif + +// TODO Date header must be present +// Date header must be < (expiration policy) age +// Date header must be within 1 minute of iat + + +/* Check signature in Identity header and save result to sip_verstat */ +SWITCH_STANDARD_APP(sofia_stir_shaken_vs_function) +{ + switch_channel_t *channel = switch_core_session_get_channel(session); +#if HAVE_STIRSHAKEN + stir_shaken_status_t verify_signature_status = STIR_SHAKEN_STATUS_FALSE; + stir_shaken_context_t verify_signature_context = { 0 }; + stir_shaken_status_t validate_passport_status = STIR_SHAKEN_STATUS_FALSE; + stir_shaken_context_t validate_passport_context = { 0 }; + stir_shaken_context_t get_grant_context = { 0 }; + stir_shaken_passport_t *passport = NULL; + stir_shaken_cert_t *cert = NULL; + stir_shaken_error_t stir_error = { 0 }; + switch_status_t claim_status = SWITCH_STATUS_FALSE; + const char *identity_header = switch_channel_get_variable(channel, "sip_h_identity"); + const char *attestation = NULL; + int orig_is_tn = 0; + switch_bool_t hangup_on_fail = switch_true(switch_channel_get_variable(channel, "sip_stir_shaken_vs_hangup_on_fail")); + + // TODO: compact Identity header is not supported - this will require construction of PASSporT from SIP headers in order to check signature + + if (zstr(identity_header)) { + // Nothing to do + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "No-TN-Validation: no SIP Identity\n"); + switch_channel_set_variable(channel, "sip_verstat_detailed", "No-TN-Validation"); + switch_channel_set_variable(channel, "sip_verstat", "No-TN-Validation"); + if (hangup_on_fail) { + switch_channel_hangup(channel, SWITCH_CAUSE_NO_IDENTITY); + } + goto done; + } + + // verify the JWT signature in the SIP Identity header + verify_signature_status = stir_shaken_vs_sih_verify(&verify_signature_context, sofia_stir_shaken_vs, identity_header, &cert, &passport); + if (verify_signature_status != STIR_SHAKEN_STATUS_OK) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "PASSporT failed signature verification: %s\n", stir_shaken_get_error(&verify_signature_context, &stir_error)); + if (hangup_on_fail) { + switch_channel_hangup(channel, SWITCH_CAUSE_INVALID_IDENTITY); + goto done; + } + } else { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "PASSporT passed signature verification\n"); + } + + if (passport) { + // validate the PASSporT is not expired + int timeout = 60; + const char *timeout_str = switch_channel_get_variable(channel, "sip_stir_shaken_vs_max_age"); + if (timeout_str && switch_is_number(timeout_str)) { + int new_timeout = atoi(timeout_str); + if (new_timeout > 0) { + timeout = new_timeout; + } + } + validate_passport_status = stir_shaken_passport_validate_iat_against_freshness(&validate_passport_context, passport, timeout); + if (validate_passport_status != STIR_SHAKEN_STATUS_OK) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "PASSporT failed stale check: %s\n", stir_shaken_get_error(&validate_passport_context, &stir_error)); + if (hangup_on_fail) { + switch_channel_hangup(channel, SWITCH_CAUSE_STALE_DATE); + goto done; + } + } else { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "PASSporT passed stale check\n"); + } + + // validate the required PASSporT headers and grants are set + validate_passport_status = stir_shaken_passport_validate_headers_and_grants(&validate_passport_context, passport); + if (validate_passport_status != STIR_SHAKEN_STATUS_OK) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "PASSporT failed header and grant validation: %s\n", stir_shaken_get_error(&validate_passport_context, &stir_error)); + if (hangup_on_fail) { + switch_channel_hangup(channel, SWITCH_CAUSE_INVALID_IDENTITY); + if (validate_passport_status == STIR_SHAKEN_STATUS_OK && verify_signature_status == STIR_SHAKEN_STATUS_OK) { + switch_channel_hangup(channel, SWITCH_CAUSE_INCOMING_CALL_BARRED); + } else { + switch_channel_hangup(channel, SWITCH_CAUSE_INVALID_IDENTITY); + } + goto done; + } + } else { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "PASSporT passed header and grant validation\n"); + } + } + + if (passport) { + // validate the PASSporT claims match the SIP headers + stir_shaken_context_t validate_claims_context = { 0 }; + int dest_is_tn = 0; + char *orig = stir_shaken_passport_get_identity(&validate_claims_context, passport, &orig_is_tn); + char *dest = sofia_stir_shaken_passport_get_dest(passport, &dest_is_tn); // TODO libstirshaken should provide helper for 'dest' values + long iat = stir_shaken_passport_get_grant_int(&validate_claims_context, passport, "iat"); + claim_status = sofia_stir_shaken_validate_passport_claims(session, iat, orig, orig_is_tn, dest, dest_is_tn); + if (claim_status != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "PASSporT claims do not match SIP request\n"); + if (hangup_on_fail) { + switch_channel_hangup(channel, SWITCH_CAUSE_INVALID_IDENTITY); + switch_safe_free(orig); + switch_safe_free(dest); + goto done; + } + } else { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "PASSporT claims match SIP request\n"); + } + switch_safe_free(orig); + switch_safe_free(dest); + } + + attestation = stir_shaken_passport_get_grant(&get_grant_context, passport, "attest"); + + if (!zstr(attestation) && verify_signature_status == STIR_SHAKEN_STATUS_OK && validate_passport_status == STIR_SHAKEN_STATUS_OK && claim_status == SWITCH_STATUS_SUCCESS) { + if (orig_is_tn) { + switch_channel_set_variable_printf(channel, "sip_verstat_detailed", "TN-Validation-Passed-%s", attestation); + } else { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "No-TN-Validation: PASSporT orig is not a telephone number\n"); + switch_channel_set_variable(channel, "sip_verstat", "No-TN-Validation"); + } + if (orig_is_tn && !strcmp(attestation, "A")) { + // Signature is valid and call has "A" attestation + switch_channel_set_variable(channel, "sip_verstat", "TN-Validation-Passed"); + } else { + // Signature is valid and call has "B" or "C" attestation or is not from a phone number + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "No-TN-Validation: PASSporT only has \"%s\" attestation\n", attestation); + switch_channel_set_variable(channel, "sip_verstat", "No-TN-Validation"); + } + } else if (!passport || !cert || zstr(attestation) || verify_signature_status == STIR_SHAKEN_STATUS_OK) { + // failed to get cert / bad passport / no attestation / claims don't match SIP + switch_channel_set_variable(channel, "sip_verstat_detailed", "No-TN-Validation"); + switch_channel_set_variable(channel, "sip_verstat", "No-TN-Validation"); + } else { + // bad signature + switch_channel_set_variable_printf(channel, "sip_verstat_detailed", "TN-Validation-Failed-%s", attestation); + switch_channel_set_variable(channel, "sip_verstat", "TN-Validation-Failed"); + } + + +done: + stir_shaken_passport_destroy(&passport); + stir_shaken_cert_destroy(&cert); + +#else + switch_channel_set_variable(channel, "sip_verstat_detailed", "No-TN-Validation"); + switch_channel_set_variable(channel, "sip_verstat", "No-TN-Validation"); +#endif + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "verstat=%s, verstat_detailed=%s\n", switch_channel_get_variable(channel, "sip_verstat"), switch_channel_get_variable(channel, "sip_verstat_detailed")); +} + +/* This assumes TN attestation for orig and dest only */ +char *sofia_stir_shaken_as_create_identity_header(switch_core_session_t *session, const char *attest, const char *orig, const char *dest) +{ +#if HAVE_STIRSHAKEN + stir_shaken_context_t as_context = { 0 }; + stir_shaken_passport_params_t passport_params = { 0 }; + char *canonical_desttn = NULL; + char *canonical_origtn = NULL; + char *passport = NULL; + + if (zstr(attest) || zstr(orig) || zstr(dest) || !mod_sofia_globals.stir_shaken_as_url) { + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "Missing required parameter to create PASSporT\n"); + return NULL; + } + + passport_params.attest = attest; + passport_params.x5u = mod_sofia_globals.stir_shaken_as_url; + passport_params.desttn_key = "tn"; + passport_params.desttn_val = canonical_desttn = canonicalize_phone_number(dest); + passport_params.iat = switch_epoch_time_now(NULL); + passport_params.origtn_key = "tn"; + passport_params.origtn_val = canonical_origtn = canonicalize_phone_number(orig); + passport_params.origid = switch_core_session_get_uuid(session); + + passport = stir_shaken_as_authenticate_to_sih(&as_context, sofia_stir_shaken_as, &passport_params, NULL); + switch_safe_free(canonical_desttn); + switch_safe_free(canonical_origtn); + return passport; +#else + return NULL; +#endif +} + SWITCH_MODULE_LOAD_FUNCTION(mod_sofia_load) { @@ -6367,6 +6785,8 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_sofia_load) SWITCH_ADD_APP(app_interface, "sofia_sla", "private sofia sla function", "private sofia sla function", sofia_sla_function, "", SAF_NONE); + SWITCH_ADD_APP(app_interface, "sofia_stir_shaken_vs", "Verify SIP Identity header and store result in sip_verstat channel variable", + "Verify SIP Identity header and store result in sip_verstat channel variable", sofia_stir_shaken_vs_function, "", SAF_SUPPORT_NOMEDIA); SWITCH_ADD_API(api_interface, "sofia", "Sofia Controls", sofia_function, " "); SWITCH_ADD_API(api_interface, "sofia_gateway_data", "Get data from a sofia gateway", sofia_gateway_data_function, " [ivar|ovar|var] "); @@ -6410,6 +6830,8 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_sofia_load) crtp_init(*module_interface); + sofia_stir_shaken_create_services(); + /* indicate that the module should continue to be loaded */ return SWITCH_STATUS_SUCCESS; @@ -6496,6 +6918,8 @@ void mod_sofia_shutdown_cleanup() { switch_core_hash_destroy(&mod_sofia_globals.profile_hash); switch_core_hash_destroy(&mod_sofia_globals.gateway_hash); switch_mutex_unlock(mod_sofia_globals.hash_mutex); + + sofia_stir_shaken_destroy_services(); } SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_sofia_shutdown) diff --git a/src/mod/endpoints/mod_sofia/mod_sofia.h b/src/mod/endpoints/mod_sofia/mod_sofia.h index 3c2d02a5a7..e91d849913 100644 --- a/src/mod/endpoints/mod_sofia/mod_sofia.h +++ b/src/mod/endpoints/mod_sofia/mod_sofia.h @@ -407,6 +407,11 @@ struct mod_sofia_globals { time_t presence_epoch; int presence_year; int abort_on_empty_external_ip; + const char *stir_shaken_as_key; + const char *stir_shaken_as_url; + const char *stir_shaken_vs_ca_dir; + int stir_shaken_vs_cert_path_check; + int stir_shaken_vs_require_date; }; extern struct mod_sofia_globals mod_sofia_globals; @@ -1274,6 +1279,8 @@ void sofia_glue_clear_soa(switch_core_session_t *session, switch_bool_t partner) sofia_auth_algs_t sofia_alg_str2id(char *algorithm, switch_bool_t permissive); switch_status_t sofia_make_digest(sofia_auth_algs_t use_alg, char **digest, const void *input, unsigned int *outputlen); +char *sofia_stir_shaken_as_create_identity_header(switch_core_session_t *session, const char *attest, const char *orig, const char *dest); + /* For Emacs: * Local Variables: * mode:c diff --git a/src/mod/endpoints/mod_sofia/sofia.c b/src/mod/endpoints/mod_sofia/sofia.c index dbd9f94343..dd46fb4c0f 100644 --- a/src/mod/endpoints/mod_sofia/sofia.c +++ b/src/mod/endpoints/mod_sofia/sofia.c @@ -4547,6 +4547,19 @@ switch_status_t config_sofia(sofia_config_t reload, char *profile_name) } } else if (!strcasecmp(var, "capture-server")) { mod_sofia_globals.capture_server = switch_core_strdup(mod_sofia_globals.pool, val); + } else if (!strcasecmp(var, "stir-shaken-as-key")) { + /* The private key to authenticate SIP Identity when sip_identity_attest is set */ + mod_sofia_globals.stir_shaken_as_key = switch_core_strdup(mod_sofia_globals.pool, val); + } else if (!strcasecmp(var, "stir-shaken-as-url")) { + /* The x5u URL to advertise when sip_identity_attest is set */ + mod_sofia_globals.stir_shaken_as_url = switch_core_strdup(mod_sofia_globals.pool, val); + } else if (!strcasecmp(var, "stir-shaken-vs-ca-dir")) { + /* The dir that contains the trusted CA root certs. */ + mod_sofia_globals.stir_shaken_vs_ca_dir = switch_core_strdup(mod_sofia_globals.pool, val); + } else if (!strcasecmp(var, "stir-shaken-vs-cert-path-check")) { + mod_sofia_globals.stir_shaken_vs_cert_path_check = switch_true(val); + } else if (!strcasecmp(var, "stir-shaken-vs-require-date")) { + mod_sofia_globals.stir_shaken_vs_require_date = switch_true(val); } } } @@ -11426,6 +11439,13 @@ void sofia_handle_sip_i_invite(switch_core_session_t *session, nua_t *nua, sofia if (sip->sip_identity && sip->sip_identity->id_value) { switch_channel_set_variable(channel, "sip_h_identity", sip->sip_identity->id_value); } + if (sip->sip_date && sip->sip_date->d_time > 0) { + // This INVITE has a SIP Date header. + // sofia-sip stores the Date header value in sip_date->d_time as seconds since January 1, 1900 0:00:00. + // Unix epoch time is seconds since January 1, 1970 0:00:00, making d_time larger by 2208988800. + // Convert to Unix epoch time and save it. + switch_channel_set_variable_printf(channel, "sip_date_epoch_time", "%ld", sip->sip_date->d_time - 2208988800); + } /* Loop thru unknown Headers Here so we can do something with them */ for (un = sip->sip_unknown; un; un = un->un_next) { diff --git a/src/mod/endpoints/mod_sofia/sofia_glue.c b/src/mod/endpoints/mod_sofia/sofia_glue.c index 0c0035412f..10282383d2 100644 --- a/src/mod/endpoints/mod_sofia/sofia_glue.c +++ b/src/mod/endpoints/mod_sofia/sofia_glue.c @@ -1064,6 +1064,9 @@ switch_status_t sofia_glue_do_invite(switch_core_session_t *session) uint8_t is_t38 = 0; const char *hold_char = "*"; const char *session_id_header = sofia_glue_session_id_header(session, tech_pvt->profile); + const char *stir_shaken_attest = NULL; + char *identity_to_free = NULL; + const char *date = NULL; if (sofia_test_flag(tech_pvt, TFLAG_SIP_HOLD_INACTIVE) || @@ -1123,7 +1126,20 @@ switch_status_t sofia_glue_do_invite(switch_core_session_t *session) alert_info = switch_core_session_sprintf(tech_pvt->session, "Alert-Info: %s", alertbuf); } - identity = switch_channel_get_variable(channel, "sip_h_identity"); + if ((stir_shaken_attest = switch_channel_get_variable(tech_pvt->channel, "sip_stir_shaken_attest"))) { + char date_buf[80] = ""; + char *dest = caller_profile->destination_number; + check_decode(dest, session); + switch_rfc822_date(date_buf, switch_micro_time_now()); + date = switch_core_session_strdup(tech_pvt->session, date_buf); + identity = identity_to_free = sofia_stir_shaken_as_create_identity_header(tech_pvt->session, stir_shaken_attest, cid_num, dest); + } + if (!identity) { + identity = switch_channel_get_variable(channel, "sip_h_identity"); + } + if (!date) { + date = switch_channel_get_variable(channel, "sip_h_date"); + } max_forwards = switch_channel_get_variable(channel, SWITCH_MAX_FORWARDS_VARIABLE); @@ -1658,6 +1674,7 @@ switch_status_t sofia_glue_do_invite(switch_core_session_t *session) TAG_IF(!zstr(tech_pvt->asserted_id), SIPTAG_P_ASSERTED_IDENTITY_STR(tech_pvt->asserted_id)), TAG_IF(!zstr(tech_pvt->privacy), SIPTAG_PRIVACY_STR(tech_pvt->privacy)), TAG_IF(!zstr(identity), SIPTAG_IDENTITY_STR(identity)), + TAG_IF(!zstr(date), SIPTAG_DATE_STR(date)), TAG_IF(!zstr(alert_info), SIPTAG_HEADER_STR(alert_info)), TAG_IF(!zstr(extra_headers), SIPTAG_HEADER_STR(extra_headers)), TAG_IF(sofia_test_pflag(tech_pvt->profile, PFLAG_PASS_CALLEE_ID), SIPTAG_HEADER_STR("X-FS-Support: " FREESWITCH_SUPPORT)), @@ -1725,6 +1742,8 @@ end: sofia_glue_free_destination(dst); } + switch_safe_free(identity_to_free); + return status; } @@ -1832,6 +1851,14 @@ switch_call_cause_t sofia_glue_sip_cause_to_freeswitch(int status) return SWITCH_CAUSE_EXCHANGE_ROUTING_ERROR; case 487: return SWITCH_CAUSE_ORIGINATOR_CANCEL; + case 428: + return SWITCH_CAUSE_NO_IDENTITY; + case 429: + return SWITCH_CAUSE_BAD_IDENTITY_INFO; + case 437: + return SWITCH_CAUSE_UNSUPPORTED_CERTIFICATE; + case 438: + return SWITCH_CAUSE_INVALID_IDENTITY; default: return SWITCH_CAUSE_NORMAL_UNSPECIFIED; } diff --git a/src/mod/endpoints/mod_sofia/test/conf/freeswitch.xml b/src/mod/endpoints/mod_sofia/test/conf/freeswitch.xml index c654a84ebc..d58f45b983 100644 --- a/src/mod/endpoints/mod_sofia/test/conf/freeswitch.xml +++ b/src/mod/endpoints/mod_sofia/test/conf/freeswitch.xml @@ -31,6 +31,11 @@ + + + + + @@ -97,6 +102,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/mod/endpoints/mod_sofia/test/stir-shaken/priv.pem b/src/mod/endpoints/mod_sofia/test/stir-shaken/priv.pem new file mode 100644 index 0000000000..0e812f09b6 --- /dev/null +++ b/src/mod/endpoints/mod_sofia/test/stir-shaken/priv.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIKigooZBuy0XvaeIEFPkuvOehEbhrqFIKdeBZAJaZIawoAoGCCqGSM49 +AwEHoUQDQgAEnqiYijyOLEo9hJ/x2oVYIQT12XL3YREF2XS+cWmabEtjJpfAPmS+ +1f+fg3APWD+owNyaDV54r3YTHqkvTK/5mA== +-----END EC PRIVATE KEY----- diff --git a/src/mod/endpoints/mod_sofia/test/stir-shaken/pub.pem b/src/mod/endpoints/mod_sofia/test/stir-shaken/pub.pem new file mode 100644 index 0000000000..96c90cbeeb --- /dev/null +++ b/src/mod/endpoints/mod_sofia/test/stir-shaken/pub.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEnqiYijyOLEo9hJ/x2oVYIQT12XL3 +YREF2XS+cWmabEtjJpfAPmS+1f+fg3APWD+owNyaDV54r3YTHqkvTK/5mA== +-----END PUBLIC KEY----- diff --git a/src/mod/endpoints/mod_sofia/test/stir-shaken/www/cert.pem b/src/mod/endpoints/mod_sofia/test/stir-shaken/www/cert.pem new file mode 100644 index 0000000000..f364baa301 --- /dev/null +++ b/src/mod/endpoints/mod_sofia/test/stir-shaken/www/cert.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB6DCCAY6gAwIBAgIBATAKBggqhkjOPQQDAjAlMQswCQYDVQQGEwJVUzEWMBQG +A1UEAwwNbGlic3RpcnNoYWtlbjAeFw0yMTA0MTMwMTA1MDBaFw0zMTA0MTEwMTA1 +MDBaMCUxCzAJBgNVBAYTAlVTMRYwFAYDVQQDDA1saWJzdGlyc2hha2VuMFkwEwYH +KoZIzj0CAQYIKoZIzj0DAQcDQgAEnqiYijyOLEo9hJ/x2oVYIQT12XL3YREF2XS+ +cWmabEtjJpfAPmS+1f+fg3APWD+owNyaDV54r3YTHqkvTK/5mKOBrjCBqzAdBgNV +HQ4EFgQUdCtIqcHHdpzTxT0uZ3BUo7f+IhYwHwYDVR0jBBgwFoAUdCtIqcHHdpzT +xT0uZ3BUo7f+IhYwNQYJYIZIAYb4QgENBCgWJkFsd2F5cyBsb29rIG9uIHRoZSBi +cmlnaHQgc2lkZSBvZiBsaWZlMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMBEGCWCGSAGG+EIBAQQEAwICBDAKBggqhkjOPQQDAgNIADBFAiAN2YS+x4Nb +fWAwiLKlQV141PkFQ7KbjVYeHHjPO7u1dgIhAI7N+vW2BdFzhH65xcHn/nWv1HXe +5NfoHbhDS+cC7Bet +-----END CERTIFICATE----- diff --git a/src/mod/endpoints/mod_sofia/test/stir-shaken/www/pub.pem b/src/mod/endpoints/mod_sofia/test/stir-shaken/www/pub.pem new file mode 100644 index 0000000000..f364baa301 --- /dev/null +++ b/src/mod/endpoints/mod_sofia/test/stir-shaken/www/pub.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB6DCCAY6gAwIBAgIBATAKBggqhkjOPQQDAjAlMQswCQYDVQQGEwJVUzEWMBQG +A1UEAwwNbGlic3RpcnNoYWtlbjAeFw0yMTA0MTMwMTA1MDBaFw0zMTA0MTEwMTA1 +MDBaMCUxCzAJBgNVBAYTAlVTMRYwFAYDVQQDDA1saWJzdGlyc2hha2VuMFkwEwYH +KoZIzj0CAQYIKoZIzj0DAQcDQgAEnqiYijyOLEo9hJ/x2oVYIQT12XL3YREF2XS+ +cWmabEtjJpfAPmS+1f+fg3APWD+owNyaDV54r3YTHqkvTK/5mKOBrjCBqzAdBgNV +HQ4EFgQUdCtIqcHHdpzTxT0uZ3BUo7f+IhYwHwYDVR0jBBgwFoAUdCtIqcHHdpzT +xT0uZ3BUo7f+IhYwNQYJYIZIAYb4QgENBCgWJkFsd2F5cyBsb29rIG9uIHRoZSBi +cmlnaHQgc2lkZSBvZiBsaWZlMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMBEGCWCGSAGG+EIBAQQEAwICBDAKBggqhkjOPQQDAgNIADBFAiAN2YS+x4Nb +fWAwiLKlQV141PkFQ7KbjVYeHHjPO7u1dgIhAI7N+vW2BdFzhH65xcHn/nWv1HXe +5NfoHbhDS+cC7Bet +-----END CERTIFICATE----- diff --git a/src/mod/endpoints/mod_sofia/test/test_sofia_funcs.c b/src/mod/endpoints/mod_sofia/test/test_sofia_funcs.c index 0c03d232d1..e769057363 100644 --- a/src/mod/endpoints/mod_sofia/test/test_sofia_funcs.c +++ b/src/mod/endpoints/mod_sofia/test/test_sofia_funcs.c @@ -112,6 +112,150 @@ FST_TEST_BEGIN(originate_test) } FST_TEST_END() +FST_TEST_BEGIN(sofia_verify_identity_test_no_identity) +{ + switch_core_session_t *session = NULL; + switch_channel_t *channel = NULL; + switch_status_t status; + switch_call_cause_t cause; + const char *local_ip_v4 = switch_core_get_variable("local_ip_v4"); + status = switch_ivr_originate(NULL, &session, &cause, switch_core_sprintf(fst_pool, "{ignore_early_media=true}sofia/internal/verifyidentity@%s:53060", local_ip_v4), 2, NULL, NULL, NULL, NULL, NULL, SOF_NONE, NULL, NULL); + fst_check(status != SWITCH_STATUS_SUCCESS); + fst_check(cause == SWITCH_CAUSE_NO_IDENTITY); + if (session) { + channel = switch_core_session_get_channel(session); + switch_channel_hangup(channel, SWITCH_CAUSE_NORMAL_CLEARING); + switch_core_session_rwunlock(session); + switch_sleep(1 * 1000 * 1000); + } +} +FST_TEST_END() + +FST_TEST_BEGIN(sofia_verify_identity_test_bad_identity) +{ + switch_core_session_t *session = NULL; + switch_channel_t *channel = NULL; + switch_status_t status; + switch_call_cause_t cause; + const char *local_ip_v4 = switch_core_get_variable("local_ip_v4"); + status = switch_ivr_originate(NULL, &session, &cause, switch_core_sprintf(fst_pool, "{ignore_early_media=true,sip_h_identity=foo;info=bar}sofia/internal/verifyidentity@%s:53060", local_ip_v4), 2, NULL, NULL, NULL, NULL, NULL, SOF_NONE, NULL, NULL); + fst_check(status != SWITCH_STATUS_SUCCESS); + fst_check(cause == SWITCH_CAUSE_INVALID_IDENTITY); + if (session) { + channel = switch_core_session_get_channel(session); + switch_channel_hangup(channel, SWITCH_CAUSE_NORMAL_CLEARING); + switch_core_session_rwunlock(session); + switch_sleep(1 * 1000 * 1000); + } +} +FST_TEST_END() + +FST_TEST_BEGIN(sofia_verify_identity_test_valid_identity_no_cert_available) +{ + switch_core_session_t *session = NULL; + switch_channel_t *channel = NULL; + switch_status_t status; + switch_call_cause_t cause; + const char *local_ip_v4 = switch_core_get_variable("local_ip_v4"); + status = switch_ivr_originate(NULL, &session, &cause, switch_core_sprintf(fst_pool, "{origination_caller_id_number=+15551231234,ignore_early_media=true,sip_h_identity=eyJhbGciOiJFUzI1NiIsInBwdCI6InNoYWtlbiIsInR5cCI6InBhc3Nwb3J0IiwieDV1IjoiaHR0cDovLzEyNy4wLjAuMS80MDQucGVtIn0.eyJhdHRlc3QiOiJBIiwiZGVzdCI6eyJ0biI6WyIxNTU1MzIxNDMyMSJdfSwiaWF0IjoxNjE4Mjc5OTYzLCJvcmlnIjp7InRuIjoiMTU1NTEyMzEyMzQifSwib3JpZ2lkIjoiMTMxMzEzMTMifQ.Cm34sISkFWYB6ohtjjJEO71Hyz4TQ5qrTDyYmCXBj-ni5Fe7IbNjmMyvY_lD_Go0u2csWQNe8n03fHSO7Z7nNw;info=;alg=ES256;ppt=shaken}sofia/internal/+15553214321@%s:53060", local_ip_v4), 2, NULL, NULL, NULL, NULL, NULL, SOF_NONE, NULL, NULL); + fst_check(status != SWITCH_STATUS_SUCCESS); + fst_check(cause == SWITCH_CAUSE_INVALID_IDENTITY); + if (session) { + channel = switch_core_session_get_channel(session); + switch_channel_hangup(channel, SWITCH_CAUSE_NORMAL_CLEARING); + switch_core_session_rwunlock(session); + switch_sleep(1 * 1000 * 1000); + } +} +FST_TEST_END() + +FST_TEST_BEGIN(sofia_auth_identity_test_attest_a) +{ + switch_core_session_t *session = NULL; + switch_channel_t *channel = NULL; + switch_status_t status; + switch_call_cause_t cause; + const char *local_ip_v4 = switch_core_get_variable("local_ip_v4"); + status = switch_ivr_originate(NULL, &session, &cause, switch_core_sprintf(fst_pool, "{origination_caller_id_number=+15551231234,ignore_early_media=true,sip_stir_shaken_attest=A}sofia/internal/+15553214322@%s:53060", local_ip_v4), 2, NULL, NULL, NULL, NULL, NULL, SOF_NONE, NULL, NULL); + fst_check(status == SWITCH_STATUS_SUCCESS); + fst_requires(session); + channel = switch_core_session_get_channel(session); + switch_channel_hangup(channel, SWITCH_CAUSE_NORMAL_CLEARING); + switch_core_session_rwunlock(session); + switch_sleep(1 * 1000 * 1000); +} +FST_TEST_END() + +FST_TEST_BEGIN(sofia_auth_identity_test_attest_b) +{ + switch_core_session_t *session = NULL; + switch_channel_t *channel = NULL; + switch_status_t status; + switch_call_cause_t cause; + const char *local_ip_v4 = switch_core_get_variable("local_ip_v4"); + status = switch_ivr_originate(NULL, &session, &cause, switch_core_sprintf(fst_pool, "{origination_caller_id_number=+15551231234,ignore_early_media=true,sip_stir_shaken_attest=B}sofia/internal/+15553214322@%s:53060", local_ip_v4), 2, NULL, NULL, NULL, NULL, NULL, SOF_NONE, NULL, NULL); + fst_check(status == SWITCH_STATUS_SUCCESS); + fst_requires(session); + channel = switch_core_session_get_channel(session); + switch_channel_hangup(channel, SWITCH_CAUSE_NORMAL_CLEARING); + switch_core_session_rwunlock(session); + switch_sleep(1 * 1000 * 1000); +} +FST_TEST_END() + +FST_TEST_BEGIN(sofia_auth_identity_test_attest_c) +{ + switch_core_session_t *session = NULL; + switch_channel_t *channel = NULL; + switch_status_t status; + switch_call_cause_t cause; + const char *local_ip_v4 = switch_core_get_variable("local_ip_v4"); + status = switch_ivr_originate(NULL, &session, &cause, switch_core_sprintf(fst_pool, "{origination_caller_id_number=+15551231234,ignore_early_media=true,sip_stir_shaken_attest=C}sofia/internal/+15553214322@%s:53060", local_ip_v4), 2, NULL, NULL, NULL, NULL, NULL, SOF_NONE, NULL, NULL); + fst_check(status == SWITCH_STATUS_SUCCESS); + fst_requires(session); + channel = switch_core_session_get_channel(session); + switch_channel_hangup(channel, SWITCH_CAUSE_NORMAL_CLEARING); + switch_core_session_rwunlock(session); + switch_sleep(1 * 1000 * 1000); +} +FST_TEST_END() + +FST_TEST_BEGIN(sofia_verify_identity_test_verified_attest_a_expired) +{ + switch_core_session_t *session = NULL; + switch_channel_t *channel = NULL; + switch_status_t status; + switch_call_cause_t cause; + const char *local_ip_v4 = switch_core_get_variable("local_ip_v4"); + status = switch_ivr_originate(NULL, &session, &cause, switch_core_sprintf(fst_pool, "{origination_caller_id_number=+15551231234,ignore_early_media=true,sip_h_identity=eyJhbGciOiJFUzI1NiIsInBwdCI6InNoYWtlbiIsInR5cCI6InBhc3Nwb3J0IiwieDV1IjoiaHR0cDovLzEyNy4wLjAuMTo4MDgwL2NlcnQucGVtIn0.eyJhdHRlc3QiOiJBIiwiZGVzdCI6eyJ0biI6WyIxNTU1MzIxNDMyMiJdfSwiaWF0IjoxNjE4MzczMTc0LCJvcmlnIjp7InRuIjoiMTU1NTEyMzEyMzQifSwib3JpZ2lkIjoiMzliZDYzZDQtOTE1Mi00MzU0LWFkNjctNjg5NjQ2NmI4ZDI3In0.mUaikwHSOb8RVPwwMZTsqBe57MZY29CgbIqmiiEmyq9DzKZO-y4qShiIVT3serg-xHgC9SCMjUOBWaDfeXnEvA;info=;alg=ES256;ppt=shaken}sofia/internal/+15553214322@%s:53060", local_ip_v4), 2, NULL, NULL, NULL, NULL, NULL, SOF_NONE, NULL, NULL); + fst_check(status != SWITCH_STATUS_SUCCESS); + fst_check(cause == SWITCH_CAUSE_CALL_REJECTED); + if (session) { + channel = switch_core_session_get_channel(session); + switch_channel_hangup(channel, SWITCH_CAUSE_NORMAL_CLEARING); + switch_core_session_rwunlock(session); + switch_sleep(1 * 1000 * 1000); + } +} +FST_TEST_END() + +FST_TEST_BEGIN(sofia_auth_identity_test_attest_a_date) +{ + switch_core_session_t *session = NULL; + switch_channel_t *channel = NULL; + switch_status_t status; + switch_call_cause_t cause; + const char *local_ip_v4 = switch_core_get_variable("local_ip_v4"); + status = switch_ivr_originate(NULL, &session, &cause, switch_core_sprintf(fst_pool, "{origination_caller_id_number=+15551231235,ignore_early_media=true,sip_stir_shaken_attest=A}sofia/internal/+15553214323@%s:53060", local_ip_v4), 2, NULL, NULL, NULL, NULL, NULL, SOF_NONE, NULL, NULL); + fst_check(status == SWITCH_STATUS_SUCCESS); + fst_requires(session); + channel = switch_core_session_get_channel(session); + switch_channel_hangup(channel, SWITCH_CAUSE_NORMAL_CLEARING); + switch_core_session_rwunlock(session); + switch_sleep(10 * 1000 * 1000); +} +FST_TEST_END() + FST_MODULE_END() FST_CORE_END() diff --git a/src/mod/endpoints/mod_sofia/test/test_sofia_funcs.sh b/src/mod/endpoints/mod_sofia/test/test_sofia_funcs.sh new file mode 100755 index 0000000000..3ef117ee52 --- /dev/null +++ b/src/mod/endpoints/mod_sofia/test/test_sofia_funcs.sh @@ -0,0 +1,11 @@ +#/bin/sh +cd test +pushd stir-shaken/www +python -m SimpleHTTPServer 8080 & +ppid=$! +popd +./test_sofia_funcs $@ +test=$? +kill $ppid +wait $ppid +exit $test diff --git a/src/switch_channel.c b/src/switch_channel.c index 09a0697f9f..1beeaef4c4 100644 --- a/src/switch_channel.c +++ b/src/switch_channel.c @@ -130,6 +130,11 @@ static struct switch_cause_table CAUSE_CHART[] = { {"DOES_NOT_EXIST_ANYWHERE", SWITCH_CAUSE_DOES_NOT_EXIST_ANYWHERE}, {"NOT_ACCEPTABLE", SWITCH_CAUSE_NOT_ACCEPTABLE}, {"UNWANTED", SWITCH_CAUSE_UNWANTED}, + {"NO_IDENTITY", SWITCH_CAUSE_NO_IDENTITY}, + {"BAD_IDENTITY_INFO", SWITCH_CAUSE_BAD_IDENTITY_INFO}, + {"UNSUPPORTED_CERTIFICATE", SWITCH_CAUSE_UNSUPPORTED_CERTIFICATE}, + {"INVALID_IDENTITY", SWITCH_CAUSE_INVALID_IDENTITY}, + {"STALE_DATE", SWITCH_CAUSE_STALE_DATE}, {NULL, 0} }; diff --git a/tests/unit/switch_vad.c b/tests/unit/switch_vad.c index 8b0f85bb1f..95519c7b92 100644 --- a/tests/unit/switch_vad.c +++ b/tests/unit/switch_vad.c @@ -75,11 +75,12 @@ FST_CORE_BEGIN("./conf") int duration; float pos = 0.0; int got_transition = 0; + int res; switch_vad_state_t cur_state = SWITCH_VAD_STATE_NONE; switch_vad_t *vad = switch_vad_init(8000, 1); fst_requires(vad); - int res = switch_vad_set_mode(vad, 0); // tone is detected as speech in mode 0 + res = switch_vad_set_mode(vad, 0); // tone is detected as speech in mode 0 fst_requires(res == 0); switch_vad_set_param(vad, "silence_ms", 400); switch_vad_set_param(vad, "voice_ms", 80);