[Asterisk-code-review] res_stir_shaken: Initial commit and reading private key. (asterisk[master])

Kevin Harwell asteriskteam at digium.com
Wed Mar 25 18:04:24 CDT 2020


Kevin Harwell has submitted this change. ( https://gerrit.asterisk.org/c/asterisk/+/13960 )

Change subject: res_stir_shaken: Initial commit and reading private key.
......................................................................

res_stir_shaken: Initial commit and reading private key.

This commit sets up some of the initial framework for the module and
adds a way to read the private key from the specified file, which will
then be appended to the certificate object. This works fine for now, but
eventually some other structure will likely need to be used to store all
this information. Similarly, the caller_id_number is specified on the
certificate config object, but in the end we will want that information
to be tied to the certificate itself and read it from there.

A method has been added that will retrieve the private key associated
with the caller_id_number passed in. Tab completion for certificates and
stores has also been added.

Change-Id: Ic4bc1416fab5d6afe15a8e2d32f7ddd4e023295f
---
A include/asterisk/res_stir_shaken.h
M res/Makefile
A res/res_pjsip_stir_shaken.c
A res/res_stir_shaken.c
A res/res_stir_shaken/certificate.c
A res/res_stir_shaken/certificate.h
A res/res_stir_shaken/general.c
A res/res_stir_shaken/general.h
A res/res_stir_shaken/stir_shaken.c
A res/res_stir_shaken/stir_shaken.h
A res/res_stir_shaken/store.c
A res/res_stir_shaken/store.h
12 files changed, 1,270 insertions(+), 0 deletions(-)

Approvals:
  Kevin Harwell: Looks good to me, approved; Approved for Submit
  George Joseph: Looks good to me, but someone else must approve



diff --git a/include/asterisk/res_stir_shaken.h b/include/asterisk/res_stir_shaken.h
new file mode 100644
index 0000000..0c589a9
--- /dev/null
+++ b/include/asterisk/res_stir_shaken.h
@@ -0,0 +1,40 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2020, Sangoma Technologies Corporation
+ *
+ * Kevin Harwell <kharwell at sangoma.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.
+ */
+#ifndef _RES_STIR_SHAKEN_H
+#define _RES_STIR_SHAKEN_H
+
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+
+/*!
+ * \brief Retrieve the stir/shaken sorcery context
+ *
+ * \retval The stir/shaken sorcery context
+ */
+struct ast_sorcery *ast_stir_shaken_sorcery(void);
+
+/*!
+ * \brief Get the private key associated with a caller id
+ *
+ * \param caller_id_number The caller id used to look up the private key
+ *
+ * \retval The private key
+ */
+EVP_PKEY *ast_stir_shaken_get_private_key(const char *caller_id_number);
+
+#endif /* _RES_STIR_SHAKEN_H */
diff --git a/res/Makefile b/res/Makefile
index b4f50b7..fc48611 100644
--- a/res/Makefile
+++ b/res/Makefile
@@ -71,6 +71,7 @@
 $(call MOD_ADD_C,res_ari,ari/cli.c ari/config.c ari/ari_websockets.c)
 $(call MOD_ADD_C,res_ari_model,ari/ari_model_validators.c)
 $(call MOD_ADD_C,res_stasis_recording,stasis_recording/stored.c)
+$(call MOD_ADD_C,res_stir_shaken,$(wildcard res_stir_shaken/*.c))
 
 res_parking.o: _ASTCFLAGS+=$(AST_NO_FORMAT_TRUNCATION)
 
diff --git a/res/res_pjsip_stir_shaken.c b/res/res_pjsip_stir_shaken.c
new file mode 100644
index 0000000..58b0cc5
--- /dev/null
+++ b/res/res_pjsip_stir_shaken.c
@@ -0,0 +1,49 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2020, Sangoma Technologies Corporation
+ *
+ * Kevin Harwell <kharwell at 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.
+ */
+
+/*** MODULEINFO
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include "asterisk/module.h"
+
+#include "asterisk/res_stir_shaken.h"
+
+static int unload_module(void)
+{
+	return 0;
+}
+
+static int load_module(void)
+{
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+#undef AST_BUILDOPT_SUM
+#define AST_BUILDOPT_SUM ""
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER,
+				"PSIP STIR/SHAKEN Module for Asterisk",
+	.support_level = AST_MODULE_SUPPORT_CORE,
+	.load = load_module,
+	.unload = unload_module,
+	.load_pri = AST_MODPRI_DEFAULT,
+	.requires = "res_pjsip,res_stir_shaken",
+);
diff --git a/res/res_stir_shaken.c b/res/res_stir_shaken.c
new file mode 100644
index 0000000..a6656d0
--- /dev/null
+++ b/res/res_stir_shaken.c
@@ -0,0 +1,104 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2020, Sangoma Technologies Corporation
+ *
+ * Kevin Harwell <kharwell at 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.
+ */
+
+/*** MODULEINFO
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+#include "asterisk/module.h"
+#include "asterisk/sorcery.h"
+
+#include "asterisk/res_stir_shaken.h"
+#include "res_stir_shaken/stir_shaken.h"
+#include "res_stir_shaken/general.h"
+#include "res_stir_shaken/store.h"
+#include "res_stir_shaken/certificate.h"
+
+static struct ast_sorcery *stir_shaken_sorcery;
+
+struct ast_sorcery *ast_stir_shaken_sorcery(void)
+{
+	return stir_shaken_sorcery;
+}
+
+EVP_PKEY *ast_stir_shaken_get_private_key(const char *caller_id_number)
+{
+	return stir_shaken_certificate_get_private_key(caller_id_number);
+}
+
+static int reload_module(void)
+{
+	if (stir_shaken_sorcery) {
+		ast_sorcery_reload(stir_shaken_sorcery);
+	}
+
+	return 0;
+}
+
+static int unload_module(void)
+{
+	stir_shaken_certificate_unload();
+	stir_shaken_store_unload();
+	stir_shaken_general_unload();
+
+	ast_sorcery_unref(stir_shaken_sorcery);
+	stir_shaken_sorcery = NULL;
+
+	return 0;
+}
+
+static int load_module(void)
+{
+	if (!(stir_shaken_sorcery = ast_sorcery_open())) {
+		ast_log(LOG_ERROR, "stir/shaken - failed to open sorcery\n");
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	if (stir_shaken_general_load()) {
+		unload_module();
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	if (stir_shaken_store_load()) {
+		unload_module();
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	if (stir_shaken_certificate_load()) {
+		unload_module();
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	ast_sorcery_load(ast_stir_shaken_sorcery());
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+#undef AST_BUILDOPT_SUM
+#define AST_BUILDOPT_SUM ""
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER,
+				"STIR/SHAKEN Module for Asterisk",
+	.support_level = AST_MODULE_SUPPORT_CORE,
+	.load = load_module,
+	.unload = unload_module,
+	.reload = reload_module,
+	.load_pri = AST_MODPRI_CHANNEL_DEPEND - 1,
+);
diff --git a/res/res_stir_shaken/certificate.c b/res/res_stir_shaken/certificate.c
new file mode 100644
index 0000000..799cea1
--- /dev/null
+++ b/res/res_stir_shaken/certificate.c
@@ -0,0 +1,267 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2020, Sangoma Technologies Corporation
+ *
+ * Kevin Harwell <kharwell at 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 <sys/stat.h>
+
+#include "asterisk/cli.h"
+#include "asterisk/sorcery.h"
+
+#include "stir_shaken.h"
+#include "certificate.h"
+#include "asterisk/res_stir_shaken.h"
+
+#define CONFIG_TYPE "certificate"
+
+struct stir_shaken_certificate {
+	SORCERY_OBJECT(details);
+	AST_DECLARE_STRING_FIELDS(
+		/*! Path to a directory containing certificates */
+		AST_STRING_FIELD(path);
+		/*! URL to the public key */
+		AST_STRING_FIELD(public_key_url);
+		/*! The caller ID number associated with the certificate */
+		AST_STRING_FIELD(caller_id_number);
+	);
+	/*! The private key for the certificate */
+	EVP_PKEY *private_key;
+};
+
+static struct stir_shaken_certificate *stir_shaken_certificate_get(const char *id)
+{
+	return ast_sorcery_retrieve_by_id(ast_stir_shaken_sorcery(), CONFIG_TYPE, id);
+}
+
+static struct ao2_container *stir_shaken_certificate_get_all(void)
+{
+	return ast_sorcery_retrieve_by_fields(ast_stir_shaken_sorcery(), CONFIG_TYPE,
+		AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
+}
+
+static void stir_shaken_certificate_destructor(void *obj)
+{
+	struct stir_shaken_certificate *cfg = obj;
+
+	EVP_PKEY_free(cfg->private_key);
+	ast_string_field_free_memory(cfg);
+}
+
+static void *stir_shaken_certificate_alloc(const char *name)
+{
+	struct stir_shaken_certificate *cfg;
+
+	cfg = ast_sorcery_generic_alloc(sizeof(*cfg), stir_shaken_certificate_destructor);
+	if (!cfg) {
+		return NULL;
+	}
+
+	if (ast_string_field_init(cfg, 512)) {
+		ao2_ref(cfg, -1);
+		return NULL;
+	}
+
+	return cfg;
+}
+
+EVP_PKEY *stir_shaken_certificate_get_private_key(const char *caller_id_number)
+{
+	struct stir_shaken_certificate *cert;
+	struct ast_variable fields = {
+		.name = "caller_id_number",
+		.value = caller_id_number,
+		.next = NULL,
+	};
+
+	cert = ast_sorcery_retrieve_by_fields(ast_stir_shaken_sorcery(),
+		"certificate", AST_RETRIEVE_FLAG_DEFAULT, &fields);
+
+	if (cert) {
+		return cert->private_key;
+	}
+
+	return NULL;
+}
+
+static int stir_shaken_certificate_apply(const struct ast_sorcery *sorcery, void *obj)
+{
+	EVP_PKEY *private_key;
+	struct stir_shaken_certificate *cert = obj;
+
+	if (strlen(cert->caller_id_number) == 0) {
+		ast_log(LOG_ERROR, "Caller ID must be present\n");
+		return -1;
+	}
+
+	private_key = read_private_key(cert->path);
+	if (!private_key) {
+		return -1;
+	}
+
+	cert->private_key = private_key;
+
+	return 0;
+}
+
+static char *stir_shaken_certificate_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct stir_shaken_certificate *cfg;
+
+	switch(cmd) {
+	case CLI_INIT:
+		e->command = "stir_shaken show certificate";
+		e->usage =
+			"Usage: stir_shaken show certificate <id>\n"
+			"       Show the certificate stir/shaken settings for a given id\n";
+		return NULL;
+	case CLI_GENERATE:
+		if (a->pos == 3) {
+			return stir_shaken_tab_complete_name(a->word, stir_shaken_certificate_get_all());
+		} else {
+			return NULL;
+		}
+	}
+
+	if (a->argc != 4) {
+		return CLI_SHOWUSAGE;
+	}
+
+	cfg = stir_shaken_certificate_get(a->argv[3]);
+	stir_shaken_cli_show(cfg, a, 0);
+	ao2_cleanup(cfg);
+
+	return CLI_SUCCESS;
+}
+
+static char *stir_shaken_certificate_show_all(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct ao2_container *container;
+
+	switch(cmd) {
+	case CLI_INIT:
+		e->command = "stir_shaken show certificates";
+		e->usage =
+			"Usage: stir_shaken show certificates\n"
+			"       Show all configured certificates for stir/shaken\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	if (a->argc != 3) {
+		return CLI_SHOWUSAGE;
+	}
+
+	container = stir_shaken_certificate_get_all();
+	if (!container || ao2_container_count(container) == 0) {
+		ast_cli(a->fd, "No stir/shaken certificates found\n");
+		ao2_cleanup(container);
+		return CLI_SUCCESS;
+	}
+
+	ao2_callback(container, OBJ_NODATA, stir_shaken_cli_show, a);
+	ao2_ref(container, -1);
+
+	return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry stir_shaken_certificate_cli[] = {
+	AST_CLI_DEFINE(stir_shaken_certificate_show, "Show stir/shaken certificate configuration by id"),
+	AST_CLI_DEFINE(stir_shaken_certificate_show_all, "Show all stir/shaken certificate configurations"),
+};
+
+static int on_load_path(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+	struct stir_shaken_certificate *cfg = obj;
+	struct stat statbuf;
+
+	if (stat(var->value, &statbuf)) {
+		ast_log(LOG_ERROR, "stir/shaken - path '%s' not found\n", var->value);
+		return -1;
+	}
+
+	if (!S_ISREG(statbuf.st_mode)) {
+		ast_log(LOG_ERROR, "stir/shaken - path '%s' is not a file\n", var->value);
+		return -1;
+	}
+
+	return ast_string_field_set(cfg, path, var->value);
+}
+
+static int path_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct stir_shaken_certificate *cfg = obj;
+
+	*buf = ast_strdup(cfg->path);
+
+	return 0;
+}
+
+static int on_load_public_key_url(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+	struct stir_shaken_certificate *cfg = obj;
+
+	if (!ast_begins_with(var->value, "http")) {
+		ast_log(LOG_ERROR, "stir/shaken - public_key_url scheme must be 'http[s]'\n");
+		return -1;
+	}
+
+	return ast_string_field_set(cfg, public_key_url, var->value);
+}
+
+static int public_key_url_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct stir_shaken_certificate *cfg = obj;
+
+	*buf = ast_strdup(cfg->public_key_url);
+
+	return 0;
+}
+
+int stir_shaken_certificate_unload(void)
+{
+	ast_cli_unregister_multiple(stir_shaken_certificate_cli,
+		ARRAY_LEN(stir_shaken_certificate_cli));
+
+	return 0;
+}
+
+int stir_shaken_certificate_load(void)
+{
+	struct ast_sorcery *sorcery = ast_stir_shaken_sorcery();
+
+	ast_sorcery_apply_default(sorcery, CONFIG_TYPE, "config", "stir_shaken.conf,criteria=type=certificate");
+
+	if (ast_sorcery_object_register(sorcery, CONFIG_TYPE, stir_shaken_certificate_alloc,
+			NULL, stir_shaken_certificate_apply)) {
+		ast_log(LOG_ERROR, "stir/shaken - failed to register '%s' sorcery object\n", CONFIG_TYPE);
+		return -1;
+	}
+
+	ast_sorcery_object_field_register(sorcery, CONFIG_TYPE, "type", "", OPT_NOOP_T, 0, 0);
+	ast_sorcery_object_field_register_custom(sorcery, CONFIG_TYPE, "path", "",
+		on_load_path, path_to_str, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(sorcery, CONFIG_TYPE, "public_key_url", "",
+		on_load_public_key_url, public_key_url_to_str, NULL, 0, 0);
+	ast_sorcery_object_field_register(sorcery, CONFIG_TYPE, "caller_id_number", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct stir_shaken_certificate, caller_id_number));
+
+	ast_cli_register_multiple(stir_shaken_certificate_cli,
+		ARRAY_LEN(stir_shaken_certificate_cli));
+
+	return 0;
+}
diff --git a/res/res_stir_shaken/certificate.h b/res/res_stir_shaken/certificate.h
new file mode 100644
index 0000000..9d6ec73
--- /dev/null
+++ b/res/res_stir_shaken/certificate.h
@@ -0,0 +1,50 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2020, Sangoma Technologies Corporation
+ *
+ * Kevin Harwell <kharwell at sangoma.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.
+ */
+#ifndef _STIR_SHAKEN_CERTIFICATE_H
+#define _STIR_SHAKEN_CERTIFICATE_H
+
+#include <openssl/evp.h>
+
+struct ast_sorcery;
+
+/*!
+ * \brief Get the private key associated with a caller id
+ *
+ * \param caller_id_number The caller id used to look up the private key
+ *
+ * \retval NULL on failure
+ * \retval The private key on success
+ */
+EVP_PKEY *stir_shaken_certificate_get_private_key(const char *caller_id_number);
+
+/*!
+ * \brief Load time initialization for the stir/shaken 'certificate' configuration
+ *
+ * \retval 0 on success, -1 on error
+ */
+int stir_shaken_certificate_load(void);
+
+/*!
+ * \brief Unload time cleanup for the stir/shaken 'certificate' configuration
+ *
+ * \retval 0 on success, -1 on error
+ */
+int stir_shaken_certificate_unload(void);
+
+#endif /* _STIR_SHAKEN_CERTIFICATE_H */
+
diff --git a/res/res_stir_shaken/general.c b/res/res_stir_shaken/general.c
new file mode 100644
index 0000000..7e807bb
--- /dev/null
+++ b/res/res_stir_shaken/general.c
@@ -0,0 +1,264 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2020, Sangoma Technologies Corporation
+ *
+ * Kevin Harwell <kharwell at 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 "stir_shaken.h"
+#include "general.h"
+#include "asterisk/res_stir_shaken.h"
+
+#define CONFIG_TYPE "general"
+
+#define DEFAULT_CA_FILE ""
+#define DEFAULT_CA_PATH ""
+#define DEFAULT_CACHE_MAX_SIZE 1000
+
+struct stir_shaken_general {
+	SORCERY_OBJECT(details);
+	AST_DECLARE_STRING_FIELDS(
+		/*! File path to a certificate authority */
+		AST_STRING_FIELD(ca_file);
+		/*! File path to a chain of trust */
+		AST_STRING_FIELD(ca_path);
+	);
+	/*! Maximum size of public keys cache */
+	unsigned int cache_max_size;
+};
+
+static struct stir_shaken_general *default_config = NULL;
+
+struct stir_shaken_general *stir_shaken_general_get()
+{
+	struct stir_shaken_general *cfg;
+	struct ao2_container *container;
+
+	container = ast_sorcery_retrieve_by_fields(ast_stir_shaken_sorcery(), CONFIG_TYPE,
+		AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
+	if (!container || ao2_container_count(container) == 0) {
+		ao2_cleanup(container);
+		return ao2_bump(default_config);
+	}
+
+	cfg = ao2_find(container, NULL, 0);
+	ao2_ref(container, -1);
+
+	return cfg;
+}
+
+const char *ast_stir_shaken_ca_file(const struct stir_shaken_general *cfg)
+{
+	return cfg ? cfg->ca_file : DEFAULT_CA_FILE;
+}
+
+const char *ast_stir_shaken_ca_path(const struct stir_shaken_general *cfg)
+{
+	return cfg ? cfg->ca_path : DEFAULT_CA_PATH;
+}
+
+unsigned int ast_stir_shaken_cache_max_size(const struct stir_shaken_general *cfg)
+{
+	return cfg ? cfg->cache_max_size : DEFAULT_CACHE_MAX_SIZE;
+}
+
+static void stir_shaken_general_destructor(void *obj)
+{
+	struct stir_shaken_general *cfg = obj;
+
+	ast_string_field_free_memory(cfg);
+}
+
+static void *stir_shaken_general_alloc(const char *name)
+{
+	struct stir_shaken_general *cfg;
+
+	cfg = ast_sorcery_generic_alloc(sizeof(*cfg), stir_shaken_general_destructor);
+	if (!cfg) {
+		return NULL;
+	}
+
+	if (ast_string_field_init(cfg, 512)) {
+		ao2_ref(cfg, -1);
+		return NULL;
+	}
+
+	return cfg;
+}
+
+static int stir_shaken_general_apply(const struct ast_sorcery *sorcery, void *obj)
+{
+	return 0;
+}
+
+static void stir_shaken_general_loaded(const char *name, const struct ast_sorcery *sorcery,
+	const char *object_type, int reloaded)
+{
+	struct stir_shaken_general *cfg;
+
+	if (strcmp(object_type, CONFIG_TYPE)) {
+		/* Not interested */
+		return;
+	}
+
+	if (default_config) {
+		ao2_ref(default_config, -1);
+		default_config = NULL;
+	}
+
+	cfg = stir_shaken_general_get();
+	if (cfg) {
+		ao2_ref(cfg, -1);
+		return;
+	}
+
+	/* Use the default configuration if on is not specified */
+	default_config = ast_sorcery_alloc(sorcery, CONFIG_TYPE, NULL);
+	if (default_config) {
+		stir_shaken_general_apply(sorcery, default_config);
+	}
+}
+
+static const struct ast_sorcery_instance_observer stir_shaken_general_observer = {
+	.object_type_loaded = stir_shaken_general_loaded,
+};
+
+static char *stir_shaken_general_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct stir_shaken_general *cfg;
+
+	switch(cmd) {
+	case CLI_INIT:
+		e->command = "stir_shaken show general";
+		e->usage =
+			"Usage: stir_shaken show general\n"
+			"       Show the general stir/shaken settings\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	if (a->argc != 3) {
+		return CLI_SHOWUSAGE;
+	}
+
+	cfg = stir_shaken_general_get();
+	stir_shaken_cli_show(cfg, a, 0);
+	ao2_cleanup(cfg);
+
+	return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry stir_shaken_general_cli[] = {
+	AST_CLI_DEFINE(stir_shaken_general_show, "Show stir/shaken general configuration"),
+};
+
+static int on_load_ca_file(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+	struct stir_shaken_general *cfg = obj;
+
+	if (!ast_file_is_readable(var->value)) {
+		ast_log(LOG_ERROR, "stir/shaken - %s '%s' not found, or is unreadable\n",
+				var->name, var->value);
+		return -1;
+	}
+
+	return ast_string_field_set(cfg, ca_file, var->value);
+}
+
+static int ca_file_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct stir_shaken_general *cfg = obj;
+
+	*buf = ast_strdup(cfg->ca_file);
+
+	return 0;
+}
+
+static int on_load_ca_path(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+	struct stir_shaken_general *cfg = obj;
+
+	if (!ast_file_is_readable(var->value)) {
+		ast_log(LOG_ERROR, "stir/shaken - %s '%s' not found, or is unreadable\n",
+				var->name, var->value);
+		return -1;
+	}
+
+	return ast_string_field_set(cfg, ca_path, var->value);
+}
+
+static int ca_path_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct stir_shaken_general *cfg = obj;
+
+	*buf = ast_strdup(cfg->ca_path);
+
+	return 0;
+}
+
+int stir_shaken_general_unload(void)
+{
+	ast_cli_unregister_multiple(stir_shaken_general_cli,
+		ARRAY_LEN(stir_shaken_general_cli));
+
+	ast_sorcery_instance_observer_remove(ast_stir_shaken_sorcery(),
+		&stir_shaken_general_observer);
+
+	if (default_config) {
+		ao2_ref(default_config, -1);
+		default_config = NULL;
+	}
+
+	return 0;
+}
+
+int stir_shaken_general_load(void)
+{
+	struct ast_sorcery *sorcery = ast_stir_shaken_sorcery();
+
+	ast_sorcery_apply_default(sorcery, CONFIG_TYPE, "config",
+		"stir_shaken.conf,criteria=type=general,single_object=yes,explicit_name=general");
+
+	if (ast_sorcery_object_register(sorcery, CONFIG_TYPE, stir_shaken_general_alloc,
+			NULL, stir_shaken_general_apply)) {
+		ast_log(LOG_ERROR, "stir/shaken - failed to register '%s' sorcery object\n", CONFIG_TYPE);
+		return -1;
+	}
+
+	ast_sorcery_object_field_register(sorcery, CONFIG_TYPE, "type", "", OPT_NOOP_T, 0, 0);
+	ast_sorcery_object_field_register_custom(sorcery, CONFIG_TYPE, "ca_file",
+		DEFAULT_CA_FILE, on_load_ca_file, ca_file_to_str, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(sorcery, CONFIG_TYPE, "ca_path",
+		DEFAULT_CA_PATH, on_load_ca_path, ca_path_to_str, NULL, 0, 0);
+	ast_sorcery_object_field_register(sorcery, CONFIG_TYPE, "cache_max_size",
+		__stringify(DEFAULT_CACHE_MAX_SIZE), OPT_UINT_T, 0,
+		FLDSET(struct stir_shaken_general, cache_max_size));
+
+	if (ast_sorcery_instance_observer_add(sorcery, &stir_shaken_general_observer)) {
+		ast_log(LOG_ERROR, "stir/shaken - failed to register loaded observer for '%s' "
+				"sorcery object type\n", CONFIG_TYPE);
+		return -1;
+	}
+
+	ast_cli_register_multiple(stir_shaken_general_cli,
+		ARRAY_LEN(stir_shaken_general_cli));
+
+	return 0;
+}
diff --git a/res/res_stir_shaken/general.h b/res/res_stir_shaken/general.h
new file mode 100644
index 0000000..0c0c5f0
--- /dev/null
+++ b/res/res_stir_shaken/general.h
@@ -0,0 +1,89 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2020, Sangoma Technologies Corporation
+ *
+ * Kevin Harwell <kharwell at sangoma.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.
+ */
+#ifndef _STIR_SHAKEN_GENERAL_H
+#define _STIR_SHAKEN_GENERAL_H
+
+struct ast_sorcery;
+
+/*!
+ * \brief General configuration for stir/shaken
+ */
+struct stir_shaken_general;
+
+/*!
+ * \brief Retrieve the stir/shaken 'general' configuration object
+ *
+ * A default configuration object is returned if no configuration was specified.
+ * As well, NULL can be returned if there is no configuration, and a problem
+ * occurred while loading the defaults.
+ *
+ * \note Object is returned with a reference that the caller is responsible
+ *     for de-referencing.
+ *
+ * \retval A 'general' configuration object, or NULL
+ */
+struct stir_shaken_general *stir_shaken_general_get(void);
+
+/*!
+ * \brief Retrieve the 'ca_file' general configuration option value
+ *
+ * \note If a NULL configuration is given, then the default value is returned
+ *
+ * \param cfg A 'general' configuration object
+ *
+ * \retval The 'ca_file' value
+ */
+const char *ast_stir_shaken_ca_file(const struct stir_shaken_general *cfg);
+
+/*!
+ * \brief Retrieve the 'ca_path' general configuration option value
+ *
+ * \note If a NULL configuration is given, then the default value is returned
+ *
+ * \param cfg A 'general' configuration object
+ *
+ * \retval The 'ca_path' value
+ */
+const char *ast_stir_shaken_ca_path(const struct stir_shaken_general *cfg);
+
+/*!
+ * \brief Retrieve the 'cache_max_size' general configuration option value
+ *
+ * \note If a NULL configuration is given, then the default value is returned
+ *
+ * \param cfg A 'general' configuration object
+ *
+ * \retval The 'cache_max_size' value
+ */
+unsigned int ast_stir_shaken_cache_max_size(const struct stir_shaken_general *cfg);
+
+/*!
+ * \brief Load time initialization for the stir/shaken 'general' configuration
+ *
+ * \retval 0 on success, -1 on error
+ */
+int stir_shaken_general_load(void);
+
+/*!
+ * \brief Unload time cleanup for the stir/shaken 'general' configuration
+ *
+ * \retval 0 on success, -1 on error
+ */
+int stir_shaken_general_unload(void);
+
+#endif /* _STIR_SHAKEN_GENERAL_H */
diff --git a/res/res_stir_shaken/stir_shaken.c b/res/res_stir_shaken/stir_shaken.c
new file mode 100644
index 0000000..5f5c054
--- /dev/null
+++ b/res/res_stir_shaken/stir_shaken.c
@@ -0,0 +1,113 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2020, Sangoma Technologies Corporation
+ *
+ * Kevin Harwell <kharwell at 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.
+ */
+
+/*! \file
+ *
+ * \brief Internal stir/shaken utilities
+ */
+
+#include "asterisk.h"
+
+#include "asterisk/cli.h"
+#include "asterisk/sorcery.h"
+
+#include "stir_shaken.h"
+#include "asterisk/res_stir_shaken.h"
+
+int stir_shaken_cli_show(void *obj, void *arg, int flags)
+{
+	struct ast_cli_args *a = arg;
+	struct ast_variable *options;
+	struct ast_variable *i;
+
+	if (!obj) {
+		ast_cli(a->fd, "No stir/shaken configuration found\n");
+		return 0;
+	}
+
+	options = ast_variable_list_sort(ast_sorcery_objectset_create2(
+		ast_stir_shaken_sorcery(), obj, AST_HANDLER_ONLY_STRING));
+	if (!options) {
+		return 0;
+	}
+
+	ast_cli(a->fd, "%s: %s\n", ast_sorcery_object_get_type(obj),
+		ast_sorcery_object_get_id(obj));
+
+	for (i = options; i; i = i->next) {
+		ast_cli(a->fd, "\t%s: %s\n", i->name, i->value);
+	}
+
+	ast_cli(a->fd, "\n");
+
+	ast_variables_destroy(options);
+
+	return 0;
+}
+
+char *stir_shaken_tab_complete_name(const char *word, struct ao2_container *container)
+{
+	void *obj;
+	struct ao2_iterator it;
+	int wordlen = strlen(word);
+	int ret;
+
+	it = ao2_iterator_init(container, 0);
+	while ((obj = ao2_iterator_next(&it))) {
+		if (!strncasecmp(word, ast_sorcery_object_get_id(obj), wordlen)) {
+			ret = ast_cli_completion_add(ast_strdup(ast_sorcery_object_get_id(obj)));
+			if (ret) {
+				ao2_ref(obj, -1);
+				break;
+			}
+		}
+		ao2_ref(obj, -1);
+	}
+	ao2_iterator_destroy(&it);
+
+	return NULL;
+}
+
+EVP_PKEY *read_private_key(const char *path)
+{
+	EVP_PKEY *private_key = NULL;
+	FILE *fp;
+
+	fp = fopen(path, "r");
+	if (!fp) {
+		ast_log(LOG_ERROR, "Failed to read private key file '%s'\n", path);
+		return NULL;
+	}
+
+	if (!PEM_read_PrivateKey(fp, &private_key, NULL, NULL)) {
+		ast_log(LOG_ERROR, "Failed to read private key from file '%s'\n", path);
+		fclose(fp);
+		return NULL;
+	}
+
+	if (EVP_PKEY_id(private_key) != EVP_PKEY_EC) {
+		ast_log(LOG_ERROR, "Private key from '%s' must be of type EVP_PKEY_EC\n", path);
+		fclose(fp);
+		EVP_PKEY_free(private_key);
+		return NULL;
+	}
+
+	fclose(fp);
+
+	return private_key;
+}
diff --git a/res/res_stir_shaken/stir_shaken.h b/res/res_stir_shaken/stir_shaken.h
new file mode 100644
index 0000000..933b3bb
--- /dev/null
+++ b/res/res_stir_shaken/stir_shaken.h
@@ -0,0 +1,54 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2020, Sangoma Technologies Corporation
+ *
+ * Kevin Harwell <kharwell at sangoma.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.
+ */
+#ifndef _STIR_SHAKEN_H
+#define _STIR_SHAKEN_H
+
+#include <openssl/evp.h>
+
+/*!
+ * \brief Output configuration settings to the Asterisk CLI
+ *
+ * \param obj A sorcery object containing configuration data
+ * \param arg Asterisk CLI argument object
+ * \param flags ao2 container flags
+ *
+ * \retval 0
+ */
+int stir_shaken_cli_show(void *obj, void *arg, int flags);
+
+/*!
+ * \brief Tab completion for name matching with STIR/SHAKEN CLI commands
+ *
+ * \param word The word to tab complete on
+ * \param container The sorcery container to iterate through
+ *
+ * \retval The tab completion options
+ */
+char *stir_shaken_tab_complete_name(const char *word, struct ao2_container *container);
+
+/*!
+ * \brief Reads the private key from the specified path
+ *
+ * \param path The path to the file containing the private key
+ *
+ * \retval NULL on failure
+ * \retval The private key on success
+ */
+EVP_PKEY *read_private_key(const char *path);
+
+#endif /* _STIR_SHAKEN_H */
diff --git a/res/res_stir_shaken/store.c b/res/res_stir_shaken/store.c
new file mode 100644
index 0000000..99a5038
--- /dev/null
+++ b/res/res_stir_shaken/store.c
@@ -0,0 +1,202 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2020, Sangoma Technologies Corporation
+ *
+ * Kevin Harwell <kharwell at 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 <sys/stat.h>
+
+#include "asterisk/cli.h"
+#include "asterisk/sorcery.h"
+
+#include "stir_shaken.h"
+#include "store.h"
+#include "asterisk/res_stir_shaken.h"
+
+#define CONFIG_TYPE "store"
+
+#define VARIABLE_SUBSTITUTE "${CERTIFICATE}"
+
+struct stir_shaken_store {
+	SORCERY_OBJECT(details);
+	AST_DECLARE_STRING_FIELDS(
+		/*! Path to a directory containing certificates */
+		AST_STRING_FIELD(path);
+		/*! URL to the public key */
+		AST_STRING_FIELD(public_key_url);
+	);
+};
+
+static struct stir_shaken_store *stir_shaken_store_get(const char *id)
+{
+	return ast_sorcery_retrieve_by_id(ast_stir_shaken_sorcery(), CONFIG_TYPE, id);
+}
+
+static struct ao2_container *stir_shaken_store_get_all(void)
+{
+	return ast_sorcery_retrieve_by_fields(ast_stir_shaken_sorcery(), CONFIG_TYPE,
+		AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
+}
+
+static void stir_shaken_store_destructor(void *obj)
+{
+	struct stir_shaken_store *cfg = obj;
+
+	ast_string_field_free_memory(cfg);
+}
+
+static void *stir_shaken_store_alloc(const char *name)
+{
+	struct stir_shaken_store *cfg;
+
+	cfg = ast_sorcery_generic_alloc(sizeof(*cfg), stir_shaken_store_destructor);
+	if (!cfg) {
+		return NULL;
+	}
+
+	if (ast_string_field_init(cfg, 512)) {
+		ao2_ref(cfg, -1);
+		return NULL;
+	}
+
+	return cfg;
+}
+
+static int stir_shaken_store_apply(const struct ast_sorcery *sorcery, void *obj)
+{
+	return 0;
+}
+
+static char *stir_shaken_store_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct stir_shaken_store *cfg;
+
+	switch(cmd) {
+	case CLI_INIT:
+		e->command = "stir_shaken show store";
+		e->usage =
+			"Usage: stir_shaken show store <id>\n"
+			"       Show the store stir/shaken settings for a given id\n";
+		return NULL;
+	case CLI_GENERATE:
+		if (a->pos == 3) {
+			return stir_shaken_tab_complete_name(a->word, stir_shaken_store_get_all());
+		} else {
+			return NULL;
+		};
+	}
+
+	if (a->argc != 4) {
+		return CLI_SHOWUSAGE;
+	}
+
+	cfg = stir_shaken_store_get(a->argv[3]);
+	stir_shaken_cli_show(cfg, a, 0);
+	ao2_cleanup(cfg);
+
+	return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry stir_shaken_store_cli[] = {
+	AST_CLI_DEFINE(stir_shaken_store_show, "Show stir/shaken store configuration by id"),
+};
+
+static int on_load_path(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+	struct stir_shaken_store *cfg = obj;
+	struct stat statbuf;
+
+	if (stat(var->value, &statbuf)) {
+		ast_log(LOG_ERROR, "stir/shaken - path '%s' not found\n", var->value);
+		return -1;
+	}
+
+	if (!S_ISDIR(statbuf.st_mode)) {
+		ast_log(LOG_ERROR, "stir/shaken - path '%s' is not a directory\n", var->value);
+		return -1;
+	}
+
+	return ast_string_field_set(cfg, path, var->value);
+}
+
+static int path_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct stir_shaken_store *cfg = obj;
+
+	*buf = ast_strdup(cfg->path);
+
+	return 0;
+}
+
+static int on_load_public_key_url(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+	struct stir_shaken_store *cfg = obj;
+
+	if (!ast_begins_with(var->value, "http")) {
+		ast_log(LOG_ERROR, "stir/shaken - public_key_url scheme must be 'http[s]'\n");
+		return -1;
+	}
+
+	if (!strstr(var->value, VARIABLE_SUBSTITUTE)) {
+		ast_log(LOG_ERROR, "stir/shaken - public_key_url must contain variable '%s' "
+				"used for substitution\n", VARIABLE_SUBSTITUTE);
+		return -1;
+	}
+
+	return ast_string_field_set(cfg, public_key_url, var->value);
+}
+
+static int public_key_url_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct stir_shaken_store *cfg = obj;
+
+	*buf = ast_strdup(cfg->public_key_url);
+
+	return 0;
+}
+
+int stir_shaken_store_unload(void)
+{
+	ast_cli_unregister_multiple(stir_shaken_store_cli,
+		ARRAY_LEN(stir_shaken_store_cli));
+
+	return 0;
+}
+
+int stir_shaken_store_load(void)
+{
+	struct ast_sorcery *sorcery = ast_stir_shaken_sorcery();
+
+	ast_sorcery_apply_default(sorcery, CONFIG_TYPE, "config", "stir_shaken.conf,criteria=type=store");
+
+	if (ast_sorcery_object_register(sorcery, CONFIG_TYPE, stir_shaken_store_alloc,
+			NULL, stir_shaken_store_apply)) {
+		ast_log(LOG_ERROR, "stir/shaken - failed to register '%s' sorcery object\n", CONFIG_TYPE);
+		return -1;
+	}
+
+	ast_sorcery_object_field_register(sorcery, CONFIG_TYPE, "type", "", OPT_NOOP_T, 0, 0);
+	ast_sorcery_object_field_register_custom(sorcery, CONFIG_TYPE, "path", "",
+		on_load_path, path_to_str, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(sorcery, CONFIG_TYPE, "public_key_url", "",
+		on_load_public_key_url, public_key_url_to_str, NULL, 0, 0);
+
+	ast_cli_register_multiple(stir_shaken_store_cli,
+		ARRAY_LEN(stir_shaken_store_cli));
+
+	return 0;
+}
diff --git a/res/res_stir_shaken/store.h b/res/res_stir_shaken/store.h
new file mode 100644
index 0000000..c2874cd
--- /dev/null
+++ b/res/res_stir_shaken/store.h
@@ -0,0 +1,37 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2020, Sangoma Technologies Corporation
+ *
+ * Kevin Harwell <kharwell at sangoma.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.
+ */
+#ifndef _STIR_SHAKEN_STORE_H
+#define _STIR_SHAKEN_STORE_H
+
+struct ast_sorcery;
+
+/*!
+ * \brief Load time initialization for the stir/shaken 'store' configuration
+ *
+ * \retval 0 on success, -1 on error
+ */
+int stir_shaken_store_load(void);
+
+/*!
+ * \brief Unload time cleanup for the stir/shaken 'store' configuration
+ *
+ * \retval 0 on success, -1 on error
+ */
+int stir_shaken_store_unload(void);
+
+#endif /* _STIR_SHAKEN_STORE_H */

-- 
To view, visit https://gerrit.asterisk.org/c/asterisk/+/13960
To unsubscribe, or for help writing mail filters, visit https://gerrit.asterisk.org/settings

Gerrit-Project: asterisk
Gerrit-Branch: master
Gerrit-Change-Id: Ic4bc1416fab5d6afe15a8e2d32f7ddd4e023295f
Gerrit-Change-Number: 13960
Gerrit-PatchSet: 2
Gerrit-Owner: Benjamin Keith Ford <bford at digium.com>
Gerrit-Reviewer: Friendly Automation
Gerrit-Reviewer: George Joseph <gjoseph at digium.com>
Gerrit-Reviewer: Kevin Harwell <kharwell at digium.com>
Gerrit-MessageType: merged
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20200325/41b9b874/attachment-0001.html>


More information about the asterisk-code-review mailing list