]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
res_stir_shaken: Added dialplan function and API call.
authorBen Ford <bford@digium.com>
Mon, 4 May 2020 21:11:00 +0000 (16:11 -0500)
committerFriendly Automation <jenkins2@gerrit.asterisk.org>
Wed, 13 May 2020 11:41:29 +0000 (06:41 -0500)
Adds the "STIR_SHAKEN" dialplan function and an API call to add a
STIR_SHAKEN verification result to a channel. This information will be
held in a datastore on the channel that can later be queried through the
"STIR_SHAKEN" dialplan funtion to get information on STIR_SHAKEN results
including identity, attestation, and verify_result. Here are some
examples:

STIR_SHAKEN(count)
STIR_SHAKEN(0, identity)
STIR_SHAKEN(1, attestation)
STIR_SHAKEN(2, verify_result)

Getting the count can be used to iterate through the results and pull
information by specifying the index and the field you want to retrieve.

Change-Id: Ice6d52a3a7d6e4607c9c35b28a1f7c25f5284a82

configs/samples/stir_shaken.conf.sample [new file with mode: 0644]
include/asterisk/res_stir_shaken.h
res/res_stir_shaken.c

diff --git a/configs/samples/stir_shaken.conf.sample b/configs/samples/stir_shaken.conf.sample
new file mode 100644 (file)
index 0000000..57d1634
--- /dev/null
@@ -0,0 +1,49 @@
+;
+; This file is used by the res_stir_shaken module to configure parameters
+; used for STIR/SHAKEN.
+;
+;
+; [general]
+;
+; File path to the certificate authority certificate
+;ca_file=/etc/asterisk/stir/ca.crt
+;
+; File path to a chain of trust
+;ca_path=/etc/asterisk/stir/ca
+;
+; Maximum size to use for caching public keys
+;cache_max_size=1000
+;
+; Maximum time to wait to CURL certificates
+;curl_timeout
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+; A certificate store is used to examine, and load all certificates found in a
+; given directory. When using this type the public key URL is generated based
+; upon the filename, and variable substitution.
+;[certificates]
+;
+; type must be "store"
+;type=store
+;
+; Path to a directory containing certificates
+;path=/etc/asterisk/stir
+;
+; URL to the public key(s). Must contain variable '${CERTIFICATE}' used for
+; substitution
+;public_key_url=http://mycompany.com/${CERTIFICATE}.pub
+;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;
+; Individual certificates are declared by using the certificate type.
+;[alice]
+;
+; type must be "certificate"
+;type=certificate
+;
+; File path to a certificate
+;path=/etc/asterisk/stir/alice.crt
+;
+; URL to the public key
+;public_key_url=http://mycompany.com/alice.pub
index a65a887cffb934a4bd4536ab5e5b8efe253f54cf..48bfa00085ce2135259b69a4daee39ce928f948a 100644 (file)
 #include <openssl/evp.h>
 #include <openssl/pem.h>
 
+enum ast_stir_shaken_verification_result {
+       AST_STIR_SHAKEN_VERIFY_NOT_PRESENT, /*! No STIR/SHAKEN information was available */
+       AST_STIR_SHAKEN_VERIFY_SIGNATURE_FAILED, /*! Signature verification failed */
+       AST_STIR_SHAKEN_VERIFY_MISMATCH, /*! Contents of the signaling and the STIR/SHAKEN payload did not match */
+       AST_STIR_SHAKEN_VERIFY_PASSED, /*! Signature verified and contents match signaling */
+};
+
 struct ast_stir_shaken_payload;
 
 struct ast_json;
 
