;device_state_busy_at=1
;allow_subscribe=yes
;sub_min_expiry=30
+;
+; STIR/SHAKEN support.
+;
+;stir_shaken=no
;[6001]
;type=auth
; chan_sip and prevents these 183 responses from
; being forwarded.
; (default: no)
+;stir_shaken =
+ ; If this is enabled, STIR/SHAKEN operations will be
+ ; performed on this endpoint. This includes inbound
+ ; and outbound INVITEs. On an inbound INVITE, Asterisk
+ ; will check for an Identity header and attempt to
+ ; verify the call. On an outbound INVITE, Asterisk will
+ ; add an Identity header that others can use to verify
+ ; calls from this endpoint. Additional configuration is
+ ; done in stir_shaken.conf.
+ ; The STIR_SHAKEN dialplan function must be used to get
+ ; the verification results on inbound INVITEs. Nothing
+ ; happens to the call if verification fails; it's up to
+ ; you to determine what to do with the results.
+ ; (default: no)
;==========================AUTH SECTION OPTIONS=========================
;[auth]
; Maximum size to use for caching public keys
;cache_max_size=1000
;
-; Maximum time to wait to CURL certificates
-;curl_timeout
+; Maximum time (in seconds) to wait to CURL certificates
+;curl_timeout=2
+;
+; Amount of time (in seconds) a signature is valid for
+;signature_timeout=15
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; URL to the public key
;public_key_url=http://mycompany.com/alice.pub
;
+; The caller ID number to match on
+;caller_id_number=1234567
+;
; Must have an attestation of A, B, or C
;attestation=C
;
--- /dev/null
+"""add stir shaken
+
+Revision ID: 61797b9fced6
+Revises: fbb7766f17bc
+Create Date: 2020-06-29 11:52:59.946929
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '61797b9fced6'
+down_revision = 'b80485ff4dd0'
+
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.dialects.postgresql import ENUM
+
+YESNO_NAME = 'yesno_values'
+YESNO_VALUES = ['yes', 'no']
+
+AST_BOOL_NAME = 'ast_bool_values'
+AST_BOOL_VALUES = [ '0', '1',
+ 'off', 'on',
+ 'false', 'true',
+ 'no', 'yes' ]
+
+def upgrade():
+ ast_bool_values = ENUM(*AST_BOOL_VALUES, name=AST_BOOL_NAME, create_type=False)
+ op.add_column('ps_endpoints', sa.Column('stir_shaken', ast_bool_values))
+
+def downgrade():
+ op.drop_column('ps_endpoints', 'stir_shaken')
--- /dev/null
+Subject: STIR/SHAKEN
+
+STIR/SHAKEN support has been added to Asterisk. Configuration is done in
+stir_shaken.conf. There is a sample configuration file to help you get
+started (asterisk/configs/samples/stir_shaken.conf.sample). Once that's
+set up, you can enable STIR/SHAKEN on any endpoint by setting stir_shaken
+to yes on the endpoint configuration object. This will add an Identity
+header on outgoing INVITEs, and check for an Identity header on incoming
+INVITEs. This option has been added to Alembic as well.
+
+The information received on an incoming INVITE can be checked using the
+STIR_SHAKEN dialplan function. There are two variations:
+
+STIR_SHAKEN(count)
+STIR_SHAKEN(0, verify_result)
+
+The first variation will tell you how many STIR/SHAKEN results are on the
+channel. The second fetches information for a specific result. The first
+parameter is the index, followed by what information you want to retrieve.
+The available options are 'verify_result', 'identity', and 'attestation'.
unsigned int suppress_q850_reason_headers;
/*! Ignore 183 if no SDP is present */
unsigned int ignore_183_without_sdp;
+ /*! Enable STIR/SHAKEN support on this endpoint */
+ unsigned int stir_shaken;
};
/*! URI parameter for symmetric transport */
being forwarded.</para>
</description>
</configOption>
+ <configOption name="stir_shaken" default="no">
+ <synopsis>Enable STIR/SHAKEN support on this endpoint</synopsis>
+ <description><para>
+ Enable STIR/SHAKEN support on this endpoint. On incoming INVITEs,
+ the Identity header will be checked for validity. On outgoing
+ INVITEs, an Identity header will be added.</para>
+ </description>
+ </configOption>
</configObject>
<configObject name="auth">
<synopsis>Authentication type</synopsis>
ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "outgoing_answer_codec_prefs",
"prefer: pending, operation: intersect, keep: all",
codec_prefs_handler, outgoing_answer_codec_prefs_to_str, NULL, 0, 0);
+ ast_sorcery_object_field_register(sip_sorcery, "endpoint", "stir_shaken", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, stir_shaken));
if (ast_sip_initialize_sorcery_transport()) {
ast_log(LOG_ERROR, "Failed to register SIP transport support with sorcery\n");
long int timestamp;
struct timeval now = ast_tvnow();
+#ifdef TEST_FRAMEWORK
+ ast_debug(3, "Ignoring STIR/SHAKEN timestamp\n");
+ return 0;
+#endif
+
json = ast_json_load_string(json_str, NULL);
timestamp = ast_json_integer_get(ast_json_object_get(json, "iat"));
int mismatch = 0;
struct ast_stir_shaken_payload *ss_payload;
+ if (!session->endpoint->stir_shaken) {
+ return 0;
+ }
+
identity_hdr_val = ast_sip_rdata_get_header_value(rdata, identity_str);
if (ast_strlen_zero(identity_hdr_val)) {
ast_stir_shaken_add_verification(chan, caller_id, "", AST_STIR_SHAKEN_VERIFY_NOT_PRESENT);
static void stir_shaken_outgoing_request(struct ast_sip_session *session, pjsip_tx_data *tdata)
{
+ if (!session->endpoint->stir_shaken) {
+ return;
+ }
+
if (ast_strlen_zero(session->id.number.str) && session->id.number.valid) {
return;
}
/* The maximum length for path storage */
#define MAX_PATH_LEN 256
+/* The default amount of time (in seconds) to use for certificate expiration
+ * if no cache data is available
+ */
+#define EXPIRATION_BUFFER 15
+
struct ast_stir_shaken_payload {
/*! The JWT header */
struct ast_json *header;
}
}
+ if (ast_strlen_zero(value)) {
+ actual_expires.tv_sec += EXPIRATION_BUFFER;
+ }
+
snprintf(time_buf, sizeof(time_buf), "%30lu", actual_expires.tv_sec);
ast_db_put(hash, "expiration", time_buf);
struct stir_shaken_datastore *ss_datastore;
struct ast_datastore *datastore;
char *parse;
+ char *first;
+ char *second;
unsigned int target_index, current_index = 0;
AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(first_param);
AST_STANDARD_APP_ARGS(args, parse);
- if (ast_strlen_zero(args.first_param)) {
+ first = ast_strip(args.first_param);
+ if (ast_strlen_zero(first)) {
ast_log(LOG_ERROR, "An argument must be passed to %s\n", function);
return -1;
}
+ second = ast_strip(args.second_param);
+
/* Check if we are only looking for the number of STIR/SHAKEN verification results */
- if (!strcasecmp(args.first_param, "count")) {
+ if (!strcasecmp(first, "count")) {
size_t count = 0;
- if (!ast_strlen_zero(args.second_param)) {
+ if (!ast_strlen_zero(second)) {
ast_log(LOG_ERROR, "%s only takes 1 paramater for 'count'\n", function);
return -1;
}
/* If we aren't doing a count, then there should be two parameters. The field
* we are searching for will be the second parameter. The index is the first.
*/
- if (ast_strlen_zero(args.second_param)) {
+ if (ast_strlen_zero(second)) {
ast_log(LOG_ERROR, "Retrieving a value using %s requires two paramaters (index, value) "
- "- only index was given (%s)\n", function, args.second_param);
+ "- only index was given (%s)\n", function, second);
return -1;
}
- if (ast_str_to_uint(args.first_param, &target_index)) {
+ if (ast_str_to_uint(first, &target_index)) {
ast_log(LOG_ERROR, "Failed to convert index %s to integer for function %s\n",
- args.first_param, function);
+ first, function);
return -1;
}
}
ast_channel_unlock(chan);
if (current_index != target_index || !datastore) {
- ast_log(LOG_WARNING, "No STIR/SHAKEN results for index '%s'\n", args.first_param);
+ ast_log(LOG_WARNING, "No STIR/SHAKEN results for index '%s'\n", first);
return -1;
}
ss_datastore = datastore->data;
- if (!strcasecmp(args.second_param, "identity")) {
+ if (!strcasecmp(second, "identity")) {
ast_copy_string(buf, ss_datastore->identity, len);
- } else if (!strcasecmp(args.second_param, "attestation")) {
+ } else if (!strcasecmp(second, "attestation")) {
ast_copy_string(buf, ss_datastore->attestation, len);
- } else if (!strcasecmp(args.second_param, "verify_result")) {
+ } else if (!strcasecmp(second, "verify_result")) {
ast_copy_string(buf, stir_shaken_verification_result_to_string(ss_datastore->verify_result), len);
} else {
- ast_log(LOG_ERROR, "No such value '%s' for %s\n", args.second_param, function);
+ ast_log(LOG_ERROR, "No such value '%s' for %s\n", second, function);
return -1;
}