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
This commit is contained in:
parent
0392a8e620
commit
035b463c93
|
@ -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
|
|
@ -21,10 +21,31 @@
|
|||
#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
|
||||
*
|
||||
|
|
|
@ -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"
|
||||
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue