]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
res_stir_shaken: Allow sending Identity headers for unknown TNs
authorGeorge Joseph <gjoseph@sangoma.com>
Fri, 8 Nov 2024 18:22:12 +0000 (11:22 -0700)
committerasterisk-org-access-app[bot] <120671045+asterisk-org-access-app[bot]@users.noreply.github.com>
Wed, 20 Nov 2024 21:38:42 +0000 (21:38 +0000)
Added a new option "unknown_tn_attest_level" to allow Identity
headers to be sent when a callerid TN isn't explicitly configured
in stir_shaken.conf.  Since there's no TN object, a private_key_file
and public_cert_url must be configured in the attestation or profile
objects.

Since "unknown_tn_attest_level" uses the same enum as attest_level,
some of the sorcery macros had to be refactored to allow sharing
the enum and to/from string conversion functions.

Also fixed a memory leak in crypto_utils:pem_file_cb().

Resolves: #921

UserNote: You can now set the "unknown_tn_attest_level" option
in the attestation and/or profile objects in stir_shaken.conf to
enable sending Identity headers for callerid TNs not explicitly
configured.

configs/samples/stir_shaken.conf.sample
res/res_stir_shaken/attestation_config.c
res/res_stir_shaken/common_config.h
res/res_stir_shaken/crypto_utils.c
res/res_stir_shaken/profile_config.c
res/res_stir_shaken/stir_shaken_doc.xml
res/res_stir_shaken/tn_config.c
res/res_stir_shaken/verification_config.c

index d5e97979fdfe867df856b6210c5e6b6fb98f3e50..3fe9d7e955792759901df7642f73cb432c554d94 100644 (file)
@@ -99,6 +99,20 @@ One of "A", "B", "C"
 
 Default: none
 
+-- unknown_tn_attest_level --------------------------------------------
+Attestation level to use for unknown TNs.
+One of "A", "B", "C"
+
+Normally if a callerid TN isn't configured in stir_shaken.conf
+no Identity header will be created. If this option is set,
+however, an Identity header will be sent using this
+attestation level.  Since there's no TN object, you must
+ensure that a private_key_file and public_cert_url are
+configured in the attestation or profile objects for
+this to work.
+
+Default: none
+
 -- send_mky -----------------------------------------------------------
 If set and an outgoing call uses DTLS, an "mky" Media Key grant will
 be added to the Identity header.  Although RFC8224/8225 require this,
@@ -116,6 +130,7 @@ Example "attestation" object:
 ;private_key_file = /var/lib/asterisk/keys/stir_shaken/tns/multi-tns-key.pem
 ;public_cert_url = https://example.com/tncerts/multi-tns-cert.pem
 ;attest_level = C
+;unknown_tn_attest_level = C
 
 ;--
 =======================================================================
index 7a5743c9f7411108876977a81a80d539649ec3d0..46ebb90399c7bb786176c5c80dc127f19f5f8d83 100644 (file)
  * the GNU General Public License Version 2. See the LICENSE file
  * at the top of the source tree.
  */
+
+#define _TRACE_PREFIX_ "ac",__LINE__, ""
+
 #include "asterisk.h"
 
 #include "asterisk/cli.h"
+#include "asterisk/logger.h"
 #include "asterisk/sorcery.h"
 #include "asterisk/paths.h"
 
@@ -31,6 +35,7 @@
 #define DEFAULT_private_key_file NULL
 #define DEFAULT_public_cert_url NULL
 #define DEFAULT_attest_level attest_level_NOT_SET
+#define DEFAULT_unknown_tn_attest_level attest_level_NOT_SET
 #define DEFAULT_send_mky send_mky_NO
 
 static struct attestation_cfg *empty_cfg = NULL;
@@ -57,6 +62,9 @@ int as_is_config_loaded(void)
 
 generate_acfg_common_sorcery_handlers(attestation_cfg);
 
