res_stir_shaken: Allow sending Identity headers for unknown TNs

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.
pull/1001/head
George Joseph 6 months ago committed by asterisk-org-access-app[bot]
parent 2694f78d03
commit 1646b78986

@ -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
;--
=======================================================================

@ -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);

@ -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); \

@ -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;
}

@ -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,);

@ -21,6 +21,18 @@
<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>
@ -228,6 +240,7 @@
<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>

@ -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)

@ -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"

Loading…
Cancel
Save