diff --git a/configs/samples/stir_shaken.conf.sample b/configs/samples/stir_shaken.conf.sample index d5e97979fd..3fe9d7e955 100644 --- a/configs/samples/stir_shaken.conf.sample +++ b/configs/samples/stir_shaken.conf.sample @@ -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 ;-- ======================================================================= diff --git a/res/res_stir_shaken/attestation_config.c b/res/res_stir_shaken/attestation_config.c index 7a5743c9f7..46ebb90399 100644 --- a/res/res_stir_shaken/attestation_config.c +++ b/res/res_stir_shaken/attestation_config.c @@ -15,9 +15,13 @@ * 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); diff --git a/res/res_stir_shaken/common_config.h b/res/res_stir_shaken/common_config.h index 3cb1795d78..e390b548b6 100644 --- a/res/res_stir_shaken/common_config.h +++ b/res/res_stir_shaken/common_config.h @@ -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); \ diff --git a/res/res_stir_shaken/crypto_utils.c b/res/res_stir_shaken/crypto_utils.c index 9ba26688fb..f00d72f9b3 100644 --- a/res/res_stir_shaken/crypto_utils.c +++ b/res/res_stir_shaken/crypto_utils.c @@ -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; } diff --git a/res/res_stir_shaken/profile_config.c b/res/res_stir_shaken/profile_config.c index 6e5a78a448..e3379ebe3b 100644 --- a/res/res_stir_shaken/profile_config.c +++ b/res/res_stir_shaken/profile_config.c @@ -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,); diff --git a/res/res_stir_shaken/stir_shaken_doc.xml b/res/res_stir_shaken/stir_shaken_doc.xml index 6663ce9a16..618c114d00 100644 --- a/res/res_stir_shaken/stir_shaken_doc.xml +++ b/res/res_stir_shaken/stir_shaken_doc.xml @@ -21,6 +21,18 @@ Attestation level + + Attestation level to use for unknown TNs + + 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. + + On load, Retrieve all TN's certificates and validate their dates @@ -228,6 +240,7 @@ + Actions performed when an endpoint references this profile diff --git a/res/res_stir_shaken/tn_config.c b/res/res_stir_shaken/tn_config.c index e23f1df191..7396a5091a 100644 --- a/res/res_stir_shaken/tn_config.c +++ b/res/res_stir_shaken/tn_config.c @@ -16,6 +16,8 @@ * at the top of the source tree. */ +#define _TRACE_PREFIX_ "tc",__LINE__, "" + #include "asterisk.h" #include @@ -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) diff --git a/res/res_stir_shaken/verification_config.c b/res/res_stir_shaken/verification_config.c index ef68ffc83e..81199747c3 100644 --- a/res/res_stir_shaken/verification_config.c +++ b/res/res_stir_shaken/verification_config.c @@ -16,9 +16,12 @@ * 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"