]> git.ipfire.org Git - thirdparty/freeswitch.git/commitdiff
STIR/SHAKEN (#1160)
authorChris Rienzo <chris@signalwire.com>
Tue, 27 Apr 2021 19:54:32 +0000 (15:54 -0400)
committerGitHub <noreply@github.com>
Tue, 27 Apr 2021 19:54:32 +0000 (13:54 -0600)
* [core] Add SWITCH_CAUSEs for STIR/SHAKEN.
[mod_sofia] Add sofia_verify_identity dialplan APP as a STIR/SHAKEN verification service.  Set sip_hangup_on_verify_identity_fail=true to end calls that fail verification, otherwise check sip_verstat and sip_verstat_detailed channel variables for verification result.

* [mod_sofia] Fix stir shaken implementation issues on fail.

* fix build

* Fix given comments

* stir_shaken_passport_get_grant return does not require to be freed.

* reworked things

* [core] add switch_rfc822_datetime_to_epoch()

* [mod_sofia] fix test return code

* [mod_sofia] Add Date header when signing Identity

* [mod_sofia] Check Date - WIP doesn't work

* [mod_sofia] STIR/SHAKEN check SIP Date header

* Try to give time for sofia to clean up calls

Co-authored-by: Andrey Volk <andywolk@gmail.com>
16 files changed:
configure.ac
src/include/switch_types.h
src/mod/endpoints/mod_sofia/Makefile.am
src/mod/endpoints/mod_sofia/mod_sofia.c
src/mod/endpoints/mod_sofia/mod_sofia.h
src/mod/endpoints/mod_sofia/sofia.c
src/mod/endpoints/mod_sofia/sofia_glue.c
src/mod/endpoints/mod_sofia/test/conf/freeswitch.xml
src/mod/endpoints/mod_sofia/test/stir-shaken/priv.pem [new file with mode: 0644]
src/mod/endpoints/mod_sofia/test/stir-shaken/pub.pem [new file with mode: 0644]
src/mod/endpoints/mod_sofia/test/stir-shaken/www/cert.pem [new file with mode: 0644]
src/mod/endpoints/mod_sofia/test/stir-shaken/www/pub.pem [new file with mode: 0644]
src/mod/endpoints/mod_sofia/test/test_sofia_funcs.c
src/mod/endpoints/mod_sofia/test/test_sofia_funcs.sh [new file with mode: 0755]
src/switch_channel.c
tests/unit/switch_vad.c

index a74edfbc8b06bc4cf4055ca82fe0e00607bad51e..73c260f3111079a3dcb1caad8759ada43d2e8126 100644 (file)
@@ -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)
index 58f708729d070edf17043fb01b31b7590c72d525..fafc508823b01a0234cdf3f071dae3e81e9a7db7 100644 (file)
@@ -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 {
index 0e4346e237367ec3f3619d995185e0354d38c7ef..4bd174917543eef71f2d31f3907f33492ebe88eb 100644 (file)
@@ -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
index 8c1b7fd3250f355bc95cbb2db331fda94469e6da..ce9d520ba83bd4116f0b1dc8084f79481feb867d 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
- * Copyright (C) 2005-2014, Anthony Minessale II <anthm@freeswitch.org>
+ * Copyright (C) 2005-2021, Anthony Minessale II <anthm@freeswitch.org>
  *
  * Version: MPL 1.1
  *
 #include "mod_sofia.h"
 #include "sofia-sip/sip_extra.h"
 
+#if HAVE_STIRSHAKEN
+#include <stir_shaken.h>
+#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, "<uuid>", 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, "<cmd> <args>");
        SWITCH_ADD_API(api_interface, "sofia_gateway_data", "Get data from a sofia gateway", sofia_gateway_data_function, "<gateway_name> [ivar|ovar|var] <name>");
@@ -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)
index 3c2d02a5a74e9e624e82cdaae93a944db29fe9c6..e91d849913bcb71d5ba5a2f7007ba7c3006d07d0 100644 (file)
@@ -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
index dbd9f94343e796c8696ec74868a55fc463ae2628..dd46fb4c0f247843aca7aea25352e574f7acffef 100644 (file)
@@ -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) {
index 0c0035412fe587f3e108b5461c6613ac1c332298..10282383d2666a7d729258568d5ee6d426d1f3b5 100644 (file)
@@ -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;
        }