+/*!
+ * \brief Add a STIR/SHAKEN verification result to a channel
+ *
+ * \param chan The channel
+ * \param identity The identity
+ * \param attestation The attestation
+ * \param result The verification result
+ *
+ * \retval -1 on failure
+ * \retval 0 on success
+ */
+int ast_stir_shaken_add_verification(struct ast_channel *chan, const char *identity, const char *attestation,
+       enum ast_stir_shaken_verification_result result);
+
 /*!
  * \brief Verify a JSON STIR/SHAKEN payload
  *
index 97fb17710e83a683dc0608645e4f6ad75b397689..90ceb93a9428159ad30c7c1a0ad05a5a843fed6f 100644 (file)
@@ -32,6 +32,9 @@
 #include "asterisk/astdb.h"
 #include "asterisk/paths.h"
 #include "asterisk/conversions.h"
+#include "asterisk/pbx.h"
+#include "asterisk/global_datastores.h"
+#include "asterisk/app.h"
 
 #include "asterisk/res_stir_shaken.h"
 #include "res_stir_shaken/stir_shaken.h"
                                         Must be a valid http, or https, URL.
                                        </para></description>
                                </configOption>
+                               <configOption name="caller_id_number" default="">
+                                       <synopsis>The caller ID number to match on.</synopsis>
+                               </configOption>
                        </configObject>
                </configFile>
        </configInfo>
+       <function name="STIR_SHAKEN" language="en_US">
+               <synopsis>
+                       Gets the number of STIR/SHAKEN results or a specific STIR/SHAKEN value from a result on the channel.
+               </synopsis>
+               <syntax>
+                       <parameter name="index" required="true">
+                               <para>The index of the STIR/SHAKEN result to get. If only 'count' is passed in, gets the number of STIR/SHAKEN results instead.</para>
+                       </parameter>
+                       <parameter name="value" required="false">
+                               <para>The value to get from the STIR/SHAKEN result. Only used when an index is passed in (instead of 'count'). Allowable values:</para>
+                               <enumlist>
+                                       <enum name = "identity" />
+                                       <enum name = "attestation" />
+                                       <enum name = "verify_result" />
+                               </enumlist>
+                       </parameter>
+               </syntax>
+               <description>
+                       <para>This function will either return the number of STIR/SHAKEN identities, or return information on the specified identity.
+                       To get the number of identities, just pass 'count' as the only parameter to the function. If you want to get information on a
+                       specific STIR/SHAKEN identity, you can get the number of identities and then pass an index as the first parameter and one of
+                       the values you would like to retrieve as the second parameter.
+                       </para>
+                       <example title="Get count and retrieve value">
+                       same => n,NoOp(Number of STIR/SHAKEN identities: ${STIR_SHAKEN(count)})
+                       same => n,NoOp(Identity ${STIR_SHAKEN(0, identity)} has attestation level ${STIR_SHAKEN(0, attestation)})
+                       </example>
+               </description>
+       </function>
  ***/
 
 #define STIR_SHAKEN_ENCRYPTION_ALGORITHM "ES256"
@@ -145,6 +180,143 @@ void ast_stir_shaken_payload_free(struct ast_stir_shaken_payload *payload)
        ast_free(payload);
 }
 
