asterisk/res/res_stir_shaken/attestation_config.c

327 lines
8.9 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 "asterisk/sorcery.h"
#include "asterisk/paths.h"
#include "stir_shaken.h"
#define CONFIG_TYPE "attestation"
#define DEFAULT_global_disable 0
#define DEFAULT_check_tn_cert_public_url check_tn_cert_public_url_NO
#define DEFAULT_private_key_file NULL
#define DEFAULT_public_cert_url NULL
#define DEFAULT_attest_level attest_level_NOT_SET
#define DEFAULT_send_mky send_mky_NO
static struct attestation_cfg *empty_cfg = NULL;
struct attestation_cfg *as_get_cfg(void)
{
struct attestation_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 as_is_config_loaded(void)
{
struct attestation_cfg *cfg = ast_sorcery_retrieve_by_id(get_sorcery(),
CONFIG_TYPE, CONFIG_TYPE);
ao2_cleanup(cfg);
return !!cfg;
}
generate_acfg_common_sorcery_handlers(attestation_cfg);
void acfg_cleanup(struct attestation_cfg_common *acfg_common)
{
if (!acfg_common) {
return;
}
ast_string_field_free_memory(acfg_common);
ao2_cleanup(acfg_common->raw_key);
}
static void attestation_destructor(void *obj)
{
struct attestation_cfg *cfg = obj;
ast_string_field_free_memory(cfg);
acfg_cleanup(&cfg->acfg_common);
}
static void *attestation_alloc(const char *name)
{
struct attestation_cfg *cfg;
cfg = ast_sorcery_generic_alloc(sizeof(*cfg), attestation_destructor);
if (!cfg) {
return NULL;
}
if (ast_string_field_init(cfg, 1024)) {
ao2_ref(cfg, -1);
return NULL;
}
/*
* The memory for acfg_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->acfg_common, 8)) {
ao2_ref(cfg, -1);
return NULL;
}
return cfg;
}
int as_copy_cfg_common(const char *id, struct attestation_cfg_common *cfg_dst,
struct attestation_cfg_common *cfg_src)
{
int rc = 0;
if (!cfg_dst || !cfg_src) {
return -1;
}
cfg_sf_copy_wrapper(id, cfg_dst, cfg_src, private_key_file);
cfg_sf_copy_wrapper(id, cfg_dst, cfg_src, public_cert_url);
cfg_enum_copy(cfg_dst, cfg_src, attest_level);
cfg_enum_copy(cfg_dst, cfg_src, check_tn_cert_public_url);
cfg_enum_copy(cfg_dst, cfg_src, send_mky);
if (cfg_src->raw_key) {
/* Free and overwrite the destination */
ao2_cleanup(cfg_dst->raw_key);
cfg_dst->raw_key = ao2_bump(cfg_src->raw_key);
cfg_dst->raw_key_length = cfg_src->raw_key_length;
}
return rc;
}
int as_check_common_config(const char *id, struct attestation_cfg_common *acfg_common)
{
SCOPE_ENTER(3, "%s: Checking common config\n", id);
if (!ast_strlen_zero(acfg_common->private_key_file)
&& !ast_file_is_readable(acfg_common->private_key_file)) {
SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "%s: default_private_key_path %s is missing or not readable\n", id,
acfg_common->private_key_file);
}
if (ENUM_BOOL(acfg_common->check_tn_cert_public_url,
check_tn_cert_public_url)
&& !ast_strlen_zero(acfg_common->public_cert_url)) {
RAII_VAR(char *, public_cert_data, NULL, ast_std_free);
X509 *public_cert;
size_t public_cert_len;
int rc = 0;
long http_code;
SCOPE_ENTER(3 , "%s: Checking public cert url '%s'\n",
id, acfg_common->public_cert_url);
http_code = curl_download_to_memory(acfg_common->public_cert_url,
&public_cert_len, &public_cert_data, NULL);
if (http_code / 100 != 2) {
SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "%s: public_cert '%s' could not be downloaded\n", id,
acfg_common->public_cert_url);
}
public_cert = crypto_load_cert_from_memory(public_cert_data,
public_cert_len);
if (!public_cert) {
SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "%s: public_cert '%s' could not be parsed as a certificate\n", id,
acfg_common->public_cert_url);
}
rc = crypto_is_cert_time_valid(public_cert, 0);
X509_free(public_cert);
if (!rc) {
SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "%s: public_cert '%s' is not valid yet or has expired\n", id,
acfg_common->public_cert_url);
}
rc = crypto_has_private_key_from_memory(public_cert_data, public_cert_len);
if (rc) {
SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "%s: DANGER!!! public_cert_url '%s' has a private key in the file!!!\n", id,
acfg_common->public_cert_url);
}
SCOPE_EXIT("%s: Done\n", id);
}
if (!ast_strlen_zero(acfg_common->private_key_file)) {
EVP_PKEY *private_key;
RAII_VAR(unsigned char *, raw_key, NULL, ast_std_free);
private_key = crypto_load_privkey_from_file(acfg_common->private_key_file);
if (!private_key) {
SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "%s: Could not extract raw private key from file '%s'\n", id,
acfg_common->private_key_file);
}
acfg_common->raw_key_length = crypto_extract_raw_privkey(private_key, &raw_key);
EVP_PKEY_free(private_key);
if (acfg_common->raw_key_length == 0 || raw_key == NULL) {
SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "%s: Could not extract raw private key from file '%s'\n", id,
acfg_common->private_key_file);
}
/*
* We're making this an ao2 object so it can be referenced
* by a profile instead of having to copy it.
*/
acfg_common->raw_key = ao2_alloc(acfg_common->raw_key_length, NULL);
if (!acfg_common->raw_key) {
SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR,
"%s: Could not allocate memory for raw private key\n", id);
}
memcpy(acfg_common->raw_key, raw_key, acfg_common->raw_key_length);
}
SCOPE_EXIT_RTN_VALUE(0, "%s: Done\n", id);
}
static int attestation_apply(const struct ast_sorcery *sorcery, void *obj)
{
struct attestation_cfg *cfg = obj;
const char *id = ast_sorcery_object_get_id(cfg);
if (as_check_common_config(id, &cfg->acfg_common) != 0) {
return -1;
}
return 0;
}
static char *attestation_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
struct attestation_cfg *cfg;
struct config_object_cli_data data = {
.title = "Default Attestation",
.object_type = config_object_type_attestation,
};
switch(cmd) {
case CLI_INIT:
e->command = "stir_shaken show attestation";
e->usage =
"Usage: stir_shaken show attestation\n"
" Show the stir/shaken attestation settings\n";
return NULL;
case CLI_GENERATE:
return NULL;
}
if (a->argc != 3) {
return CLI_SHOWUSAGE;
}
cfg = as_get_cfg();
config_object_cli_show(cfg, a, &data, 0);
ao2_cleanup(cfg);
return CLI_SUCCESS;
}
static struct ast_cli_entry attestation_cli[] = {
AST_CLI_DEFINE(attestation_show, "Show stir/shaken attestation configuration"),
};
int as_config_reload(void)
{
struct ast_sorcery *sorcery = get_sorcery();
ast_sorcery_force_reload_object(sorcery, CONFIG_TYPE);
if (!as_is_config_loaded()) {
ast_log(LOG_WARNING,"Stir/Shaken attestation service disabled. Either there were errors in the 'attestation' object in stir_shaken.conf or it was missing altogether.\n");
}
if (!empty_cfg) {
empty_cfg = attestation_alloc(CONFIG_TYPE);
if (!empty_cfg) {
return -1;
}
empty_cfg->global_disable = 1;
}
return 0;
}
int as_config_unload(void)
{
ast_cli_unregister_multiple(attestation_cli,
ARRAY_LEN(attestation_cli));
ao2_cleanup(empty_cfg);
return 0;
}
int as_config_load(void)
{
struct ast_sorcery *sorcery = get_sorcery();
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, attestation_alloc,
NULL, attestation_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 attestation_cfg, global_disable));
register_common_attestation_fields(sorcery, attestation_cfg, CONFIG_TYPE,);
ast_sorcery_load_object(sorcery, CONFIG_TYPE);
if (!as_is_config_loaded()) {
ast_log(LOG_WARNING,"Stir/Shaken attestation service disabled. Either there were errors in the 'attestation' object in stir_shaken.conf or it was missing altogether.\n");
}
if (!empty_cfg) {
empty_cfg = attestation_alloc(CONFIG_TYPE);
if (!empty_cfg) {
return -1;
}
empty_cfg->global_disable = 1;
}
ast_cli_register_multiple(attestation_cli,
ARRAY_LEN(attestation_cli));
return 0;
}