[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