+/*!
+ * \brief Convert an ast_stir_shaken_verification_result to string representation
+ *
+ * \param result The result to convert
+ *
+ * \retval empty string if not a valid enum value
+ * \retval string representation of result otherwise
+ */
+static const char *stir_shaken_verification_result_to_string(enum ast_stir_shaken_verification_result result)
+{
+       switch (result) {
+               case AST_STIR_SHAKEN_VERIFY_NOT_PRESENT:
+                       return "Verification not present";
+               case AST_STIR_SHAKEN_VERIFY_SIGNATURE_FAILED:
+                       return "Signature failed";
+               case AST_STIR_SHAKEN_VERIFY_MISMATCH:
+                       return "Verification mismatch";
+               case AST_STIR_SHAKEN_VERIFY_PASSED:
+                       return "Verification passed";
+               default:
+                       break;
+       }
+
+       return "";
+}
+
+/* The datastore struct holding verification information for the channel */
+struct stir_shaken_datastore {
+       /* The identitifier for the STIR/SHAKEN verification */
+       char *identity;
+       /* The attestation value */
+       char *attestation;
+       /* The actual verification result */
+       enum ast_stir_shaken_verification_result verify_result;
+};
+
+/*!
+ * \brief Frees a stir_shaken_datastore structure
+ *
+ * \param datastore The datastore to free
+ */
+static void stir_shaken_datastore_free(struct stir_shaken_datastore *datastore)
+{
+       if (!datastore) {
+               return;
+       }
+
+       ast_free(datastore->identity);
+       ast_free(datastore->attestation);
+       ast_free(datastore);
+}
+
+/*!
+ * \brief The callback to destroy a stir_shaken_datastore
+ *
+ * \param data The stir_shaken_datastore
+ */
+static void stir_shaken_datastore_destroy_cb(void *data)
+{
+       struct stir_shaken_datastore *datastore = data;
+       stir_shaken_datastore_free(datastore);
+}
+
+/* The stir_shaken_datastore info used to add and compare stir_shaken_datastores on the channel */
+static const struct ast_datastore_info stir_shaken_datastore_info = {
+       .type = "STIR/SHAKEN VERIFICATION",
+       .destroy = stir_shaken_datastore_destroy_cb,
+};
+
+int ast_stir_shaken_add_verification(struct ast_channel *chan, const char *identity, const char *attestation,
+       enum ast_stir_shaken_verification_result result)
+{
+       struct stir_shaken_datastore *ss_datastore;
+       struct ast_datastore *datastore;
+       const char *chan_name;
+
+       if (!chan) {
+               ast_log(LOG_ERROR, "Channel is required to add STIR/SHAKEN verification\n");
+               return -1;
+       }
+
+       chan_name = ast_channel_name(chan);
+
+       if (!identity) {
+               ast_log(LOG_ERROR, "No identity to add STIR/SHAKEN verification to channel "
+                       "%s\n", chan_name);
+               return -1;
+       }
+
+       if (ast_strlen_zero(attestation)) {
+               ast_log(LOG_ERROR, "No attestation to add STIR/SHAKEN verification to "
+                       "channel %s\n", chan_name);
+               return -1;
+       }
+
+       ss_datastore = ast_calloc(1, sizeof(*ss_datastore));
+       if (!ss_datastore) {
+               ast_log(LOG_ERROR, "Failed to allocate space for STIR/SHAKEN datastore for "
+                       "channel %s\n", chan_name);
+               return -1;
+       }
+
+       ss_datastore->identity = ast_strdup(identity);
+       if (!ss_datastore->identity) {
+               ast_log(LOG_ERROR, "Failed to allocate space for STIR/SHAKEN datastore "
+                       "identity for channel %s\n", chan_name);
+               stir_shaken_datastore_free(ss_datastore);
+               return -1;
+       }
+
+       ss_datastore->attestation = ast_strdup(attestation);
+       if (!ss_datastore->attestation) {
+               ast_log(LOG_ERROR, "Failed to allocate space for STIR/SHAKEN datastore "
+                       "attestation for channel %s\n", chan_name);
+               stir_shaken_datastore_free(ss_datastore);
+               return -1;
+       }
+
+       ss_datastore->verify_result = result;
+
+       datastore = ast_datastore_alloc(&stir_shaken_datastore_info, NULL);
+       if (!datastore) {
+               ast_log(LOG_ERROR, "Failed to allocate space for datastore for channel "
+                       "%s\n", chan_name);
+               stir_shaken_datastore_free(ss_datastore);
+               return -1;
+       }
+
+       datastore->data = ss_datastore;
+
+       ast_channel_lock(chan);
+       ast_channel_datastore_add(chan, datastore);
+       ast_channel_unlock(chan);
+
+       return 0;
+}
+
 /*!
  * \brief Sets the expiration for the public key based on the provided fields.
  * If Cache-Control is present, use it. Otherwise, use Expires.
@@ -903,6 +1075,126 @@ cleanup:
        return NULL;
 }
 
+/*!
+ * \brief Retrieves STIR/SHAKEN verification information for the channel via dialplan.
+ * Examples:
+ *
+ * STIR_SHAKEN(count)
+ * STIR_SHAKEN(0, identity)
+ * STIR_SHAKEN(1, attestation)
+ * STIR_SHAKEN(27, verify_result)
+ *
+ * \retval -1 on failure
+ * \retval 0 on success
+ */
+static int stir_shaken_read(struct ast_channel *chan, const char *function,
+       char *data, char *buf, size_t len)
+{
+       struct stir_shaken_datastore *ss_datastore;
+       struct ast_datastore *datastore;
+       char *parse;
+       unsigned int target_index, current_index = 0;
+       AST_DECLARE_APP_ARGS(args,
+               AST_APP_ARG(first_param);
+               AST_APP_ARG(second_param);
+       );
+
+       if (ast_strlen_zero(data)) {
+               ast_log(LOG_WARNING, "%s requires at least one argument\n", function);
+               return -1;
+       }
+
+       if (!chan) {
+               ast_log(LOG_ERROR, "No channel for %s function\n", function);
+               return -1;
+       }
+
+       parse = ast_strdupa(data);
+
+       AST_STANDARD_APP_ARGS(args, parse);
+
+       if (ast_strlen_zero(args.first_param)) {
+               ast_log(LOG_ERROR, "An argument must be passed to %s\n", function);
+               return -1;
+       }
+
+       /* Check if we are only looking for the number of STIR/SHAKEN verification results */
+       if (!strcasecmp(args.first_param, "count")) {
+
+               size_t count = 0;
+
+               if (!ast_strlen_zero(args.second_param)) {
+                       ast_log(LOG_ERROR, "%s only takes 1 paramater for 'count'\n", function);
+                       return -1;
+               }
+
+               ast_channel_lock(chan);
+               AST_LIST_TRAVERSE(ast_channel_datastores(chan), datastore, entry) {
+                       if (datastore->info != &stir_shaken_datastore_info) {
+                               continue;
+                       }
+                       count++;
+               }
+               ast_channel_unlock(chan);
+
+               snprintf(buf, len, "%zu", count);
+               return 0;
+       }
+
+       /* 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)) {
+               ast_log(LOG_ERROR, "Retrieving a value using %s requires two paramaters (index, value) "
+                       "- only index was given (%s)\n", function, args.second_param);
+               return -1;
+       }
+
+       if (ast_str_to_uint(args.first_param, &target_index)) {
+               ast_log(LOG_ERROR, "Failed to convert index %s to integer for function %s\n",
+                       args.first_param, function);
+               return -1;
+       }
+
+       /* We don't store by uid for the datastore, so just search for the specified index */
+       ast_channel_lock(chan);
+       AST_LIST_TRAVERSE(ast_channel_datastores(chan), datastore, entry) {
+               if (datastore->info != &stir_shaken_datastore_info) {
+                       continue;
+               }
+
+               if (current_index == target_index) {
+                       break;
+               }
+
+               current_index++;
+       }
+       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);
+               return -1;
+       }
+       ss_datastore = datastore->data;
+
+       if (!strcasecmp(args.second_param, "identity")) {
+               ast_copy_string(buf, ss_datastore->identity, len);
+       } else if (!strcasecmp(args.second_param, "attestation")) {
+               ast_copy_string(buf, ss_datastore->attestation, len);
+       } else if (!strcasecmp(args.second_param, "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);
+               return -1;
+       }
+
+       return 0;
+}
+
+static struct ast_custom_function stir_shaken_function = {
+       .name = "STIR_SHAKEN",
+       .read = stir_shaken_read,
+};
+
 static int reload_module(void)
 {
        if (stir_shaken_sorcery) {
@@ -914,6 +1206,8 @@ static int reload_module(void)
 
 static int unload_module(void)
 {
+       int res = 0;
+
        stir_shaken_certificate_unload();
        stir_shaken_store_unload();
        stir_shaken_general_unload();
@@ -921,11 +1215,15 @@ static int unload_module(void)
        ast_sorcery_unref(stir_shaken_sorcery);
        stir_shaken_sorcery = NULL;
 
-       return 0;
+       res |= ast_custom_function_unregister(&stir_shaken_function);
+
+       return res;
 }
 
 static int load_module(void)
 {
+       int res = 0;
+
        if (!(stir_shaken_sorcery = ast_sorcery_open())) {
                ast_log(LOG_ERROR, "stir/shaken - failed to open sorcery\n");
                return AST_MODULE_LOAD_DECLINE;
@@ -948,7 +1246,9 @@ static int load_module(void)
 
        ast_sorcery_load(ast_stir_shaken_sorcery());
 
-       return AST_MODULE_LOAD_SUCCESS;
+       res |= ast_custom_function_register(&stir_shaken_function);
+
+       return res;
 }
 
 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER,