[Asterisk-code-review] res_stir_shaken: Added dialplan function and API call. (asterisk[master])

Friendly Automation asteriskteam at digium.com
Wed May 13 06:41:31 CDT 2020


Friendly Automation has submitted this change. ( https://gerrit.asterisk.org/c/asterisk/+/14339 )

Change subject: res_stir_shaken: Added dialplan function and API call.
......................................................................

res_stir_shaken: Added dialplan function and API call.

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
---
A configs/samples/stir_shaken.conf.sample
M include/asterisk/res_stir_shaken.h
M res/res_stir_shaken.c
3 files changed, 372 insertions(+), 2 deletions(-)

Approvals:
  Joshua Colp: Looks good to me, approved
  Kevin Harwell: Looks good to me, but someone else must approve
  Friendly Automation: Approved for Submit



diff --git a/configs/samples/stir_shaken.conf.sample b/configs/samples/stir_shaken.conf.sample
new file mode 100644
index 0000000..57d1634
--- /dev/null
+++ b/configs/samples/stir_shaken.conf.sample
@@ -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
diff --git a/include/asterisk/res_stir_shaken.h b/include/asterisk/res_stir_shaken.h
index a65a887..48bfa00 100644
--- a/include/asterisk/res_stir_shaken.h
+++ b/include/asterisk/res_stir_shaken.h
@@ -21,11 +21,32 @@
 #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
  *
  * \param header The payload header
diff --git a/res/res_stir_shaken.c b/res/res_stir_shaken.c
index 97fb177..90ceb93 100644
--- a/res/res_stir_shaken.c
+++ b/res/res_stir_shaken.c
@@ -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"
@@ -92,9 +95,41 @@
 					 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"
@@ -146,6 +181,143 @@
 }
 
 /*!
+ * \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 @@
 	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 unload_module(void)
 {
+	int res = 0;
+
 	stir_shaken_certificate_unload();
 	stir_shaken_store_unload();
 	stir_shaken_general_unload();
@@ -921,11 +1215,15 @@
 	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 @@
 
 	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,

-- 
To view, visit https://gerrit.asterisk.org/c/asterisk/+/14339
To unsubscribe, or for help writing mail filters, visit https://gerrit.asterisk.org/settings

Gerrit-Project: asterisk
Gerrit-Branch: master
Gerrit-Change-Id: Ice6d52a3a7d6e4607c9c35b28a1f7c25f5284a82
Gerrit-Change-Number: 14339
Gerrit-PatchSet: 8
Gerrit-Owner: Benjamin Keith Ford <bford at digium.com>
Gerrit-Reviewer: Friendly Automation
Gerrit-Reviewer: George Joseph <gjoseph at digium.com>
Gerrit-Reviewer: Joshua Colp <jcolp at sangoma.com>
Gerrit-Reviewer: Kevin Harwell <kharwell at digium.com>
Gerrit-MessageType: merged
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20200513/0e113aac/attachment-0001.html>


More information about the asterisk-code-review mailing list