From 8684a9bfea9223808e33edca9f91b8bd76379fd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zg=C3=BCr=20Kesim?= Date: Sun, 23 Jan 2022 01:31:02 +0100 Subject: [PATCH] [age_restriction] progress 13/n - major refactoring of extensions - extensions live now in a separate library, libtalerextensions - refactored all components using age_restriction accordingly - plumbing for plugin support for extensions roughly layed down --- configure.ac | 1 + src/Makefile.am | 1 + src/benchmark/Makefile.am | 1 + src/exchange-tools/Makefile.am | 1 + src/exchange-tools/taler-exchange-offline.c | 198 ++--------- src/exchange/Makefile.am | 1 + src/exchange/taler-exchange-httpd.c | 5 - src/exchange/taler-exchange-httpd.h | 10 - .../taler-exchange-httpd_extensions.c | 131 +------ src/exchange/taler-exchange-httpd_keys.c | 49 +-- ...ler-exchange-httpd_management_extensions.c | 38 +- src/extensions/Makefile.am | 30 ++ src/extensions/extension_age_restriction.c | 321 +++++++++++++++++ src/extensions/extensions.c | 333 ++++++++++++++++++ src/include/taler_exchange_service.h | 1 - src/include/taler_extensions.h | 150 ++++++-- src/include/taler_json_lib.h | 6 +- src/json/json.c | 4 +- src/json/json_helper.c | 2 +- src/lib/Makefile.am | 1 + src/lib/exchange_api_handle.c | 69 ++-- src/util/Makefile.am | 2 - src/util/extension_age_restriction.c | 169 --------- src/util/extensions.c | 49 --- 24 files changed, 936 insertions(+), 637 deletions(-) create mode 100644 src/extensions/Makefile.am create mode 100644 src/extensions/extension_age_restriction.c create mode 100644 src/extensions/extensions.c delete mode 100644 src/util/extension_age_restriction.c delete mode 100644 src/util/extensions.c diff --git a/configure.ac b/configure.ac index 6995b9b3e..99d2e534c 100644 --- a/configure.ac +++ b/configure.ac @@ -541,6 +541,7 @@ AC_CONFIG_FILES([Makefile src/exchange/Makefile src/exchangedb/Makefile src/exchange-tools/Makefile + src/extensions/Makefile src/lib/Makefile src/testing/Makefile src/benchmark/Makefile diff --git a/src/Makefile.am b/src/Makefile.am index 64d6020f4..5d46850c2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -18,6 +18,7 @@ SUBDIRS = \ include \ util \ json \ + extensions \ curl \ $(PQ_DIR) \ $(SQ_DIR) \ diff --git a/src/benchmark/Makefile.am b/src/benchmark/Makefile.am index 7c5ceefd6..87f1e7e5d 100644 --- a/src/benchmark/Makefile.am +++ b/src/benchmark/Makefile.am @@ -54,6 +54,7 @@ taler_exchange_benchmark_LDADD = \ $(top_builddir)/src/bank-lib/libtalerbank.la \ $(top_builddir)/src/json/libtalerjson.la \ $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/extensions/libtalerextensions.la \ -lgnunetjson \ -lgnunetcurl \ -lgnunetutil \ diff --git a/src/exchange-tools/Makefile.am b/src/exchange-tools/Makefile.am index b233a4b3a..ae53cb30b 100644 --- a/src/exchange-tools/Makefile.am +++ b/src/exchange-tools/Makefile.am @@ -25,6 +25,7 @@ taler_exchange_offline_LDADD = \ $(top_builddir)/src/lib/libtalerexchange.la \ $(top_builddir)/src/json/libtalerjson.la \ $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/extensions/libtalerextensions.la \ -lgnunetjson \ -lgnunetcurl \ -ljansson \ diff --git a/src/exchange-tools/taler-exchange-offline.c b/src/exchange-tools/taler-exchange-offline.c index 8db1fc9fa..6ad345ebf 100644 --- a/src/exchange-tools/taler-exchange-offline.c +++ b/src/exchange-tools/taler-exchange-offline.c @@ -1798,7 +1798,8 @@ upload_extensions (const char *exchange_url, { struct TALER_ExtensionConfigHash h_config; - if (GNUNET_OK != TALER_extension_config_hash (extensions, &h_config)) + if (GNUNET_OK != + TALER_JSON_extensions_config_hash (extensions, &h_config)) { GNUNET_JSON_parse_free (spec); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, @@ -3506,163 +3507,6 @@ do_setup (char *const *args) } -/** - * struct extension carries the information about an extension together with - * callbacks to parse the configuration and marshal it as JSON - */ -struct extension -{ - char *name; - bool enabled; - bool critical; - char *version; - void *config; - - enum GNUNET_GenericReturnValue (*parse_config)(struct extension *this, - const char *section); - json_t *(*config_json)(const struct extension *this); -}; - -#define EXT_PREFIX "exchange-extension-" - -#define DEFAULT_AGE_GROUPS "8:10:12:14:16:18:21" - -static enum GNUNET_GenericReturnValue -age_restriction_parse_config (struct extension *this, const char *section) -{ - char *age_groups = NULL; - struct TALER_AgeMask mask = {0}; - enum GNUNET_GenericReturnValue ret; - - ret = GNUNET_CONFIGURATION_get_value_yesno (kcfg, section, "ENABLED"); - - this->enabled = (GNUNET_YES == ret); - - if (! this->enabled) - return GNUNET_OK; - - if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (kcfg, - section, - "AGE_GROUPS", - &age_groups)) - age_groups = DEFAULT_AGE_GROUPS; - - if (GNUNET_OK != TALER_parse_age_group_string (age_groups, &mask)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - section, - "AGE_GROUPS"); - test_shutdown (); - global_ret = EXIT_NOTCONFIGURED; - return GNUNET_SYSERR; - } - - /* Don't look here. We just store the mask in/as the pointer .*/ - this->config = (void *) (size_t) mask.mask; - return GNUNET_OK; -} - - -static json_t * -age_restriction_json (const struct extension *this) -{ - struct TALER_AgeMask mask; - json_t *conf; - - if (! this->enabled) - return NULL; - - /* Don't look here. We just restore the mask from/as the pointer .*/ - mask.mask = (uint32_t) (size_t) this->config; - - conf = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ( - "age_groups", - TALER_age_mask_to_string (&mask))); - - return GNUNET_JSON_PACK ( - GNUNET_JSON_pack_bool ("critical", - this->critical), - GNUNET_JSON_pack_string ("version", - this->version), - GNUNET_JSON_pack_object_steal ("config", conf)); -} - - -static struct extension extensions[] = { - { - .name = "age_restriction", - .version = "1", - .config = 0, - .parse_config = &age_restriction_parse_config, - .config_json = &age_restriction_json, - }, - /* TODO: add p2p here */ - {0}, -}; - - -static const struct extension* -get_extension (const char *extension) -{ - for (const struct extension *known = extensions; - NULL != known->name; - known++) - { - if (0 == strncasecmp (extension, - known->name, - strlen (known->name))) - return known; - } - return NULL; -} - - -static void -collect_extensions (void *cls, const char *section) -{ - json_t *obj = (json_t *) cls; - const char *name; - const struct extension *extension; - - if (0 != global_ret) - return; - - if (0 != strncasecmp (section, - EXT_PREFIX, - sizeof(EXT_PREFIX) - 1)) - { - return; - } - - name = section + sizeof(EXT_PREFIX) - 1; - - if (NULL == (extension = get_extension (name))) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unsupported extension `%s` (section [%s]).\n", name, - section); - test_shutdown (); - global_ret = EXIT_NOTCONFIGURED; - return; - } - - if (GNUNET_OK != extension->parse_config ((struct extension *) extension, - section)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Couldn't parse configuration for extension `%s` (section [%s]).\n", - name, - section); - test_shutdown (); - global_ret = EXIT_NOTCONFIGURED; - return; - } - - json_object_set (obj, name, extension->config_json (extension)); -} - - /* * Print the current extensions as configured */ @@ -3672,10 +3516,21 @@ do_extensions_show (char *const *args) json_t *obj = json_object (); json_t *exts = json_object (); + const struct TALER_Extension *it; + + TALER_extensions_init (); + if (GNUNET_OK != TALER_extensions_load_taler_config (kcfg)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "error while loading taler config for extensions\n"); + return; + } + + for (it = TALER_extensions_get_head (); + NULL != it; + it = it->next) + json_object_set (exts, it->name, it->config_to_json (it)); - GNUNET_CONFIGURATION_iterate_sections (kcfg, - &collect_extensions, - exts); json_object_set (obj, "extensions", exts); GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, "%s\n", @@ -3695,13 +3550,24 @@ do_extensions_sign (char *const *args) json_t *extensions = json_object (); struct TALER_ExtensionConfigHash h_config; struct TALER_MasterSignatureP sig; + const struct TALER_Extension *it; - GNUNET_CONFIGURATION_iterate_sections (kcfg, - &collect_extensions, - extensions); + TALER_extensions_init (); - // TODO: check size of extensions? - if (GNUNET_OK != TALER_extension_config_hash (extensions, &h_config)) + if (GNUNET_OK != TALER_extensions_load_taler_config (kcfg)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "error while loading taler config for extensions\n"); + return; + } + + for (it = TALER_extensions_get_head (); + NULL != it; + it = it->next) + json_object_set (extensions, it->name, it->config_to_json (it)); + + if (GNUNET_OK != + TALER_JSON_extensions_config_hash (extensions, &h_config)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "error while hashing config for extensions\n"); diff --git a/src/exchange/Makefile.am b/src/exchange/Makefile.am index e7688f735..44487a3a7 100644 --- a/src/exchange/Makefile.am +++ b/src/exchange/Makefile.am @@ -119,6 +119,7 @@ taler_exchange_httpd_LDADD = \ $(top_builddir)/src/json/libtalerjson.la \ $(top_builddir)/src/exchangedb/libtalerexchangedb.la \ $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/extensions/libtalerextensions.la \ -lmicrohttpd \ -lgnunetcurl \ -lgnunetutil \ diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c index 5fe707304..ae5847d11 100644 --- a/src/exchange/taler-exchange-httpd.c +++ b/src/exchange/taler-exchange-httpd.c @@ -147,11 +147,6 @@ int TEH_check_invariants_flag; */ bool TEH_suicide; -/** - * Global register of extensions - */ -struct TALER_Extension **TEH_extensions; - /** * Signature of the configuration of all enabled extensions, * signed by the exchange's offline master key with purpose diff --git a/src/exchange/taler-exchange-httpd.h b/src/exchange/taler-exchange-httpd.h index 017d5520b..d3b1ba84a 100644 --- a/src/exchange/taler-exchange-httpd.h +++ b/src/exchange/taler-exchange-httpd.h @@ -201,21 +201,11 @@ extern volatile bool MHD_terminating; */ extern struct GNUNET_CURL_Context *TEH_curl_ctx; -/** - * The manifest of the available extensions, NULL terminated - */ -extern struct TALER_Extension **TEH_extensions; - /* * Signature of the offline master key of all enabled extensions' configuration */ extern struct TALER_MasterSignatureP TEH_extensions_sig; -/* TODO: this will not work anymore, once we have plugable extensions */ -#define TEH_extension_enabled(ext) (0 <= ext && TALER_Extension_MaxPredefined > \ - ext && \ - NULL != TEH_extensions[ext]->config) - /** * @brief Struct describing an URL and the handler for it. */ diff --git a/src/exchange/taler-exchange-httpd_extensions.c b/src/exchange/taler-exchange-httpd_extensions.c index 0245797d4..8edb24d40 100644 --- a/src/exchange/taler-exchange-httpd_extensions.c +++ b/src/exchange/taler-exchange-httpd_extensions.c @@ -27,106 +27,6 @@ #include "taler_extensions.h" #include -/** - * @brief implements the TALER_Extension.disable interface. - */ -void -age_restriction_disable (struct TALER_Extension *this) -{ - if (NULL == this) - return; - - this->config = NULL; - - if (NULL != this->config_json) - { - json_decref (this->config_json); - this->config_json = NULL; - } -} - - -/** - * @brief implements the TALER_Extension.parse_and_set_config interface. - * @param this if NULL, only tests the configuration - * @param config the configuration as json - */ -static enum GNUNET_GenericReturnValue -age_restriction_parse_and_set_config (struct TALER_Extension *this, - json_t *config) -{ - struct TALER_AgeMask mask = {0}; - enum GNUNET_GenericReturnValue ret; - - ret = TALER_agemask_parse_json (config, &mask); - if (GNUNET_OK != ret) - return ret; - - /* only testing the parser */ - if (this == NULL) - return GNUNET_OK; - - if (TALER_Extension_AgeRestriction != this->type) - return GNUNET_SYSERR; - - if (NULL != this->config) - GNUNET_free (this->config); - - this->config = GNUNET_malloc (sizeof(struct TALER_AgeMask)); - GNUNET_memcpy (this->config, &mask, sizeof(struct TALER_AgeMask)); - - if (NULL != this->config_json) - json_decref (this->config_json); - - this->config_json = config; - - return GNUNET_OK; -} - - -/** - * @brief implements the TALER_Extension.test_config interface. - */ -static enum GNUNET_GenericReturnValue -age_restriction_test_config (const json_t *config) -{ - struct TALER_AgeMask mask = {0}; - - return TALER_agemask_parse_json (config, &mask); -} - - -/* The extension for age restriction */ -static struct TALER_Extension extension_age_restriction = { - .type = TALER_Extension_AgeRestriction, - .name = "age_restriction", - .critical = false, - .version = "1", - .config = NULL, // disabled per default - .config_json = NULL, - .disable = &age_restriction_disable, - .test_config = &age_restriction_test_config, - .parse_and_set_config = &age_restriction_parse_and_set_config, -}; - -/** - * Create a list with the extensions for Age Restriction (and later Peer2Peer, - * ...) - */ -static struct TALER_Extension ** -get_known_extensions () -{ - - struct TALER_Extension **list = GNUNET_new_array ( - TALER_Extension_MaxPredefined + 1, - struct TALER_Extension *); - list[TALER_Extension_AgeRestriction] = &extension_age_restriction; - list[TALER_Extension_MaxPredefined] = NULL; - - return list; -} - - /** * Handler listening for extensions updates by other exchange * services. @@ -148,6 +48,7 @@ extension_update_event_cb (void *cls, { (void) cls; enum TALER_Extension_Type type; + const struct TALER_Extension *extension; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Received extensions update event\n"); @@ -161,12 +62,15 @@ extension_update_event_cb (void *cls, } type = *(enum TALER_Extension_Type *) extra; - /* TODO: This check will not work once we have plugable extensions */ - if (type <0 || type >= TALER_Extension_MaxPredefined) + + + /* Get the corresponding extension */ + extension = TALER_extensions_get_by_type (type); + if (NULL == extension) { GNUNET_break (0); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Oops, incorrect type for TALER_Extension_type\n"); + "Oops, unknown extension type: %d\n", type); return; } @@ -174,13 +78,10 @@ extension_update_event_cb (void *cls, { char *config_str = NULL; enum GNUNET_DB_QueryStatus qs; - struct TALER_Extension *extension; json_error_t err; json_t *config; enum GNUNET_GenericReturnValue ret; - extension = TEH_extensions[type]; - qs = TEH_plugin->get_extension_config (TEH_plugin->cls, extension->name, &config_str); @@ -193,10 +94,10 @@ extension_update_event_cb (void *cls, return; } - // No config found -> extension is disabled + // No config found -> disable extension if (NULL == config_str) { - extension->disable (extension); + extension->disable ((struct TALER_Extension *) extension); return; } @@ -214,7 +115,10 @@ extension_update_event_cb (void *cls, } // Call the parser for the extension - ret = extension->parse_and_set_config (extension, config); + ret = extension->load_json_config ( + (struct TALER_Extension *) extension, + config); + if (GNUNET_OK != ret) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, @@ -229,8 +133,7 @@ extension_update_event_cb (void *cls, enum GNUNET_GenericReturnValue TEH_extensions_init () { - /* Populate the known extensions. */ - TEH_extensions = get_known_extensions (); + TALER_extensions_init (); /* Set the event handler for updates */ struct GNUNET_DB_EventHeaderP ev = { @@ -249,8 +152,10 @@ TEH_extensions_init () } /* Trigger the initial load of configuration from the db */ - for (struct TALER_Extension **it = TEH_extensions; NULL != *it; it++) - extension_update_event_cb (NULL, &(*it)->type, sizeof((*it)->type)); + for (const struct TALER_Extension *it = TALER_extensions_get_head (); + NULL != it->next; + it = it->next) + extension_update_event_cb (NULL, &it->type, sizeof(it->type)); return GNUNET_OK; } diff --git a/src/exchange/taler-exchange-httpd_keys.c b/src/exchange/taler-exchange-httpd_keys.c index 7c64cdb75..de9b81cdd 100644 --- a/src/exchange/taler-exchange-httpd_keys.c +++ b/src/exchange/taler-exchange-httpd_keys.c @@ -744,14 +744,14 @@ load_age_mask (const char*section_name) { static const struct TALER_AgeMask null_mask = {0}; struct TALER_AgeMask age_mask = {0}; + /* TODO: optimize by putting this into global? */ const struct TALER_Extension *age_ext = - TEH_extensions[TALER_Extension_AgeRestriction]; + TALER_extensions_get_by_type (TALER_Extension_AgeRestriction); // Get the age mask from the extension, if configured - if (NULL != age_ext->config) - { + /* TODO: optimize by putting this into global? */ + if (TALER_extensions_is_enabled (age_ext)) age_mask = *(struct TALER_AgeMask *) age_ext->config; - } if (age_mask.mask == 0) { @@ -1706,14 +1706,14 @@ create_krd (struct TEH_KeyStateHandle *ksh, // Signal support for the configured, enabled extensions. { json_t *extensions = json_object (); - bool has_extensions; + bool has_extensions = false; + bool age_restriction_enabled = false; /* Fill in the configurations of the enabled extensions */ - for (struct TALER_Extension **it = TEH_extensions; - NULL != *it; - it++) + for (const struct TALER_Extension *extension = TALER_extensions_get_head (); + NULL != extension; + extension = extension->next) { - const struct TALER_Extension *extension = *it; json_t *ext; json_t *config_json; int r; @@ -1722,7 +1722,10 @@ create_krd (struct TEH_KeyStateHandle *ksh, if (NULL == extension->config) continue; + /* flag our findings so far */ has_extensions = true; + age_restriction_enabled = (extension->type == + TALER_Extension_AgeRestriction); GNUNET_assert (NULL != extension->config_json); @@ -1743,7 +1746,6 @@ create_krd (struct TEH_KeyStateHandle *ksh, extensions, extension->name, ext); - GNUNET_assert (0 == r); } @@ -1768,20 +1770,20 @@ create_krd (struct TEH_KeyStateHandle *ksh, r = json_object_update (keys, sig); GNUNET_assert (0 == r); } - } + // Special case for age restrictions: if enabled, provide the lits of + // age-restricted denominations. + if (age_restriction_enabled && + NULL != age_restricted_denoms) + { + GNUNET_assert ( + 0 == + json_object_set_new ( + keys, + "age_restricted_denoms", + age_restricted_denoms)); + } - // Special case for age restrictions: if enabled, provide the lits of - // age-restricted denominations. - if (TEH_extension_enabled (TALER_Extension_AgeRestriction) && - NULL != age_restricted_denoms) - { - GNUNET_assert ( - 0 == - json_object_set_new ( - keys, - "age_restricted_denoms", - age_restricted_denoms)); } @@ -1884,7 +1886,8 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh) GNUNET_assert (NULL != denoms); // If age restriction is enabled, initialize the array of age restricted denoms. - if (TEH_extension_enabled (TALER_Extension_AgeRestriction)) + /* TODO: optimize by putting this into global? */ + if (TALER_extensions_is_enabled_type (TALER_Extension_AgeRestriction)) { age_restricted_denoms = json_array (); GNUNET_assert (NULL != age_restricted_denoms); diff --git a/src/exchange/taler-exchange-httpd_management_extensions.c b/src/exchange/taler-exchange-httpd_management_extensions.c index 17db8e8c4..17b000067 100644 --- a/src/exchange/taler-exchange-httpd_management_extensions.c +++ b/src/exchange/taler-exchange-httpd_management_extensions.c @@ -79,14 +79,14 @@ set_extensions (void *cls, for (uint32_t i = 0; inum_extensions; i++) { struct Extension *ext = &sec->extensions[i]; + const struct TALER_Extension *taler_ext; enum GNUNET_DB_QueryStatus qs; char *config; - /* Sanity check. - * TODO: This will not work anymore, once we have plugable extensions - */ - if (0 > ext->type || TALER_Extension_MaxPredefined <= ext->type) + taler_ext = TALER_extensions_get_by_type (ext->type); + if (NULL == taler_ext) { + /* No such extension found */ GNUNET_break (0); return GNUNET_DB_STATUS_HARD_ERROR; } @@ -104,7 +104,7 @@ set_extensions (void *cls, qs = TEH_plugin->set_extension_config ( TEH_plugin->cls, - TEH_extensions[ext->type]->name, + taler_ext->name, config); if (qs < 0) @@ -183,17 +183,11 @@ TEH_handler_management_post_extensions ( /* Verify the signature */ { struct TALER_ExtensionConfigHash h_config; - if (GNUNET_OK != TALER_extension_config_hash (extensions, &h_config)) - { - GNUNET_JSON_parse_free (top_spec); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "invalid object, non-hashable"); - } - if (GNUNET_OK != TALER_exchange_offline_extension_config_hash_verify ( + if (GNUNET_OK != + TALER_JSON_extensions_config_hash (extensions, &h_config) || + GNUNET_OK != + TALER_exchange_offline_extension_config_hash_verify ( &h_config, &TEH_master_public_key, &sec.extensions_sig)) @@ -207,7 +201,6 @@ TEH_handler_management_post_extensions ( } } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Received /management/extensions\n"); @@ -217,7 +210,7 @@ TEH_handler_management_post_extensions ( /* Now parse individual extensions and signatures from those objects. */ { - const struct TALER_Extension *extension; + const struct TALER_Extension *extension = NULL; const char *name; json_t *config; int idx = 0; @@ -225,11 +218,8 @@ TEH_handler_management_post_extensions ( json_object_foreach (extensions, name, config){ /* 1. Make sure name refers to a supported extension */ - if (GNUNET_OK != TALER_extension_get_by_name (name, - (const struct - TALER_Extension **) - TEH_extensions, - &extension)) + extension = TALER_extensions_get_by_name (name); + if (NULL == extension) { ret = TALER_MHD_reply_with_error ( connection, @@ -243,7 +233,9 @@ TEH_handler_management_post_extensions ( sec.extensions[idx].type = extension->type; /* 2. Make sure the config is sound */ - if (GNUNET_OK != extension->test_config (sec.extensions[idx].config)) + if (GNUNET_OK != + extension->test_json_config ( + sec.extensions[idx].config)) { ret = TALER_MHD_reply_with_error ( connection, diff --git a/src/extensions/Makefile.am b/src/extensions/Makefile.am new file mode 100644 index 000000000..792b7eeb3 --- /dev/null +++ b/src/extensions/Makefile.am @@ -0,0 +1,30 @@ +# This Makefile.am is in the public domain + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/include \ + $(LIBGCRYPT_CFLAGS) \ + $(POSTGRESQL_CPPFLAGS) + +if USE_COVERAGE + AM_CFLAGS = --coverage -O0 + XLIB = -lgcov +endif + + +# Libraries + +lib_LTLIBRARIES = \ + libtalerextensions.la + +libtalerextensions_la_LDFLAGS = \ + -version-info 0:0:0 \ + -no-undefined + +libtalerextensions_la_SOURCES = \ + extensions.c \ + extension_age_restriction.c + +libtalerextensions_la_LIBADD = \ + -lgnunetjson \ + -ljansson \ + $(XLIB) diff --git a/src/extensions/extension_age_restriction.c b/src/extensions/extension_age_restriction.c new file mode 100644 index 000000000..a9ffb7f1a --- /dev/null +++ b/src/extensions/extension_age_restriction.c @@ -0,0 +1,321 @@ +/* + This file is part of TALER + Copyright (C) 2021-2022 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + */ +/** + * @file extension_age_restriction.c + * @brief Utility functions regarding age restriction + * @author Özgür Kesim + */ +#include "platform.h" +#include "taler_util.h" +#include "taler_extensions.h" +#include "stdint.h" + + +/** + * @param groups String representation of the age groups. Must be of the form + * a:b:...:n:m + * with + * 0 < a < b <...< n < m < 32 + * @param[out] mask Bit representation of the age groups. + * @return Error if string was invalid, OK otherwise. + */ +enum GNUNET_GenericReturnValue +TALER_parse_age_group_string ( + const char *groups, + struct TALER_AgeMask *mask) +{ + + const char *pos = groups; + unsigned int prev = 0; + unsigned int val = 0; + char c; + + while (*pos) + { + c = *pos++; + if (':' == c) + { + if (prev >= val) + return GNUNET_SYSERR; + + mask->mask |= 1 << val; + prev = val; + val = 0; + continue; + } + + if ('0'>c || '9'=val || 32<=val) + return GNUNET_SYSERR; + } + + if (0>val || 32<=val || prev>=val) + return GNUNET_SYSERR; + + mask->mask |= (1 << val); + mask->mask |= 1; // mark zeroth group, too + + return GNUNET_OK; +} + + +/** + * Encodes the age mask into a string, like "8:10:12:14:16:18:21" + * + * @param mask Age mask + * @return String representation of the age mask, allocated by GNUNET_malloc. + * Can be used as value in the TALER config. + */ +char * +TALER_age_mask_to_string ( + const struct TALER_AgeMask *m) +{ + uint32_t mask = m->mask; + unsigned int n = 0; + char *buf = GNUNET_malloc (32 * 3); // max characters possible + char *pos = buf; + + if (NULL == buf) + { + return buf; + } + + while (mask != 0) + { + mask >>= 1; + n++; + if (0 == (mask & 1)) + { + continue; + } + + if (n > 9) + { + *(pos++) = '0' + n / 10; + } + *(pos++) = '0' + n % 10; + + if (0 != (mask >> 1)) + { + *(pos++) = ':'; + } + } + return buf; +} + + +/* ================================================== + * + * Age Restriction TALER_Extension imlementation + * + * ================================================== + */ + + +/** + * @brief implements the TALER_Extension.disable interface. + */ +void +age_restriction_disable ( + struct TALER_Extension *this) +{ + if (NULL == this) + return; + + this->config = NULL; + + if (NULL != this->config_json) + { + json_decref (this->config_json); + this->config_json = NULL; + } +} + + +/** + * @brief implements the TALER_Extension.load_taler_config interface. + * @param cfg Handle to the GNUNET configuration + * @param[out] enabled Set to true if age restriction is enabled in the config, false otherwise. + * @param[out] mask Mask for age restriction. Will be 0 if age restriction was not enabled in the config. + * @return Error if extension for age restriction was set, but age groups were + * invalid, OK otherwise. + */ +static enum GNUNET_GenericReturnValue +age_restriction_load_taler_config ( + struct TALER_Extension *this, + const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + char *groups = NULL; + enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR; + struct TALER_AgeMask mask = {0}; + + if ((GNUNET_YES != + GNUNET_CONFIGURATION_have_value (cfg, + TALER_EXTENSION_SECTION_AGE_RESTRICTION, + "ENABLED")) + || + (GNUNET_YES != + GNUNET_CONFIGURATION_get_value_yesno (cfg, + TALER_EXTENSION_SECTION_AGE_RESTRICTION, + "ENABLED"))) + { + /* Age restriction is not enabled */ + this->config = NULL; + this->config_json = NULL; + return GNUNET_OK; + } + + /* Age restriction is enabled, extract age groups */ + if ((GNUNET_YES == + GNUNET_CONFIGURATION_have_value (cfg, + TALER_EXTENSION_SECTION_AGE_RESTRICTION, + "AGE_GROUPS")) + && + (GNUNET_YES != + GNUNET_CONFIGURATION_get_value_string (cfg, + TALER_EXTENSION_SECTION_AGE_RESTRICTION, + "AGE_GROUPS", + &groups))) + return GNUNET_SYSERR; + + + mask.mask = TALER_EXTENSION_AGE_RESTRICTION_DEFAULT_AGE_MASK; + + ret = GNUNET_OK; + + if (groups != NULL) + { + ret = TALER_parse_age_group_string (groups, &mask); + if (GNUNET_OK != ret) + mask.mask = TALER_EXTENSION_AGE_RESTRICTION_DEFAULT_AGE_MASK; + } + + if (GNUNET_OK == ret) + this->config = (void *) (size_t) mask.mask; + + GNUNET_free (groups); + return ret; +} + + +/** + * @brief implements the TALER_Extension.load_json_config interface. + * @param this if NULL, only tests the configuration + * @param config the configuration as json + */ +static enum GNUNET_GenericReturnValue +age_restriction_load_json_config ( + struct TALER_Extension *this, + json_t *config) +{ + struct TALER_AgeMask mask = {0}; + enum GNUNET_GenericReturnValue ret; + + ret = TALER_JSON_parse_agemask (config, &mask); + if (GNUNET_OK != ret) + return ret; + + /* only testing the parser */ + if (this == NULL) + return GNUNET_OK; + + if (TALER_Extension_AgeRestriction != this->type) + return GNUNET_SYSERR; + + if (NULL != this->config) + GNUNET_free (this->config); + + this->config = GNUNET_malloc (sizeof(struct TALER_AgeMask)); + GNUNET_memcpy (this->config, &mask, sizeof(struct TALER_AgeMask)); + + if (NULL != this->config_json) + json_decref (this->config_json); + + this->config_json = config; + + return GNUNET_OK; +} + + +/** + * @brief implements the TALER_Extension.load_json_config interface. + * @param this if NULL, only tests the configuration + * @param config the configuration as json + */ +json_t * +age_restriction_config_to_json ( + const struct TALER_Extension *this) +{ + struct TALER_AgeMask mask; + char *mask_str; + json_t *conf; + + GNUNET_assert (NULL != this); + GNUNET_assert (NULL != this->config); + + if (NULL != this->config_json) + { + return json_copy (this->config_json); + } + + mask.mask = (uint32_t) (size_t) this->config; + mask_str = TALER_age_mask_to_string (&mask); + conf = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("age_groups", mask_str) + ); + + return GNUNET_JSON_PACK ( + GNUNET_JSON_pack_bool ("critical", this->critical), + GNUNET_JSON_pack_string ("version", this->version), + GNUNET_JSON_pack_object_steal ("config", conf) + ); +} + + +/** + * @brief implements the TALER_Extension.test_json_config interface. + */ +static enum GNUNET_GenericReturnValue +age_restriction_test_json_config ( + const json_t *config) +{ + struct TALER_AgeMask mask = {0}; + + return TALER_JSON_parse_agemask (config, &mask); +} + + +/* The extension for age restriction */ +struct TALER_Extension _extension_age_restriction = { + .next = NULL, + .type = TALER_Extension_AgeRestriction, + .name = "age_restriction", + .critical = false, + .version = "1", + .config = NULL, // disabled per default + .config_json = NULL, + .disable = &age_restriction_disable, + .test_json_config = &age_restriction_test_json_config, + .load_json_config = &age_restriction_load_json_config, + .config_to_json = &age_restriction_config_to_json, + .load_taler_config = &age_restriction_load_taler_config, +}; + +/* end of extension_age_restriction.c */ diff --git a/src/extensions/extensions.c b/src/extensions/extensions.c new file mode 100644 index 000000000..55d970c57 --- /dev/null +++ b/src/extensions/extensions.c @@ -0,0 +1,333 @@ +/* + This file is part of TALER + Copyright (C) 2021-2022 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + */ +/** + * @file extensions.c + * @brief Utility functions for extensions + * @author Özgür Kesim + */ +#include "platform.h" +#include "taler_util.h" +#include "taler_signatures.h" +#include "taler_extensions.h" +#include "stdint.h" + + +/* head of the list of all registered extensions */ +static struct TALER_Extension *_extensions = NULL; +static bool _initialized = false; + +void +TALER_extensions_init () +{ + extern struct TALER_Extension _extension_age_restriction; + if (! _initialized) + _extensions = &_extension_age_restriction; + + _initialized = true; +} + + +const struct TALER_Extension * +TALER_extensions_get_head () +{ + return _extensions; +} + + +enum GNUNET_GenericReturnValue +TALER_extensions_add ( + const struct TALER_Extension *new) +{ + struct TALER_Extension *ext; + + if (_initialized) + return GNUNET_SYSERR; + + GNUNET_assert (NULL != _extensions); + + /* Sanity checks */ + if (NULL == new || + NULL == new->name || + NULL == new->version || + NULL == new->disable || + NULL == new->test_json_config || + NULL == new->load_json_config || + NULL == new->config_to_json || + NULL == new->load_taler_config || + NULL == new->next) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "invalid extension\n"); + return GNUNET_SYSERR; + } + + /* Check for collisions */ + for (ext = _extensions; NULL != ext; ext = ext->next) + { + if (new->type == ext->type || + 0 == strcmp (new->name, ext->name)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "extension collision\n"); + return GNUNET_NO; + } + } + + /* No collisions found, so add this extension to the list */ + ext->next = (struct TALER_Extension *) new; + + return GNUNET_OK; +} + + +const struct TALER_Extension * +TALER_extensions_get_by_type ( + enum TALER_Extension_Type type) +{ + + for (const struct TALER_Extension *it = _extensions; + NULL != it; + it = it->next) + { + if (it->type == type) + return it; + } + + /* No extension found. */ + return NULL; +} + + +bool +TALER_extensions_is_enabled_type ( + enum TALER_Extension_Type type) +{ + const struct TALER_Extension *ext = + TALER_extensions_get_by_type (type); + + return (NULL != ext && + TALER_extensions_is_enabled (ext)); +} + + +const struct TALER_Extension * +TALER_extensions_get_by_name ( + const char *name) +{ + for (const struct TALER_Extension *it = _extensions; + NULL != it; + it = it->next) + { + if (0 == strcmp (name, it->name)) + return it; + } + /* No extension found. */ + return NULL; +} + + +enum GNUNET_GenericReturnValue +config_hash_verify ( + const struct TALER_ExtensionConfigHash *h_config, + const struct TALER_MasterPublicKeyP *master_pub, + const struct TALER_MasterSignatureP *master_sig + ) +{ + struct TALER_MasterExtensionConfigurationPS ec = { + .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_EXTENSION), + .purpose.size = htonl (sizeof(ec)), + .h_config = *h_config + }; + + return GNUNET_CRYPTO_eddsa_verify ( + TALER_SIGNATURE_MASTER_EXTENSION, + &ec, + &master_sig->eddsa_signature, + &master_pub->eddsa_pub); +} + + +enum GNUNET_GenericReturnValue +TALER_extensions_verify_json_config_signature ( + json_t *extensions, + struct TALER_MasterSignatureP *extensions_sig, + struct TALER_MasterPublicKeyP *master_pub) +{ + struct TALER_ExtensionConfigHash h_config; + + if (GNUNET_OK != + TALER_JSON_extensions_config_hash (extensions, &h_config)) + return GNUNET_SYSERR; + + if (GNUNET_OK != config_hash_verify ( + &h_config, + master_pub, + extensions_sig)) + return GNUNET_NO; + + return GNUNET_OK; +} + + +struct load_conf_closure +{ + const struct GNUNET_CONFIGURATION_Handle *cfg; + enum GNUNET_GenericReturnValue error; +}; + +static void +collect_extensions ( + void *cls, + const char *section) +{ + struct load_conf_closure *col = cls; + const char *name; + const struct TALER_Extension *extension; + + if (GNUNET_OK != col->error) + return; + + if (0 != strncasecmp (section, + TALER_EXTENSION_SECTION_PREFIX, + sizeof(TALER_EXTENSION_SECTION_PREFIX) - 1)) + { + return; + } + + name = section + sizeof(TALER_EXTENSION_SECTION_PREFIX) - 1; + + if (NULL == (extension = TALER_extensions_get_by_name (name))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unsupported extension `%s` (section [%s]).\n", name, + section); + col->error = GNUNET_SYSERR; + return; + } + + if (GNUNET_OK != + extension->load_taler_config ( + (struct TALER_Extension *) extension, + col->cfg)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Couldn't parse configuration for extension `%s` (section [%s]).\n", + name, + section); + col->error = GNUNET_SYSERR; + return; + } +} + + +enum GNUNET_GenericReturnValue +TALER_extensions_load_taler_config ( + const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + struct load_conf_closure col = { + .cfg = cfg, + .error = GNUNET_OK, + }; + + GNUNET_CONFIGURATION_iterate_sections (cfg, + &collect_extensions, + &col); + return col.error; +} + + +static enum GNUNET_GenericReturnValue +is_json_extension_config ( + json_t *obj, + int *critical, + const char **version, + json_t **config) +{ + enum GNUNET_GenericReturnValue ret; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_boolean ("critical", + critical), + GNUNET_JSON_spec_string ("version", + version), + GNUNET_JSON_spec_json ("config", + config), + GNUNET_JSON_spec_end () + }; + + ret = GNUNET_JSON_parse (obj, spec, NULL, NULL); + if (GNUNET_OK == ret) + GNUNET_JSON_parse_free (spec); + + return ret; +} + + +enum GNUNET_GenericReturnValue +TALER_extensions_load_json_config ( + json_t *extensions) +{ + const char*name; + json_t *blob; + + GNUNET_assert (NULL != extensions); + GNUNET_assert (json_is_object (extensions)); + + json_object_foreach (extensions, name, blob) + { + int critical; + const char *version; + json_t *config; + const struct TALER_Extension *extension = + TALER_extensions_get_by_name (name); + + if (NULL == extension) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "no such extension: %s\n", name); + return GNUNET_SYSERR; + } + + /* load and verify criticality, version, etc. */ + if (GNUNET_OK != + is_json_extension_config ( + blob, &critical, &version, &config)) + return GNUNET_SYSERR; + + if (critical != extension->critical + || 0 != strcmp (version, extension->version) // TODO: libtool compare? + || NULL == config + || GNUNET_OK != extension->test_json_config (config)) + return GNUNET_SYSERR; + + /* This _should_ work now */ + if (GNUNET_OK != + extension->load_json_config ((struct TALER_Extension *) extension, + config)) + return GNUNET_SYSERR; + } + + /* make sure to disable all extensions that weren't mentioned in the json */ + for (const struct TALER_Extension *it = TALER_extensions_get_head (); + NULL != it; + it = it->next) + { + if (NULL == json_object_get (extensions, it->name)) + it->disable ((struct TALER_Extension *) it); + } + + return GNUNET_OK; +} + + +/* end of extensions.c */ diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h index 5bc87cf47..caa61c5f1 100644 --- a/src/include/taler_exchange_service.h +++ b/src/include/taler_exchange_service.h @@ -3159,5 +3159,4 @@ void TALER_EXCHANGE_add_auditor_denomination_cancel ( struct TALER_EXCHANGE_AuditorAddDenominationHandle *ah); - #endif /* _TALER_EXCHANGE_SERVICE_H */ diff --git a/src/include/taler_extensions.h b/src/include/taler_extensions.h index 7199304d9..f00f3ed56 100644 --- a/src/include/taler_extensions.h +++ b/src/include/taler_extensions.h @@ -40,6 +40,9 @@ enum TALER_Extension_Type */ struct TALER_Extension { + /* simple linked list */ + struct TALER_Extension *next; + enum TALER_Extension_Type type; char *name; bool critical; @@ -48,28 +51,125 @@ struct TALER_Extension json_t *config_json; void (*disable)(struct TALER_Extension *this); - enum GNUNET_GenericReturnValue (*test_config)(const json_t *config); - enum GNUNET_GenericReturnValue (*parse_and_set_config)(struct - TALER_Extension *this, - json_t *config); + + enum GNUNET_GenericReturnValue (*test_json_config)( + const json_t *config); + + enum GNUNET_GenericReturnValue (*load_json_config)( + struct TALER_Extension *this, + json_t *config); + + json_t *(*config_to_json)( + const struct TALER_Extension *this); + + enum GNUNET_GenericReturnValue (*load_taler_config)( + struct TALER_Extension *this, + const struct GNUNET_CONFIGURATION_Handle *cfg); }; /** * Generic functions for extensions */ +void +TALER_extensions_init (); + +/* + * Sets the configuration of the extensions from the given TALER configuration + * + * @param cfg Handle to the TALER configuration + * @return GNUNET_OK on success, GNUNET_SYSERR if unknown extensions were found + * or any particular configuration couldn't be parsed. + */ +enum GNUNET_GenericReturnValue +TALER_extensions_load_taler_config ( + const struct GNUNET_CONFIGURATION_Handle *cfg); + +/* + * Returns the head of the linked list of extensions + */ +const struct TALER_Extension * +TALER_extensions_get_head (); + +/* + * Adds an extension to the linked list of extensions + * + * @param new_extension the new extension to be added + * @return GNUNET_OK on success, GNUNET_SYSERR if the extension is invalid + * (missing fields), GNUNET_NO if there is already an extension with that name + * or type. + */ +enum GNUNET_GenericReturnValue +TALER_extensions_add ( + const struct TALER_Extension *new_extension); + +/** + * Finds and returns a supported extension by a given type. + * + * @param type type of the extension to lookup + * @return extension found, or NULL (should not happen!) + */ +const struct TALER_Extension * +TALER_extensions_get_by_type ( + enum TALER_Extension_Type type); + + /** * Finds and returns a supported extension by a given name. * * @param name name of the extension to lookup - * @param extensions list of TALER_Extensions as haystack, terminated by a NULL-entry - * @param[out] ext set to the extension, if found, NULL otherwise - * @return GNUNET_OK if extension was found, GNUNET_NO otherwise + * @return the extension, if found, NULL otherwise + */ +const struct TALER_Extension * +TALER_extensions_get_by_name ( + const char *name); + +#define TALER_extensions_is_enabled(ext) (NULL != (ext)->config) + +/** + * Check if a given type of an extension is enabled + * + * @param type type of to check + * @return true enabled, false if not enabled, will assert if type is not found. + */ +bool +TALER_extensions_is_enabled_type ( + enum TALER_Extension_Type type); + + +/* + * Verify the signature of a given JSON object for extensions with the master + * key of the exchange. + * + * The JSON object must be of type ExchangeKeysResponse as described in + * https://docs.taler.net/design-documents/006-extensions.html#exchange + * + * @param extensions JSON object with the extension configuration + * @param extensions_sig signature of the hash of the JSON object + * @param master_pub public key to verify the signature + * @return GNUNET_OK on success, GNUNET_SYSERR when hashing of the JSON fails + * and GNUNET_NO if the signature couldn't be verified. */ enum GNUNET_GenericReturnValue -TALER_extension_get_by_name (const char *name, - const struct TALER_Extension **extensions, - const struct TALER_Extension **ext); +TALER_extensions_verify_json_config_signature ( + json_t *extensions, + struct TALER_MasterSignatureP *extensions_sig, + struct TALER_MasterPublicKeyP *master_pub); + +/* + * Sets the configuration of the extensions from a given JSON object. + * + * The JSON object must be of type ExchangeKeysResponse as described in + * https://docs.taler.net/design-documents/006-extensions.html#exchange + * + * @param cfg Handle to the TALER configuration + * @return GNUNET_OK on success, GNUNET_SYSERR if unknown extensions were found + * or any particular configuration couldn't be parsed. + */ +enum GNUNET_GenericReturnValue +TALER_extensions_load_json_config ( + json_t *extensions); + /* * TALER Age Restriction Extension @@ -82,9 +182,11 @@ TALER_extension_get_by_name (const char *name, * The default age mask represents the age groups * 0-7, 8-9, 10-11, 12-13, 14-15, 16-17, 18-20, 21-... */ -#define TALER_EXTENSION_DEFAULT_AGE_MASK (1 | 1 << 8 | 1 << 10 | 1 << 12 | 1 \ - << 14 | 1 << 16 | 1 << 18 | 1 \ - << 21) +#define TALER_EXTENSION_AGE_RESTRICTION_DEFAULT_AGE_MASK (1 | 1 << 8 | 1 << 10 \ + | 1 << 12 | 1 << 14 \ + | 1 << 16 | 1 << 18 \ + | 1 << 21) +#define TALER_EXTENSION_AGE_RESTRICTION_DEFAULT_AGE_GROUPS "8:10:12:14:16:18:21" /** * @brief Parses a string as a list of age groups. @@ -104,8 +206,9 @@ TALER_extension_get_by_name (const char *name, * @return Error, if age groups were invalid, OK otherwise. */ enum GNUNET_GenericReturnValue -TALER_parse_age_group_string (const char *groups, - struct TALER_AgeMask *mask); +TALER_parse_age_group_string ( + const char *groups, + struct TALER_AgeMask *mask); /** * Encodes the age mask into a string, like "8:10:12:14:16:18:21" @@ -115,21 +218,8 @@ TALER_parse_age_group_string (const char *groups, * Can be used as value in the TALER config. */ char * -TALER_age_mask_to_string (const struct TALER_AgeMask *mask); - - -/** - * @brief Reads the age groups from the configuration and sets the - * corresponding age mask. - * - * @param cfg - * @param[out] mask for age restriction, will be set to 0 if age restriction is disabled. - * @return Error if extension for age restriction was set but age groups were - * invalid, OK otherwise. - */ -enum GNUNET_GenericReturnValue -TALER_get_age_mask (const struct GNUNET_CONFIGURATION_Handle *cfg, - struct TALER_AgeMask *mask); +TALER_age_mask_to_string ( + const struct TALER_AgeMask *mask); /* diff --git a/src/include/taler_json_lib.h b/src/include/taler_json_lib.h index 102b3a6ff..2a101d269 100644 --- a/src/include/taler_json_lib.h +++ b/src/include/taler_json_lib.h @@ -549,8 +549,8 @@ TALER_deposit_extension_hash (const json_t *extensions, * @return GNUNET_OK on success, GNUNET_SYSERR on failure */ enum GNUNET_GenericReturnValue -TALER_extension_config_hash (const json_t *config, - struct TALER_ExtensionConfigHash *eh); +TALER_JSON_extensions_config_hash (const json_t *config, + struct TALER_ExtensionConfigHash *eh); /** * Parses a JSON object { "extension": "age_restriction", "mask": }. @@ -560,7 +560,7 @@ TALER_extension_config_hash (const json_t *config, * @return #GNUNET_OK on success and #GNUNET_SYSERR on failure. */ enum GNUNET_GenericReturnValue -TALER_agemask_parse_json (const json_t *root, +TALER_JSON_parse_agemask (const json_t *root, struct TALER_AgeMask *mask); #endif /* TALER_JSON_LIB_H_ */ diff --git a/src/json/json.c b/src/json/json.c index 956aad1a5..705cfe92b 100644 --- a/src/json/json.c +++ b/src/json/json.c @@ -1010,8 +1010,8 @@ TALER_deposit_extension_hash (const json_t *extensions, enum GNUNET_GenericReturnValue -TALER_extension_config_hash (const json_t *config, - struct TALER_ExtensionConfigHash *ech) +TALER_JSON_extensions_config_hash (const json_t *config, + struct TALER_ExtensionConfigHash *ech) { return dump_and_hash (config, "taler-extension-configuration", diff --git a/src/json/json_helper.c b/src/json/json_helper.c index 3b4da5595..1942d09bd 100644 --- a/src/json/json_helper.c +++ b/src/json/json_helper.c @@ -660,7 +660,7 @@ TALER_JSON_spec_i18n_str (const char *name, enum GNUNET_GenericReturnValue -TALER_agemask_parse_json (const json_t *root, +TALER_JSON_parse_agemask (const json_t *root, struct TALER_AgeMask *mask) { const char *name; diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index dd4c527d5..3398bdf14 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -57,6 +57,7 @@ libtalerexchange_la_LIBADD = \ $(top_builddir)/src/json/libtalerjson.la \ $(top_builddir)/src/curl/libtalercurl.la \ $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/extensions/libtalerextensions.la \ -lgnunetcurl \ -lgnunetjson \ -lgnunetutil \ diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c index aea09a81f..cf3d69d6a 100644 --- a/src/lib/exchange_api_handle.c +++ b/src/lib/exchange_api_handle.c @@ -795,50 +795,39 @@ decode_keys_json (const json_t *resp_obj, } /* Parse the supported extension(s): age-restriction. */ - /* TODO: maybe lift this into a FP in TALER_Extension ? */ + /* TODO: maybe lift all this into a FP in TALER_Extension ? */ { - json_t *age_restriction = json_object_get (resp_obj, - "age_restriction"); + struct TALER_MasterSignatureP extensions_sig = {0}; + json_t *extensions = NULL; + struct GNUNET_JSON_Specification ext_spec[] = { + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_json ("extensions", + &extensions)), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ( + "extensions_sig", + &extensions_sig)), + GNUNET_JSON_spec_end () + }; - if (NULL != age_restriction) + /* 1. Search for extensions in the response to /keys */ + EXITIF (GNUNET_OK != + GNUNET_JSON_parse (resp_obj, + ext_spec, + NULL, NULL)); + + if (NULL != extensions) { - bool critical; - const char *version; - const char *age_groups; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_bool ("critical", - &critical), - GNUNET_JSON_spec_string ("version", - &version), - GNUNET_JSON_spec_string ("age_groups", - &age_groups), - GNUNET_JSON_spec_end () - }; + /* 2. We have an extensions object. Verify its signature. */ + EXITIF (GNUNET_OK != + TALER_extensions_verify_json_config_signature ( + extensions, + &extensions_sig, + &key_data->master_pub)); - if (GNUNET_OK != - GNUNET_JSON_parse (age_restriction, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - if (critical || // do we care? - 0 != strncmp (version, "1", 1) ) /* TODO: better compatibility check */ - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - if (GNUNET_OK != - TALER_parse_age_group_string (age_groups, - &key_data->age_mask)) - { - // TODO: print more specific error? - GNUNET_break_op (0); - return GNUNET_SYSERR; - } + /* 3. Parse and set the the configuration of the extensions accordingly */ + EXITIF (GNUNET_OK != + TALER_extensions_load_json_config (extensions)); } } diff --git a/src/util/Makefile.am b/src/util/Makefile.am index 55ebb4dff..35e580347 100644 --- a/src/util/Makefile.am +++ b/src/util/Makefile.am @@ -72,8 +72,6 @@ libtalerutil_la_SOURCES = \ crypto_wire.c \ denom.c \ exchange_signatures.c \ - extensions.c \ - extension_age_restriction.c \ getopt.c \ lang.c \ iban.c \ diff --git a/src/util/extension_age_restriction.c b/src/util/extension_age_restriction.c deleted file mode 100644 index 0b04c7d7b..000000000 --- a/src/util/extension_age_restriction.c +++ /dev/null @@ -1,169 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2020 Taler Systems SA - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - TALER; see the file COPYING. If not, see - */ -/** - * @file extension_age_restriction.c - * @brief Utility functions regarding age restriction - * @author Özgür Kesim - */ -#include "platform.h" -#include "taler_util.h" -#include "taler_extensions.h" -#include "stdint.h" - -/** - * - * @param cfg Handle to the GNUNET configuration - * @param[out] Mask for age restriction. Will be 0 if age restriction was not enabled in the config. - * @return Error if extension for age restriction was set, but age groups were - * invalid, OK otherwise. - */ -enum GNUNET_GenericReturnValue -TALER_get_age_mask (const struct GNUNET_CONFIGURATION_Handle *cfg, - struct TALER_AgeMask *mask) -{ - char *groups; - enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR; - - if ((GNUNET_YES != GNUNET_CONFIGURATION_have_value (cfg, - TALER_EXTENSION_SECTION_AGE_RESTRICTION, - "ENABLED")) || - (GNUNET_YES != GNUNET_CONFIGURATION_get_value_yesno (cfg, - TALER_EXTENSION_SECTION_AGE_RESTRICTION, - "ENABLED"))) - { - /* Age restriction is not enabled */ - mask->mask = 0; - return GNUNET_OK; - } - - /* Age restriction is enabled, extract age groups */ - if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, - TALER_EXTENSION_SECTION_AGE_RESTRICTION, - "AGE_GROUPS", - &groups)) - { - /* FIXME: log error? */ - return GNUNET_SYSERR; - } - if (groups == NULL) - { - /* No groups defined in config, return default_age_mask */ - mask->mask = TALER_EXTENSION_DEFAULT_AGE_MASK; - return GNUNET_OK; - } - - ret = TALER_parse_age_group_string (groups, mask); - GNUNET_free (groups); - return ret; -} - - -/** - * @param groups String representation of the age groups. Must be of the form - * a:b:...:n:m - * with - * 0 < a < b <...< n < m < 32 - * @param[out] mask Bit representation of the age groups. - * @return Error if string was invalid, OK otherwise. - */ -enum GNUNET_GenericReturnValue -TALER_parse_age_group_string (const char *groups, - struct TALER_AgeMask *mask) -{ - - const char *pos = groups; - unsigned int prev = 0; - unsigned int val = 0; - char c; - - while (*pos) - { - c = *pos++; - if (':' == c) - { - if (prev >= val) - return GNUNET_SYSERR; - - mask->mask |= 1 << val; - prev = val; - val = 0; - continue; - } - - if ('0'>c || '9'=val || 32<=val) - return GNUNET_SYSERR; - } - - if (0>val || 32<=val || prev>=val) - return GNUNET_SYSERR; - - mask->mask |= (1 << val); - mask->mask |= 1; // mark zeroth group, too - - return GNUNET_OK; -} - - -/** - * Encodes the age mask into a string, like "8:10:12:14:16:18:21" - * - * @param mask Age mask - * @return String representation of the age mask, allocated by GNUNET_malloc. - * Can be used as value in the TALER config. - */ -char * -TALER_age_mask_to_string (const struct TALER_AgeMask *m) -{ - uint32_t mask = m->mask; - unsigned int n = 0; - char *buf = GNUNET_malloc (32 * 3); // max characters possible - char *pos = buf; - - if (NULL == buf) - { - return buf; - } - - while (mask != 0) - { - mask >>= 1; - n++; - if (0 == (mask & 1)) - { - continue; - } - - if (n > 9) - { - *(pos++) = '0' + n / 10; - } - *(pos++) = '0' + n % 10; - - if (0 != (mask >> 1)) - { - *(pos++) = ':'; - } - } - return buf; -} - - -/* end of extension_age_restriction.c */ diff --git a/src/util/extensions.c b/src/util/extensions.c deleted file mode 100644 index 87dd16b4d..000000000 --- a/src/util/extensions.c +++ /dev/null @@ -1,49 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2021 Taler Systems SA - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - TALER; see the file COPYING. If not, see - */ -/** - * @file extensions.c - * @brief Utility functions for extensions - * @author Özgür Kesim - */ -#include "platform.h" -#include "taler_util.h" -#include "taler_extensions.h" -#include "stdint.h" - -enum GNUNET_GenericReturnValue -TALER_extension_get_by_name (const char *name, - const struct TALER_Extension **extensions, - const struct TALER_Extension **ext) -{ - - const struct TALER_Extension *it = *extensions; - - for (; NULL != it; it++) - { - if (0 == strncmp (name, - it->name, - strlen (it->name))) - { - *ext = it; - return GNUNET_OK; - } - } - - return GNUNET_NO; -} - - -/* end of extensions.c */