asterisk/res/res_stir_shaken/verification_config.c

441 lines
12 KiB
C

/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2023, Sangoma Technologies Corporation
*
* George Joseph <gjoseph@digium.com>
*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/
#include "asterisk.h"
#include "asterisk/cli.h"
#include "stir_shaken.h"
#define CONFIG_TYPE "verification"
#define DEFAULT_global_disable 0
#define DEFAULT_ca_file NULL
#define DEFAULT_ca_path NULL
#define DEFAULT_crl_file NULL
#define DEFAULT_crl_path NULL
static char DEFAULT_cert_cache_dir[PATH_MAX];
#define DEFAULT_curl_timeout 2
#define DEFAULT_max_iat_age 15
#define DEFAULT_max_date_header_age 15
#define DEFAULT_max_cache_entry_age 3600
#define DEFAULT_max_cache_size 1000
#define DEFAULT_stir_shaken_failure_action stir_shaken_failure_action_CONTINUE
#define DEFAULT_use_rfc9410_responses use_rfc9410_responses_NO
#define DEFAULT_relax_x5u_port_scheme_restrictions relax_x5u_port_scheme_restrictions_NO
#define DEFAULT_relax_x5u_path_restrictions relax_x5u_path_restrictions_NO
#define DEFAULT_load_system_certs load_system_certs_NO
static struct verification_cfg *empty_cfg = NULL;
#define STIR_SHAKEN_DIR_NAME "stir_shaken"
struct verification_cfg *vs_get_cfg(void)
{
struct verification_cfg *cfg = ast_sorcery_retrieve_by_id(get_sorcery(),
CONFIG_TYPE, CONFIG_TYPE);
if (cfg) {
return cfg;
}
return empty_cfg ? ao2_bump(empty_cfg) : NULL;
}
int vs_is_config_loaded(void)
{
struct verification_cfg *cfg = ast_sorcery_retrieve_by_id(get_sorcery(),
CONFIG_TYPE, CONFIG_TYPE);
ao2_cleanup(cfg);
return !!cfg;
}
generate_vcfg_common_sorcery_handlers(verification_cfg);
void vcfg_cleanup(struct verification_cfg_common *vcfg_common)
{
if (!vcfg_common) {
return;
}
ast_string_field_free_memory(vcfg_common);
if (vcfg_common->tcs) {
crypto_free_cert_store(vcfg_common->tcs);
}
ast_free_acl_list(vcfg_common->acl);
}
static void verification_destructor(void *obj)
{
struct verification_cfg *cfg = obj;
ast_string_field_free_memory(cfg);
vcfg_cleanup(&cfg->vcfg_common);
}
static void *verification_alloc(const char *name)
{
struct verification_cfg *cfg;
cfg = ast_sorcery_generic_alloc(sizeof(*cfg), verification_destructor);
if (!cfg) {
return NULL;
}
if (ast_string_field_init(cfg, 1024)) {
ao2_ref(cfg, -1);
return NULL;
}
/*
* The memory for vcfg_common actually comes from cfg
* due to the weirdness of the STRFLDSET macro used with
* sorcery. We just use a token amount of memory in
* this call so the initialize doesn't fail.
*/
if (ast_string_field_init(&cfg->vcfg_common, 8)) {
ao2_ref(cfg, -1);
return NULL;
}
return cfg;
}
int vs_copy_cfg_common(const char *id, struct verification_cfg_common *cfg_dst,
struct verification_cfg_common *cfg_src)
{
int rc = 0;
if (!cfg_dst || !cfg_src) {
return -1;
}
if (!cfg_dst->tcs && cfg_src->tcs) {
cfg_sf_copy_wrapper(id, cfg_dst, cfg_src, ca_file);
cfg_sf_copy_wrapper(id, cfg_dst, cfg_src, ca_path);
cfg_sf_copy_wrapper(id, cfg_dst, cfg_src, crl_file);
cfg_sf_copy_wrapper(id, cfg_dst, cfg_src, crl_path);
X509_STORE_up_ref(cfg_src->tcs);
cfg_dst->tcs = cfg_src->tcs;
}
cfg_sf_copy_wrapper(id, cfg_dst, cfg_src, cert_cache_dir);
cfg_uint_copy(cfg_dst, cfg_src, curl_timeout);
cfg_uint_copy(cfg_dst, cfg_src, max_iat_age);
cfg_uint_copy(cfg_dst, cfg_src, max_date_header_age);
cfg_uint_copy(cfg_dst, cfg_src, max_cache_entry_age);
cfg_uint_copy(cfg_dst, cfg_src, max_cache_size);
cfg_enum_copy(cfg_dst, cfg_src, stir_shaken_failure_action);
cfg_enum_copy(cfg_dst, cfg_src, use_rfc9410_responses);
cfg_enum_copy(cfg_dst, cfg_src, relax_x5u_port_scheme_restrictions);
cfg_enum_copy(cfg_dst, cfg_src, relax_x5u_path_restrictions);
cfg_enum_copy(cfg_dst, cfg_src, load_system_certs);
if (cfg_src->acl) {
ast_free_acl_list(cfg_dst->acl);
cfg_dst->acl = ast_duplicate_acl_list(cfg_src->acl);
}
return rc;
}
int vs_check_common_config(const char *id,
struct verification_cfg_common *vcfg_common)
{
SCOPE_ENTER(3, "%s: Checking common config\n", id);
if (!ast_strlen_zero(vcfg_common->ca_file)
&& !ast_file_is_readable(vcfg_common->ca_file)) {
SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR,
"%s: ca_file '%s' not found, or is unreadable\n",
id, vcfg_common->ca_file);
}
if (!ast_strlen_zero(vcfg_common->ca_path)
&& !ast_file_is_readable(vcfg_common->ca_path)) {
SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR,
"%s: ca_path '%s' not found, or is unreadable\n",
id, vcfg_common->ca_path);
}
if (!ast_strlen_zero(vcfg_common->crl_file)
&& !ast_file_is_readable(vcfg_common->crl_file)) {
SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR,
"%s: crl_file '%s' not found, or is unreadable\n",
id, vcfg_common->crl_file);
}
if (!ast_strlen_zero(vcfg_common->crl_path)
&& !ast_file_is_readable(vcfg_common->crl_path)) {
SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR,
"%s: crl_path '%s' not found, or is unreadable\n",
id, vcfg_common->crl_path);
}
if (!ast_strlen_zero(vcfg_common->ca_file)
|| !ast_strlen_zero(vcfg_common->ca_path)) {
int rc = 0;
if (!vcfg_common->tcs) {
vcfg_common->tcs = crypto_create_cert_store();
if (!vcfg_common->tcs) {
SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR,
"%s: Unable to create CA cert store\n", id);
}
}
rc = crypto_load_cert_store(vcfg_common->tcs,
vcfg_common->ca_file, vcfg_common->ca_path);
if (rc != 0) {
SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR,
"%s: Unable to load CA cert store from '%s' or '%s'\n",
id, vcfg_common->ca_file, vcfg_common->ca_path);
}
}
if (!ast_strlen_zero(vcfg_common->crl_file)
|| !ast_strlen_zero(vcfg_common->crl_path)) {
int rc = 0;
if (!vcfg_common->tcs) {
vcfg_common->tcs = crypto_create_cert_store();
if (!vcfg_common->tcs) {
SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR,
"%s: Unable to create CA cert store\n", id);
}
}
rc = crypto_load_cert_store(vcfg_common->tcs,
vcfg_common->crl_file, vcfg_common->crl_path);
if (rc != 0) {
SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR,
"%s: Unable to load CA CRL store from '%s' or '%s'\n",
id, vcfg_common->crl_file, vcfg_common->crl_path);
}
}
if (vcfg_common->tcs) {
if (ENUM_BOOL(vcfg_common->load_system_certs, load_system_certs)) {
X509_STORE_set_default_paths(vcfg_common->tcs);
}
if (!ast_strlen_zero(vcfg_common->crl_file)
|| !ast_strlen_zero(vcfg_common->crl_path)) {
X509_STORE_set_flags(vcfg_common->tcs, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);
}
}
if (!ast_strlen_zero(vcfg_common->cert_cache_dir)) {
FILE *fp;
char *testfile;
if (ast_asprintf(&testfile, "%s/testfile", vcfg_common->cert_cache_dir) <= 0) {
SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR,
"%s: Unable to allocate memory for testfile\n", id);
}
fp = fopen(testfile, "w+");
if (!fp) {
ast_free(testfile);
SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR,
"%s: cert_cache_dir '%s' was not writable\n",
id, vcfg_common->cert_cache_dir);
}
fclose(fp);
remove(testfile);
ast_free(testfile);
}
SCOPE_EXIT_RTN_VALUE(0, "%s: Done\n", id);
}
static char *special_addresses[] = {
"0.0.0.0/8",
"10.0.0.0/8",
"100.64.0.0/10",
"127.0.0.0/8",
"169.254.0.0/16",
"172.16.0.0/12",
"192.0.0.0/24",
"192.0.0.0/29",
"192.88.99.0/24",
"192.168.0.0/16",
"198.18.0.0/15",
"198.51.100.0/24",
"203.0.113.0/24",
"240.0.0.0/4",
"255.255.255.255/32",
"::1/128",
"::/128",
/* "64:ff9b::/96", IPv4-IPv6 translation addresses should probably not be blocked by default */
/* "::ffff:0:0/96", IPv4 mapped addresses should probably not be blocked by default */
"100::/64",
"2001::/23",
"2001::/32",
"2001:2::/48",
"2001:db8::/32",
"2001:10::/28",
/* "2002::/16", 6to4 should problably not be blocked by default */
"fc00::/7",
"fe80::/10",
};
static int verification_apply(const struct ast_sorcery *sorcery, void *obj)
{
struct verification_cfg *cfg = obj;
const char *id = ast_sorcery_object_get_id(cfg);
if (vs_check_common_config("verification", &cfg->vcfg_common) !=0) {
return -1;
}
if (!cfg->vcfg_common.acl) {
int error = 0;
int ignore;
int i;
ast_append_acl("permit", "0.0.0.0/0", &cfg->vcfg_common.acl, &error, &ignore);
if (error) {
ast_free_acl_list(cfg->vcfg_common.acl);
cfg->vcfg_common.acl = NULL;
ast_log(LOG_ERROR, "%s: Unable to create default acl rule for '%s: %s'\n",
id, "permit", "0.0.0.0/0");
return -1;
}
for (i = 0; i < ARRAY_LEN(special_addresses); i++) {
ast_append_acl("deny", special_addresses[i], &cfg->vcfg_common.acl, &error, &ignore);
if (error) {
ast_free_acl_list(cfg->vcfg_common.acl);
cfg->vcfg_common.acl = NULL;
ast_log(LOG_ERROR, "%s: Unable to create default acl rule for '%s: %s'\n",
id, "deny", special_addresses[i]);
return -1;
}
}
}
return 0;
}
static char *cli_verification_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
struct verification_cfg *cfg;
struct config_object_cli_data data = {
.title = "Default Verification",
.object_type = config_object_type_verification,
};
switch(cmd) {
case CLI_INIT:
e->command = "stir_shaken show verification";
e->usage =
"Usage: stir_shaken show verification\n"
" Show the stir/shaken verification settings\n";
return NULL;
case CLI_GENERATE:
return NULL;
}
if (a->argc != 3) {
return CLI_SHOWUSAGE;
}
cfg = vs_get_cfg();
config_object_cli_show(cfg, a, &data, 0);
ao2_cleanup(cfg);
return CLI_SUCCESS;
}
static struct ast_cli_entry verification_cli[] = {
AST_CLI_DEFINE(cli_verification_show, "Show stir/shaken verification configuration"),
};
int vs_config_reload(void)
{
struct ast_sorcery *sorcery = get_sorcery();
ast_sorcery_force_reload_object(sorcery, CONFIG_TYPE);
if (!vs_is_config_loaded()) {
ast_log(LOG_WARNING,"Stir/Shaken verification service disabled. Either there were errors in the 'verification' object in stir_shaken.conf or it was missing altogether.\n");
}
if (!empty_cfg) {
empty_cfg = verification_alloc(CONFIG_TYPE);
if (!empty_cfg) {
return -1;
}
empty_cfg->global_disable = 1;
}
return 0;
}
int vs_config_unload(void)
{
ast_cli_unregister_multiple(verification_cli,
ARRAY_LEN(verification_cli));
ao2_cleanup(empty_cfg);
return 0;
}
int vs_config_load(void)
{
struct ast_sorcery *sorcery = get_sorcery();
snprintf(DEFAULT_cert_cache_dir, sizeof(DEFAULT_cert_cache_dir), "%s/keys/%s/cache",
ast_config_AST_DATA_DIR, STIR_SHAKEN_DIR_NAME);
ast_sorcery_apply_default(sorcery, CONFIG_TYPE, "config",
"stir_shaken.conf,criteria=type=" CONFIG_TYPE ",single_object=yes,explicit_name=" CONFIG_TYPE);
if (ast_sorcery_object_register(sorcery, CONFIG_TYPE, verification_alloc,
NULL, verification_apply)) {
ast_log(LOG_ERROR, "stir/shaken - failed to register '%s' sorcery object\n", CONFIG_TYPE);
return -1;
}
ast_sorcery_object_field_register_nodoc(sorcery, CONFIG_TYPE, "type", "",
OPT_NOOP_T, 0, 0);
ast_sorcery_object_field_register(sorcery, CONFIG_TYPE, "global_disable",
DEFAULT_global_disable ? "yes" : "no",
OPT_YESNO_T, 1, FLDSET(struct verification_cfg, global_disable));
register_common_verification_fields(sorcery, verification_cfg, CONFIG_TYPE,);
ast_sorcery_load_object(sorcery, CONFIG_TYPE);
if (!vs_is_config_loaded()) {
ast_log(LOG_WARNING,"Stir/Shaken verification service disabled. Either there were errors in the 'verification' object in stir_shaken.conf or it was missing altogether.\n");
}
if (!empty_cfg) {
empty_cfg = verification_alloc(CONFIG_TYPE);
if (!empty_cfg) {
return -1;
}
empty_cfg->global_disable = 1;
}
ast_cli_register_multiple(verification_cli,
ARRAY_LEN(verification_cli));
return 0;
}