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

Benjamin Keith Ford asteriskteam at digium.com
Mon May 4 16:26:27 CDT 2020


Benjamin Keith Ford has uploaded this change for review. ( 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, 340 insertions(+), 2 deletions(-)



  git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/39/14339/1

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..0a42a6c 100644
--- a/include/asterisk/res_stir_shaken.h
+++ b/include/asterisk/res_stir_shaken.h
@@ -21,11 +21,27 @@
 #include <openssl/evp.h>
 #include <openssl/pem.h>
 
+enum ast_stir_shaken_verification_result;
+
 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..cb101df 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"
@@ -112,6 +115,13 @@
 /* The maximum length for path storage */
 #define MAX_PATH_LEN 256
 
+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 {
 	/*! The JWT header */
 	struct ast_json *header;
@@ -146,6 +156,142 @@
 }
 
 /*!
+ * \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);
+		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 +1049,125 @@
 	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) {
+				ast_log(LOG_NOTICE, "skip\n");
+				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)\n", function);
+		return -1;
+	}
+
+	if (ast_str_to_uint(args.first_param, &target_index)) {
+		ast_log(LOG_ERROR, "Could not convert %s to integer\n", args.first_param);
+		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 +1179,8 @@
 
 static int unload_module(void)
 {
+	int res = 0;
+
 	stir_shaken_certificate_unload();
 	stir_shaken_store_unload();
 	stir_shaken_general_unload();
@@ -921,11 +1188,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 +1219,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: 1
Gerrit-Owner: Benjamin Keith Ford <bford at digium.com>
Gerrit-MessageType: newchange
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20200504/76c01b3b/attachment-0001.html>


More information about the asterisk-code-review mailing list