+generate_sorcery_enum_from_str_ex(attestation_cfg,,unknown_tn_attest_level, attest_level, UNKNOWN);
+generate_sorcery_enum_to_str_ex(attestation_cfg,,unknown_tn_attest_level, attest_level);
+
 void acfg_cleanup(struct attestation_cfg_common *acfg_common)
 {
        if (!acfg_common) {
@@ -309,6 +317,9 @@ int as_config_load(void)
                DEFAULT_global_disable ? "yes" : "no",
                OPT_YESNO_T, 1, FLDSET(struct attestation_cfg, global_disable));
 
+       enum_option_register_ex(sorcery, CONFIG_TYPE, unknown_tn_attest_level,
+               unknown_tn_attest_level, attest_level,);
+
        register_common_attestation_fields(sorcery, attestation_cfg, CONFIG_TYPE,);
 
        ast_sorcery_load_object(sorcery, CONFIG_TYPE);
index 3cb1795d7843a6b1e5c4c6e562b0eb3bdf1354cf..e390b548b6be793b04762333bb509319df7efb6e 100644 (file)
@@ -116,6 +116,12 @@ generate_enum_string_prototypes(attest_level,
        attest_level_C,
 );
 
+/*
+ * unknown_tn_attest_level uses the same enum as attest_level.
+ */
+enum attest_level_enum unknown_tn_attest_level_from_str(const char *value);
+const char *unknown_tn_attest_level_to_str(enum attest_level_enum value);
+
 /*
  * enum stir_shaken_failure_action is defined in
  * res_stir_shaken.h because res_pjsip_stir_shaken needs it
@@ -136,20 +142,23 @@ const char *stir_shaken_failure_action_to_str(
  * are _to_str and _from_str functions defined elsewhere.
  *
  */
-#define generate_sorcery_enum_to_str(__struct, __substruct, __lc_param) \
+#define generate_sorcery_enum_to_str_ex(__struct, __substruct, __lc_param, __base_enum) \
 static int sorcery_ ## __lc_param ## _to_str(const void *obj, const intptr_t *args, char **buf) \
 { \
        const struct __struct *cfg = obj; \
-       *buf = ast_strdup(__lc_param ## _to_str(cfg->__substruct __lc_param)); \
+       *buf = ast_strdup(__base_enum ## _to_str(cfg->__substruct __lc_param)); \
        return *buf ? 0 : -1; \
 }
 
-#define generate_sorcery_enum_from_str_ex(__struct, __substruct, __lc_param, __unknown) \
+#define generate_sorcery_enum_to_str(__struct, __substruct, __lc_param) \
+       generate_sorcery_enum_to_str_ex(__struct, __substruct, __lc_param, __lc_param)
+
+#define generate_sorcery_enum_from_str_ex(__struct, __substruct, __lc_param, __base_enum, __unknown) \
 static int sorcery_ ## __lc_param ## _from_str(const struct aco_option *opt, struct ast_variable *var, void *obj) \
 { \
        struct __struct *cfg = obj; \
-       cfg->__substruct __lc_param = __lc_param ## _from_str (var->value); \
-       if (cfg->__substruct __lc_param == __unknown) { \
+       cfg->__substruct __lc_param = __base_enum ## _from_str (var->value); \
+       if (cfg->__substruct __lc_param == __base_enum ## _ ## __unknown) { \
                ast_log(LOG_WARNING, "Unknown value '%s' specified for %s\n", \
                        var->value, var->name); \
                return -1; \
@@ -158,7 +167,7 @@ static int sorcery_ ## __lc_param ## _from_str(const struct aco_option *opt, str
 }
 
 #define generate_sorcery_enum_from_str(__struct, __substruct, __lc_param, __unknown) \
-       generate_sorcery_enum_from_str_ex(__struct, __substruct, __lc_param, __lc_param ## _ ## __unknown) \
+       generate_sorcery_enum_from_str_ex(__struct, __substruct, __lc_param, __lc_param, __unknown) \
 
 
 #define generate_sorcery_acl_to_str(__struct, __lc_param) \
@@ -263,14 +272,18 @@ struct ast_acl_list *get_default_acl_list(void);
  * Copy an enum from the source to the dest only if the source is
  * neither NOT_SET nor UNKNOWN
  */
-#define cfg_enum_copy(__cfg_dst, __cfg_src, __field) \
+#define cfg_enum_copy_ex(__cfg_dst, __cfg_src, __field, __not_set, __unknown) \
 ({ \
-       if (__cfg_src->__field != __field ## _NOT_SET \
-               && __cfg_src->__field != __field ## _UNKNOWN) { \
+       if (__cfg_src->__field != __not_set \
+               && __cfg_src->__field != __unknown) { \
                __cfg_dst->__field = __cfg_src->__field; \
        } \
 })
 
+#define cfg_enum_copy(__cfg_dst, __cfg_src, __field) \
+       cfg_enum_copy_ex(__cfg_dst, __cfg_src, __field, __field ## _NOT_SET, __field ## _UNKNOWN)
+
+
 /*!
  * \brief Attestation Service configuration for stir/shaken
  *
@@ -314,6 +327,7 @@ struct attestation_cfg {
         */
        AST_DECLARE_STRING_FIELDS();
        struct attestation_cfg_common acfg_common;
+       enum attest_level_enum unknown_tn_attest_level;
        int global_disable;
 };
 
@@ -412,6 +426,7 @@ struct profile_cfg {
        struct attestation_cfg_common acfg_common;
        struct verification_cfg_common vcfg_common;
        enum endpoint_behavior_enum endpoint_behavior;
+       enum attest_level_enum unknown_tn_attest_level;
        struct profile_cfg *eprofile;
 };
 
@@ -486,13 +501,13 @@ int tn_config_unload(void);
                __stringify(DEFAULT_ ## name), OPT_UINT_T, 0, \
                FLDSET(struct object, field))
 
-#define enum_option_register_ex(sorcery, CONFIG_TYPE, name, field, nodoc) \
+#define enum_option_register_ex(sorcery, CONFIG_TYPE, name, field, function_prefix, nodoc) \
        ast_sorcery_object_field_register_custom ## nodoc(sorcery, CONFIG_TYPE, \
-               #name, field ## _to_str(DEFAULT_ ## field), \
+               #name, function_prefix ## _to_str(DEFAULT_ ## field), \
                sorcery_ ## field ## _from_str, sorcery_ ## field ## _to_str, NULL, 0, 0)
 
 #define enum_option_register(sorcery, CONFIG_TYPE, name, nodoc) \
-       enum_option_register_ex(sorcery, CONFIG_TYPE, name, name, nodoc)
+       enum_option_register_ex(sorcery, CONFIG_TYPE, name, name, name, nodoc)
 
 #define register_common_verification_fields(sorcery, object, CONFIG_TYPE, nodoc) \
 ({ \
@@ -510,7 +525,7 @@ int tn_config_unload(void);
        uint_option_register(sorcery, CONFIG_TYPE, object, max_cache_entry_age, vcfg_common.max_cache_entry_age, nodoc);\
        uint_option_register(sorcery, CONFIG_TYPE, object, max_cache_size, vcfg_common.max_cache_size, nodoc);\
 \
-       enum_option_register_ex(sorcery, CONFIG_TYPE, failure_action, stir_shaken_failure_action, nodoc); \
+       enum_option_register_ex(sorcery, CONFIG_TYPE, failure_action, stir_shaken_failure_action, stir_shaken_failure_action, nodoc); \
        enum_option_register(sorcery, CONFIG_TYPE, use_rfc9410_responses, nodoc); \
        enum_option_register(sorcery, CONFIG_TYPE, \
                relax_x5u_port_scheme_restrictions, nodoc); \
index 9ba26688fbf9a40c424fd04ed58608d4a4e22611..f00d72f9b31243ab112d844b3b145e66379e4bc5 100644 (file)
@@ -498,11 +498,13 @@ static int pem_file_cb(const char *dir_name, const char *filename, void *obj)
        if (lstat(filename_merged, &statbuf)) {
                printf("Error reading path stats - %s: %s\n",
                                        filename_merged, strerror(errno));
+               ast_free(filename_merged);
                return -1;
        }
 
        /* We only want the symlinks from the directory */
        if (!S_ISLNK(statbuf.st_mode)) {
+               ast_free(filename_merged);
                return 0;
        }
 
@@ -512,6 +514,7 @@ static int pem_file_cb(const char *dir_name, const char *filename, void *obj)
                rc = crypto_load_store_from_cert_file(data->store, filename_merged);
        }
 
+       ast_free(filename_merged);
        return rc;
 }
 
index 6e5a78a4482b653ba8a1c251874330be931f2577..e3379ebe3bbf6b7bdc96d6c184f1ffdb17b9c98c 100644 (file)
@@ -54,6 +54,7 @@
 #define DEFAULT_private_key_file NULL
 #define DEFAULT_public_cert_url NULL
 #define DEFAULT_attest_level attest_level_NOT_SET
+#define DEFAULT_unknown_tn_attest_level attest_level_NOT_SET
 #define DEFAULT_send_mky send_mky_NOT_SET
 
 static void profile_destructor(void *obj)
@@ -167,6 +168,9 @@ static struct profile_cfg *create_effective_profile(
                return NULL;
        }
 
+       cfg_enum_copy_ex(eprofile, acfg, unknown_tn_attest_level,
+               attest_level_NOT_SET, attest_level_UNKNOWN);
+
        rc = as_copy_cfg_common(id, &eprofile->acfg_common,
                &base_profile->acfg_common);
        if (rc != 0) {
@@ -174,6 +178,10 @@ static struct profile_cfg *create_effective_profile(
                return NULL;
        }
 
+       cfg_enum_copy_ex(eprofile, base_profile, unknown_tn_attest_level,
+               attest_level_NOT_SET, attest_level_UNKNOWN);
+
+
        eprofile->endpoint_behavior = base_profile->endpoint_behavior;
 
        if (eprofile->endpoint_behavior == endpoint_behavior_ON) {
@@ -252,6 +260,9 @@ generate_vcfg_common_sorcery_handlers(profile_cfg);
 generate_sorcery_enum_from_str(profile_cfg, , endpoint_behavior, UNKNOWN);
 generate_sorcery_enum_to_str(profile_cfg, , endpoint_behavior);
 
+generate_sorcery_enum_from_str_ex(profile_cfg,,unknown_tn_attest_level, attest_level, UNKNOWN);
+generate_sorcery_enum_to_str_ex(profile_cfg,,unknown_tn_attest_level, attest_level);
+
 static char *cli_profile_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
        struct profile_cfg *profile;
@@ -445,6 +456,9 @@ int profile_load(void)
 
        ast_sorcery_object_field_register_nodoc(sorcery, "eprofile", "type", "", OPT_NOOP_T, 0, 0);
        enum_option_register(sorcery, "eprofile", endpoint_behavior, _nodoc);
+       enum_option_register_ex(sorcery, "eprofile", unknown_tn_attest_level,
+               unknown_tn_attest_level, attest_level,_nodoc);
+
        register_common_verification_fields(sorcery, profile_cfg, "eprofile", _nodoc);
        register_common_attestation_fields(sorcery, profile_cfg, "eprofile", _nodoc);
 
@@ -460,6 +474,9 @@ int profile_load(void)
 
        ast_sorcery_object_field_register(sorcery, CONFIG_TYPE, "type", "", OPT_NOOP_T, 0, 0);
        enum_option_register(sorcery, CONFIG_TYPE, endpoint_behavior,);
+       enum_option_register_ex(sorcery, CONFIG_TYPE, unknown_tn_attest_level,
+               unknown_tn_attest_level, attest_level,);
+
        register_common_verification_fields(sorcery, profile_cfg, CONFIG_TYPE,);
        register_common_attestation_fields(sorcery, profile_cfg, CONFIG_TYPE,);
 
index 6663ce9a16c908998a26ff5d88de54374c88c51b..618c114d007d23aca46138327503bcd6ca3472fc 100644 (file)
                                <configOption name="attest_level">
                                        <synopsis>Attestation level</synopsis>
                                </configOption>
+                               <configOption name="unknown_tn_attest_level">
+                                       <synopsis>Attestation level to use for unknown TNs</synopsis>
+                                       <description><para>
+                                       Normally if a callerid TN isn't configured in stir_shaken.conf
+                                       no Identity header will be created. If this option is set,
+                                       however, an Identity header will be sent using this
+                                       attestation level.  Since there's no TN object, you must
+                                       ensure that a private_key_file and public_cert_url are
+                                       configured in the attestation or profile objects for
+                                       this to work.
+                                       </para></description>
+                               </configOption>
                                <configOption name="check_tn_cert_public_url" default="false">
                                        <synopsis>On load, Retrieve all TN's certificates and validate their dates</synopsis>
                                </configOption>
                                <xi:include xpointer="xpointer(/docs/configInfo[@name='res_stir_shaken']/configFile[@name='stir_shaken.conf']/configObject[@name='attestation']/configOption[@name='private_key_file'])" />
                                <xi:include xpointer="xpointer(/docs/configInfo[@name='res_stir_shaken']/configFile[@name='stir_shaken.conf']/configObject[@name='attestation']/configOption[@name='public_cert_url'])" />
                                <xi:include xpointer="xpointer(/docs/configInfo[@name='res_stir_shaken']/configFile[@name='stir_shaken.conf']/configObject[@name='attestation']/configOption[@name='attest_level'])" />
+                               <xi:include xpointer="xpointer(/docs/configInfo[@name='res_stir_shaken']/configFile[@name='stir_shaken.conf']/configObject[@name='attestation']/configOption[@name='unknown_tn_attest_level'])" />
                                <xi:include xpointer="xpointer(/docs/configInfo[@name='res_stir_shaken']/configFile[@name='stir_shaken.conf']/configObject[@name='attestation']/configOption[@name='send_mky'])" />
                                <configOption name="endpoint_behavior" default="off">
                                        <synopsis>Actions performed when an endpoint references this profile</synopsis>
index e23f1df191bc3fa787e754062a44af7538ec127c..7396a5091a95a304acd36d6ad4468f5012ff9aa5 100644 (file)
@@ -16,6 +16,8 @@
  * at the top of the source tree.
  */
 
+#define _TRACE_PREFIX_ "tc",__LINE__, ""
+
 #include "asterisk.h"
 
 #include <sys/stat.h>
@@ -108,31 +110,46 @@ static void *etn_alloc(const char *name)
 
 struct tn_cfg *tn_get_etn(const char *id, struct profile_cfg *eprofile)
 {
+       const char *profile_id = eprofile ? ast_sorcery_object_get_id(eprofile) : "unknown";
        RAII_VAR(struct tn_cfg *, tn,
                ast_sorcery_retrieve_by_id(get_sorcery(), CONFIG_TYPE, S_OR(id, "")),
                ao2_cleanup);
-       struct tn_cfg *etn = etn_alloc(id);
+       RAII_VAR(struct tn_cfg *, etn, etn_alloc(id), ao2_cleanup);
+       enum attest_level_enum effective_al = attest_level_NOT_SET;
        int rc = 0;
+       SCOPE_ENTER(3, "%s:%s: Getting effective TN\n", profile_id, S_OR(id, ""));
 
-       if (!tn || !eprofile || !etn) {
-               ao2_cleanup(etn);
-               return NULL;
+       if (ast_strlen_zero(id) || !eprofile || !etn) {
+               SCOPE_EXIT_RTN_VALUE(NULL, "Missing params\n");
+       }
+
+       if (!tn) {
+               if (eprofile->unknown_tn_attest_level != attest_level_NOT_SET
+                       && eprofile->unknown_tn_attest_level != attest_level_UNKNOWN) {
+                       effective_al = eprofile->unknown_tn_attest_level;
+                       ast_trace(-1, "%s:%s: TN not found. Using unknown_tn_attest_level %s\n",
+                               profile_id, id, attest_level_to_str(effective_al));
+               } else {
+                       SCOPE_EXIT_RTN_VALUE(NULL, "%s:%s: TN not found and unknown_tn_attest_level not set\n", profile_id, id);
+               }
        }
 
        /* Initialize with the acfg from the eprofile first */
        rc = as_copy_cfg_common(id, &etn->acfg_common,
                &eprofile->acfg_common);
        if (rc != 0) {
-               ao2_cleanup(etn);
-               return NULL;
+               SCOPE_EXIT_RTN_VALUE(NULL, "%s:%s: Couldn't copy from eprofile\n", profile_id, id);
        }
 
        /* Overwrite with anything in the TN itself */
-       rc = as_copy_cfg_common(id, &etn->acfg_common,
-               &tn->acfg_common);
-       if (rc != 0) {
-               ao2_cleanup(etn);
-               return NULL;
+       if (tn) {
+               rc = as_copy_cfg_common(id, &etn->acfg_common,
+                       &tn->acfg_common);
+               if (rc != 0) {
+                       SCOPE_EXIT_RTN_VALUE(NULL, "%s:%s: Couldn't copy from tn\n", profile_id, id);
+               }
+       } else {
+               etn->acfg_common.attest_level = effective_al;
        }
 
        /*
@@ -141,7 +158,7 @@ struct tn_cfg *tn_get_etn(const char *id, struct profile_cfg *eprofile)
         * the same TN could be used with multiple profiles.
         */
 
-       return etn;
+       SCOPE_EXIT_RTN_VALUE(ao2_bump(etn), "%s:%s: Done\n", profile_id, id);
 }
 
 static int tn_apply(const struct ast_sorcery *sorcery, void *obj)
index ef68ffc83ec461152dc8f0ec995a5a1184ea0ff7..81199747c38a115de7b34a5ec9ced369f842df75 100644 (file)
  * at the top of the source tree.
  */
 
+#define _TRACE_PREFIX_ "vc",__LINE__, ""
+
 #include "asterisk.h"
 
 #include "asterisk/cli.h"
+#include "asterisk/logger.h"
 #include "stir_shaken.h"
 
 #define CONFIG_TYPE "verification"