mirror of
				https://github.com/asterisk/asterisk.git
				synced 2025-10-31 02:37:10 +00:00 
			
		
		
		
	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:
		
				
					committed by
					
						 Friendly Automation
						Friendly Automation
					
				
			
			
				
	
			
			
			
						parent
						
							801d570f6e
						
					
				
				
					commit
					e29df34de0
				
			
							
								
								
									
										49
									
								
								configs/samples/stir_shaken.conf.sample
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								configs/samples/stir_shaken.conf.sample
									
									
									
									
									
										Normal file
									
								
							| @@ -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, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user