index c654a84ebc2a1dace1e245b7ee1c5ab5a3fb2876..d58f45b98368f94313c2ddecab1b73a23697e97e 100644 (file)
       <global_settings>
         <param name="log-level" value="9"/>
         <param name="tracelevel" value="debug"/>
+        <param name="stir-shaken-as-key" value="stir-shaken/priv.pem"/>
+        <param name="stir-shaken-as-url" value="http://127.0.0.1:8080/cert.pem"/>
+        <!--param name="stir-shaken-vs-ca-dir" value="stir-shaken/ca"/-->
+        <param name="stir-shaken-vs-cert-path-check" value="false"/>
+        <param name="stir-shaken-vs-require-date" value="false"/>
       </global_settings>
       <profiles>
         <profile name="internal">
           <action application="park" data=""/>
         </condition>
       </extension>
+      <extension name="verifyidentity">
+        <condition field="destination_number" expression="^verifyidentity$">
+          <action application="pre_answer" data=""/>
+          <action application="set" data="sip_stir_shaken_vs_hangup_on_fail=true"/>
+          <action application="sofia_stir_shaken_vs" data=""/>
+          <action application="answer" data=""/>
+          <action application="park" data=""/>
+        </condition>
+      </extension>
+      <extension name="verifyidentitytn">
+        <condition field="destination_number" expression="^\+15553214321$">
+          <action application="pre_answer" data=""/>
+          <action application="set" data="sip_stir_shaken_vs_hangup_on_fail=true"/>
+          <action application="sofia_stir_shaken_vs" data=""/>
+          <action application="answer" data=""/>
+          <action application="park" data=""/>
+        </condition>
+      </extension>
+      <extension name="verifyidentitytn2">
+        <condition field="destination_number" expression="^\+15553214322$">
+          <action application="pre_answer" data=""/>
+          <!-- verifies and makes sure JWT signature is fresh (<= 60 seconds) -->
+          <action application="set" data="sip_stir_shaken_vs_hangup_on_fail=true"/>
+          <action application="sofia_stir_shaken_vs" data=""/>
+          <action application="answer" data=""/>
+          <action application="park" data=""/>
+        </condition>
+      </extension>
+      <extension name="verifyidentitytndate">
+        <condition field="destination_number" expression="^\+15553214323$">
+          <action application="pre_answer" data=""/>
+          <action application="info"/>
+          <action application="set" data="sip_stir_shaken_vs_hangup_on_fail=true"/>
+          <action application="set" data="sip_stir_shaken_vs_require_date=true"/>
+          <action application="sofia_stir_shaken_vs" data=""/>
+          <action application="answer" data=""/>
+          <action application="park" data=""/>
+        </condition>
+      </extension>
     </context>
   </section>
 </document>
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 (file)
index 0000000..0e812f0
--- /dev/null
@@ -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 (file)
index 0000000..96c90cb
--- /dev/null
@@ -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 (file)
index 0000000..f364baa
--- /dev/null
@@ -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 (file)
index 0000000..f364baa
--- /dev/null
@@ -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-----
index 0c03d232d1f3bef5b3638306972732bdb31a77f6..e769057363e18057da203b02c30288096257f5c2 100644 (file)
@@ -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=<http://127.0.0.1/404.pem>;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=<http://127.0.0.1:8080/cert.pem>;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 (executable)
index 0000000..3ef117e
--- /dev/null
@@ -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
index 09a0697f9f45c2c0926923696237df752f2d4616..1beeaef4c40a928d7da33c7ba05796b5d78ed59e 100644 (file)
@@ -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}
 };
 
index 8b0f85bb1ff97badcbb47f5daecba5d14992fca6..95519c7b92c5872289b1e779e08e2c8c1da7e4da 100644 (file)
@@ -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);