diff options
author | Özgür Kesim <oec-taler@kesim.org> | 2022-01-08 14:40:20 +0100 |
---|---|---|
committer | Özgür Kesim <oec-taler@kesim.org> | 2022-01-08 14:40:20 +0100 |
commit | cc7d7707ab2bd43bc9e95c0eeec9ce95cdc0c523 (patch) | |
tree | 472e895b321e539f4675f016a285d6f6e6436b76 | |
parent | b49fac3d5892f75a2eb7fbfbca0056965c6967c7 (diff) |
[age restriction] progress 10/n
More work towards support for extensions:
- Prepared statements and DB-plugin-functions for setting and retrieving
configurations from the database added.
- primitive "registry" of extensions for age restrictions and peer2peer
(stub)
- TALER_Extensions now with FP for parsing, setting and converting a
configuration.
- /management/extensions handler now verifies signature of the (opaque)
json object for all extensions.
- /management/extensions handler calls the FP in the corrensponding
TALER_Extension for parsing and setting the configuration of a
particular extension
More work towards age restriction:
- TALER_Extensions interfaces for config-parser, -setter and converter
implemented for age restriction
- DB event handler now retrieves config from database, parses it and
sets it (the age mask) in the global extension.
- load_age_mask now loads age mask from the global extension (and not
from the config file)
- add age_restricted_denoms to /keys response
-rw-r--r-- | src/exchange/taler-exchange-httpd.c | 56 | ||||
-rw-r--r-- | src/exchange/taler-exchange-httpd.h | 7 | ||||
-rw-r--r-- | src/exchange/taler-exchange-httpd_extensions.c | 219 | ||||
-rw-r--r-- | src/exchange/taler-exchange-httpd_extensions.h | 8 | ||||
-rw-r--r-- | src/exchange/taler-exchange-httpd_keys.c | 111 | ||||
-rw-r--r-- | src/exchange/taler-exchange-httpd_management_extensions.c | 377 | ||||
-rw-r--r-- | src/exchangedb/plugin_exchangedb_postgres.c | 153 | ||||
-rw-r--r-- | src/include/taler_crypto_lib.h | 30 | ||||
-rw-r--r-- | src/include/taler_exchangedb_plugin.h | 29 | ||||
-rw-r--r-- | src/include/taler_extensions.h | 63 | ||||
-rw-r--r-- | src/include/taler_json_lib.h | 13 | ||||
-rw-r--r-- | src/include/taler_signatures.h | 26 | ||||
-rw-r--r-- | src/json/json.c | 10 | ||||
-rw-r--r-- | src/lib/exchange_api_management_post_extensions.c | 23 | ||||
-rw-r--r-- | src/util/Makefile.am | 1 | ||||
-rw-r--r-- | src/util/extension_age_restriction.c | 5 | ||||
-rw-r--r-- | src/util/extensions.c | 49 | ||||
-rw-r--r-- | src/util/offline_signatures.c | 56 |
18 files changed, 823 insertions, 413 deletions
diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c index b435ee4ae..59398c6fc 100644 --- a/src/exchange/taler-exchange-httpd.c +++ b/src/exchange/taler-exchange-httpd.c @@ -1,18 +1,18 @@ /* - This file is part of TALER - Copyright (C) 2014-2021 Taler Systems SA + 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 Affero General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero 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 Affero General Public License for more details. + 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 Affero General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with - TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> -*/ + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ /** * @file taler-exchange-httpd.c * @brief Serve the HTTP interface of the exchange @@ -148,23 +148,9 @@ int TEH_check_invariants_flag; bool TEH_suicide; /** - * The global manifest with the list supported extensions, sorted by - * TALER_Extension_Type. - **/ -const struct TALER_Extension TEH_extensions[TALER_Extension_Max] = { - [TALER_Extension_Peer2Peer] = { - .type = TALER_Extension_Peer2Peer, - .name = "peer2peer", - .critical = false, - .config = NULL, // disabled per default - }, - [TALER_Extension_AgeRestriction] = { - .type = TALER_Extension_AgeRestriction, - .name = "age_restriction", - .critical = false, - .config = NULL, // disabled per default - }, -}; + * Global register of extensions + */ +struct TALER_Extension **TEH_extensions; /** * Value to return from main() @@ -485,7 +471,7 @@ proceed_with_handler (struct TEH_RequestContext *rc, if (GNUNET_SYSERR == res) { GNUNET_assert (NULL == root); - return MHD_NO; /* bad upload, could not even generate error */ + return MHD_NO; /* bad upload, could not even generate error */ } if ( (GNUNET_NO == res) || (NULL == root) ) @@ -528,8 +514,8 @@ proceed_with_handler (struct TEH_RequestContext *rc, sizeof (emsg), "Got %u/%u segments for %s request ('%s')", (NULL == args[i - 1]) - ? i - 1 - : i + ((NULL != fin) ? 1 : 0), + ? i - 1 + : i + ((NULL != fin) ? 1 : 0), rh->nargs, rh->url, url); @@ -553,7 +539,7 @@ proceed_with_handler (struct TEH_RequestContext *rc, root, args); else /* We also only have "POST" or "GET" in the API for at this point - (OPTIONS/HEAD are taken care of earlier) */ + (OPTIONS/HEAD are taken care of earlier) */ ret = rh->handler.get (rc, args); } @@ -1120,7 +1106,7 @@ handle_mhd_request (void *cls, if (0 == strcasecmp (method, MHD_HTTP_METHOD_HEAD)) - method = MHD_HTTP_METHOD_GET; /* treat HEAD as GET here, MHD will do the rest */ + method = MHD_HTTP_METHOD_GET; /* treat HEAD as GET here, MHD will do the rest */ /* parse first part of URL */ { @@ -1954,8 +1940,8 @@ run (void *cls, MHD_OPTION_CONNECTION_TIMEOUT, connection_timeout, (0 == allow_address_reuse) - ? MHD_OPTION_END - : MHD_OPTION_LISTENING_ADDRESS_REUSE, + ? MHD_OPTION_END + : MHD_OPTION_LISTENING_ADDRESS_REUSE, (unsigned int) allow_address_reuse, MHD_OPTION_END); if (NULL == mhd) diff --git a/src/exchange/taler-exchange-httpd.h b/src/exchange/taler-exchange-httpd.h index fa47af6f4..4f04029e6 100644 --- a/src/exchange/taler-exchange-httpd.h +++ b/src/exchange/taler-exchange-httpd.h @@ -202,9 +202,12 @@ extern volatile bool MHD_terminating; extern struct GNUNET_CURL_Context *TEH_curl_ctx; /** - * The manifest of the available extensions + * The manifest of the available extensions, NULL terminated */ -extern const struct TALER_Extension TEH_extensions[TALER_Extension_Max]; +extern struct TALER_Extension **TEH_extensions; + +#define TEH_extension_enabled(ext) (0 <= ext && TALER_Extension_Max > 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 98092bd09..8723bebc8 100644 --- a/src/exchange/taler-exchange-httpd_extensions.c +++ b/src/exchange/taler-exchange-httpd_extensions.c @@ -24,8 +24,107 @@ #include "taler-exchange-httpd_extensions.h" #include "taler_json_lib.h" #include "taler_mhd_lib.h" +#include "taler_extensions.h" #include <jansson.h> +/** + * @brief implements the TALER_Extension.parse_and_set_config interface. + */ +static enum GNUNET_GenericReturnValue +age_restriction_parse_and_set_config (struct TALER_Extension *this, + const json_t *config) +{ + enum GNUNET_GenericReturnValue ret; + struct TALER_AgeMask mask = {0}; + + ret = TALER_agemask_parse_json (config, &mask); + if (GNUNET_OK != ret) + return ret; + + if (this != NULL && TALER_Extension_AgeRestriction == this->type) + { + 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)); + } + + return GNUNET_OK; +} + + +/** + * @brief implements the TALER_Extension.test_config interface. + */ +static enum GNUNET_GenericReturnValue +age_restriction_test_config (const json_t *config) +{ + return age_restriction_parse_and_set_config (NULL, config); +} + + +/** + * @brief implements the TALER_Extension.config_to_json interface. + */ +static json_t * +age_restriction_config_to_json (const struct TALER_Extension *this) +{ + const struct TALER_AgeMask *mask; + if (NULL == this || TALER_Extension_AgeRestriction != this->type) + return NULL; + + mask = (struct TALER_AgeMask *) this->config; + json_t *config = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("extension", this->name), + GNUNET_JSON_pack_string ("mask", + TALER_age_mask_to_string (mask)) + ); + + return config; +} + + +/* The extension for age restriction */ +static struct TALER_Extension extension_age_restriction = { + .type = TALER_Extension_AgeRestriction, + .name = "age_restriction", + .critical = false, + .config = NULL, // disabled per default + .test_config = &age_restriction_test_config, + .parse_and_set_config = &age_restriction_parse_and_set_config, + .config_to_json = &age_restriction_config_to_json, +}; + +/* TODO: The extension for peer2peer */ +static struct TALER_Extension extension_peer2peer = { + .type = TALER_Extension_Peer2Peer, + .name = "peer2peer", + .critical = false, + .config = NULL, // disabled per default + .test_config = NULL, // TODO + .parse_and_set_config = NULL, // TODO + .config_to_json = NULL, // TODO +}; + + +/** + * Create a list with the extensions for Age Restriction and Peer2Peer + */ +static struct TALER_Extension ** +get_known_extensions () +{ + + struct TALER_Extension **list = GNUNET_new_array (TALER_Extension_Max + 1, + struct TALER_Extension *); + list[TALER_Extension_AgeRestriction] = &extension_age_restriction; + list[TALER_Extension_Peer2Peer] = &extension_peer2peer; + list[TALER_Extension_Max] = NULL; + + return list; +} + /** * Handler listening for extensions updates by other exchange @@ -33,7 +132,6 @@ */ static struct GNUNET_DB_EventHandler *extensions_eh; - /** * Function called whenever another exchange process has updated * the extensions data in the database. @@ -48,30 +146,99 @@ extension_update_event_cb (void *cls, size_t extra_size) { (void) cls; - (void) extra; - (void) extra_size; + enum TALER_Extension_Type type; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Received /management/extensions update event\n"); + "Received extensions update event\n"); + + if (sizeof(enum TALER_Extension_Type) != extra_size) + { + GNUNET_break (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Oops, incorrect size of extra for TALER_Extension_type\n"); + return; + } + + type = *(enum TALER_Extension_Type *) extra; + if (type <0 || type >= TALER_Extension_Max) + { + GNUNET_break (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Oops, incorrect type for TALER_Extension_type\n"); + return; + } + + // Get the config from the database as string + { + char *config_str; + enum GNUNET_DB_QueryStatus qs; + struct TALER_Extension *extension; + json_error_t err; + json_t *config; + enum GNUNET_GenericReturnValue ret; + + // TODO: make this a safe lookup + extension = TEH_extensions[type]; + + qs = TEH_plugin->get_extension_config (TEH_plugin->cls, + extension->name, + &config_str); + + if (qs < 0) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Couldn't get extension config\n"); + GNUNET_break (0); + return; + } + + // Parse the string as JSON + config = json_loads (config_str, JSON_DECODE_ANY, &err); + if (NULL == config) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse config for extension `%s' as JSON: %s (%s)\n", + extension->name, + err.text, + err.source); + GNUNET_break (0); + return; + } + + // Call the parser for the extension + ret = extension->parse_and_set_config (extension, config); + if (GNUNET_OK != ret) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Couldn't parse configuration for extension %s from the database", + extension->name); + GNUNET_break (0); + } + } } enum GNUNET_GenericReturnValue TEH_extensions_init () { - struct GNUNET_DB_EventHeaderP es = { - .size = htons (sizeof (es)), - .type = htons (TALER_DBEVENT_EXCHANGE_EXTENSIONS_UPDATED), - }; - - extensions_eh = TEH_plugin->event_listen (TEH_plugin->cls, - GNUNET_TIME_UNIT_FOREVER_REL, - &es, - &extension_update_event_cb, - NULL); - if (NULL == extensions_eh) + TEH_extensions = get_known_extensions (); + { - GNUNET_break (0); - return GNUNET_SYSERR; + struct GNUNET_DB_EventHeaderP ev = { + .size = htons (sizeof (ev)), + .type = htons (TALER_DBEVENT_EXCHANGE_EXTENSIONS_UPDATED), + }; + + extensions_eh = TEH_plugin->event_listen (TEH_plugin->cls, + GNUNET_TIME_UNIT_FOREVER_REL, + &ev, + &extension_update_event_cb, + NULL); + if (NULL == extensions_eh) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } } return GNUNET_OK; } @@ -89,22 +256,4 @@ TEH_extensions_done () } -void -TEH_extensions_update_state (void) -{ - /* TODO */ -#if 0 - struct GNUNET_DB_EventHeaderP es = { - .size = htons (sizeof (es)), - .type = htons (TALER_DBEVENT_EXCHANGE_WIRE_UPDATED), - }; - - TEH_plugin->event_notify (TEH_plugin->cls, - &es, - NULL, - 0); -#endif -} - - /* end of taler-exchange-httpd_extensions.c */ diff --git a/src/exchange/taler-exchange-httpd_extensions.h b/src/exchange/taler-exchange-httpd_extensions.h index 3c86e2662..4659b653e 100644 --- a/src/exchange/taler-exchange-httpd_extensions.h +++ b/src/exchange/taler-exchange-httpd_extensions.h @@ -40,12 +40,4 @@ TEH_extensions_init (void); void TEH_extensions_done (void); -/** - * Something changed in the database. Rebuild the extension state metadata. - * This function should be called if the exchange learns about a new signature - * from our master key. - */ -void -TEH_extensions_update_state (void); - #endif diff --git a/src/exchange/taler-exchange-httpd_keys.c b/src/exchange/taler-exchange-httpd_keys.c index 5d7476771..30bbe8ebb 100644 --- a/src/exchange/taler-exchange-httpd_keys.c +++ b/src/exchange/taler-exchange-httpd_keys.c @@ -736,10 +736,6 @@ destroy_key_helpers (struct HelperState *hs) * Looks up the AGE_RESTRICTED setting for a denomination in the config and * returns the age restriction (mask) accordingly. * - * FIXME: The mask is currently taken from the config. However, It MUST come - * from the database where it has been persisted after a signed call to the - * /management/extension API (TODO). - * * @param section_name Section in the configuration for the particular * denomination. */ @@ -748,15 +744,13 @@ load_age_mask (const char*section_name) { static const struct TALER_AgeMask null_mask = {0}; struct TALER_AgeMask age_mask = {0}; + const struct TALER_Extension *age_ext = + TEH_extensions[TALER_Extension_AgeRestriction]; - /* FIXME-oec: get age_mask from database, not from config */ - if (TALER_Extension_OK != TALER_get_age_mask (TEH_cfg, &age_mask)) + // Get the age mask from the extension, if configured + if (NULL != age_ext->config) { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - TALER_EXTENSION_SECTION_AGE_RESTRICTION, - "AGE_GROUPS", - "must be of form a:b:...:n:m, where 0<a<b<...<n<m<32\n"); - return null_mask; + age_mask = *(struct TALER_AgeMask *) age_ext->config; } if (age_mask.mask == 0) @@ -1450,7 +1444,6 @@ struct DenomKeyCtx * valid denomination keys? */ struct GNUNET_TIME_Relative min_dk_frequency; - }; @@ -1613,6 +1606,7 @@ setup_general_response_headers (struct TEH_KeyStateHandle *ksh, * @param signkeys list of sign keys to return * @param recoup list of revoked keys to return * @param denoms list of denominations to return + * @param age_restricted_denoms list of age restricted denominations to return, can be NULL * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue @@ -1621,7 +1615,8 @@ create_krd (struct TEH_KeyStateHandle *ksh, struct GNUNET_TIME_Timestamp last_cpd, json_t *signkeys, json_t *recoup, - json_t *denoms) + json_t *denoms, + json_t *age_restricted_denoms) { struct KeysResponseData krd; struct TALER_ExchangePublicKeyP exchange_pub; @@ -1693,6 +1688,8 @@ create_krd (struct TEH_KeyStateHandle *ksh, GNUNET_JSON_pack_data_auto ("eddsa_sig", &exchange_sig)); GNUNET_assert (NULL != keys); + + // Set wallet limit if KYC is configured if ( (TEH_KYC_NONE != TEH_kyc_config.mode) && (GNUNET_OK == TALER_amount_is_valid (&TEH_kyc_config.wallet_balance_limit)) ) @@ -1706,6 +1703,40 @@ create_krd (struct TEH_KeyStateHandle *ksh, &TEH_kyc_config.wallet_balance_limit))); } + // Signal support for the age-restriction extension, if so configured, and + // add the array of age-restricted denominations. + if (TEH_extension_enabled (TALER_Extension_AgeRestriction) && + NULL != age_restricted_denoms) + { + struct TALER_AgeMask *mask; + json_t *config; + + mask = (struct + TALER_AgeMask *) TEH_extensions[TALER_Extension_AgeRestriction]-> + config; + config = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_bool ("critical", false), + GNUNET_JSON_pack_string ("version", "1"), + GNUNET_JSON_pack_string ("age_groups", TALER_age_mask_to_string (mask))); + GNUNET_assert (NULL != config); + GNUNET_assert ( + 0 == + json_object_set_new ( + keys, + "age_restriction", + config)); + + GNUNET_assert ( + 0 == + json_object_set_new ( + keys, + "age_restricted_denoms", + age_restricted_denoms)); + } + + // TODO: signal support and configuration for the P2P extension, once + // implemented. + { char *keys_json; void *keys_jsonz; @@ -1772,7 +1803,8 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh) { json_t *recoup; struct SignKeyCtx sctx; - json_t *denoms; + json_t *denoms = NULL; + json_t *age_restricted_denoms = NULL; struct GNUNET_TIME_Timestamp last_cpd; struct GNUNET_CONTAINER_Heap *heap; struct GNUNET_HashContext *hash_context; @@ -1802,6 +1834,14 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh) } denoms = json_array (); GNUNET_assert (NULL != denoms); + + // If age restriction is enabled, initialize the array of age restricted denoms. + if (TEH_extension_enabled (TALER_Extension_AgeRestriction)) + { + age_restricted_denoms = json_array (); + GNUNET_assert (NULL != age_restricted_denoms); + } + last_cpd = GNUNET_TIME_UNIT_ZERO_TS; hash_context = GNUNET_CRYPTO_hash_context_start (); { @@ -1826,7 +1866,8 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh) last_cpd, sctx.signkeys, recoup, - denoms)) + denoms, + age_restricted_denoms)) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failed to generate key response data for %s\n", @@ -1837,6 +1878,8 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh) /* intentionally empty */; GNUNET_CONTAINER_heap_destroy (heap); json_decref (denoms); + if (NULL != age_restricted_denoms) + json_decref (age_restricted_denoms); json_decref (sctx.signkeys); json_decref (recoup); return GNUNET_SYSERR; @@ -1846,10 +1889,12 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh) GNUNET_CRYPTO_hash_context_read (hash_context, &dk->h_denom_pub, sizeof (struct GNUNET_HashCode)); - GNUNET_assert ( - 0 == - json_array_append_new ( - denoms, + + { + json_t *denom; + json_t *array; + + denom = GNUNET_JSON_PACK ( GNUNET_JSON_pack_data_auto ("master_sig", &dk->master_sig), @@ -1872,7 +1917,26 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh) TALER_JSON_pack_amount ("fee_refresh", &dk->meta.fee_refresh), TALER_JSON_pack_amount ("fee_refund", - &dk->meta.fee_refund)))); + &dk->meta.fee_refund)); + + /* Put the denom into the correct array - denoms or age_restricted_denoms - + * depending on the settings and the properties of the denomination */ + if (NULL != age_restricted_denoms && + 0 != dk->meta.age_restrictions.mask) + { + array = age_restricted_denoms; + } + else + { + array = denoms; + } + + GNUNET_assert ( + 0 == + json_array_append_new ( + array, + denom)); + } } } GNUNET_CONTAINER_heap_destroy (heap); @@ -1888,12 +1952,15 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh) last_cpd, sctx.signkeys, recoup, - denoms)) + denoms, + age_restricted_denoms)) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failed to generate key response data for %s\n", GNUNET_TIME_timestamp2s (last_cpd)); json_decref (denoms); + if (NULL != age_restricted_denoms) + json_decref (age_restricted_denoms); json_decref (sctx.signkeys); json_decref (recoup); return GNUNET_SYSERR; @@ -1909,6 +1976,8 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh) json_decref (sctx.signkeys); json_decref (recoup); json_decref (denoms); + if (NULL != age_restricted_denoms) + json_decref (age_restricted_denoms); return GNUNET_OK; } diff --git a/src/exchange/taler-exchange-httpd_management_extensions.c b/src/exchange/taler-exchange-httpd_management_extensions.c index 6a771bf43..96b855c3c 100644 --- a/src/exchange/taler-exchange-httpd_management_extensions.c +++ b/src/exchange/taler-exchange-httpd_management_extensions.c @@ -29,21 +29,17 @@ #include "taler-exchange-httpd_management.h" #include "taler-exchange-httpd_responses.h" #include "taler_extensions.h" +#include "taler_dbevents.h" +/** + * Extension carries the necessary data for a particular extension. + * + */ struct Extension { enum TALER_Extension_Type type; - json_t *config_json; - - // This union contains the parsed configuration for each extension. - union - { - // configuration for the age restriction - struct TALER_AgeMask mask; - - /* TODO oec - peer2peer config */ - }; + json_t *config; }; /** @@ -56,6 +52,38 @@ struct SetExtensionsContext struct TALER_MasterSignatureP *extensions_sigs; }; + +/** + * @brief verifies the signature a configuration with the offline master key. + * + * @param config configuration of an extension given as JSON object + * @param master_priv offline master public key of the exchange + * @param[out] master_sig signature + * @return GNUNET_OK on success, GNUNET_SYSERR otherwise + */ +static enum GNUNET_GenericReturnValue +config_verify ( + const json_t *config, + const struct TALER_MasterPublicKeyP *master_pub, + const struct TALER_MasterSignatureP *master_sig + ) +{ + enum GNUNET_GenericReturnValue ret; + struct TALER_ExtensionConfigHash h_config; + + ret = TALER_extension_config_hash (config, &h_config); + if (GNUNET_OK != ret) + { + GNUNET_break (0); + return ret; + } + + return TALER_exchange_offline_extension_config_hash_verify (h_config, + master_pub, + master_sig); +} + + /** * Function implementing database transaction to set the configuration of * extensions. It runs the transaction logic. @@ -77,9 +105,68 @@ set_extensions (void *cls, struct MHD_Connection *connection, MHD_RESULT *mhd_ret) { - // struct SetExtensionContext *sec = cls; + struct SetExtensionsContext *sec = cls; + + /* save the configurations of all extensions */ + for (uint32_t i = 0; i<sec->num_extensions; i++) + { + struct Extension *ext = &sec->extensions[i]; + struct TALER_MasterSignatureP *sig = &sec->extensions_sigs[i]; + enum GNUNET_DB_QueryStatus qs; + char *config; + + /* Sanity check. + * TODO: replace with general API to retrieve the extension-handler + */ + if (0 > ext->type || TALER_Extension_Max <= ext->type) + { + GNUNET_break (0); + return GNUNET_DB_STATUS_HARD_ERROR; + } + + config = json_dumps (ext->config, JSON_COMPACT | JSON_SORT_KEYS); + if (NULL == config) + { + GNUNET_break (0); + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_JSON_INVALID, + "convert configuration to string"); + return GNUNET_DB_STATUS_HARD_ERROR; + } + + qs = TEH_plugin->set_extension_config ( + TEH_plugin->cls, + TEH_extensions[ext->type]->name, + config, + sig); + + if (qs < 0) + { + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + return qs; + GNUNET_break (0); + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_STORE_FAILED, + "save extension configuration"); + } + + /* Success, trigger event */ + { + enum TALER_Extension_Type *type = &sec->extensions[i].type; + struct GNUNET_DB_EventHeaderP ev = { + .size = htons (sizeof (ev)), + .type = htons (TALER_DBEVENT_EXCHANGE_EXTENSIONS_UPDATED) + }; + TEH_plugin->event_notify (TEH_plugin->cls, + &ev, + type, + sizeof(*type)); + } + + } - // TODO oec return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; /* only 'success', so >=0, matters here */ } @@ -92,50 +179,51 @@ TEH_handler_management_post_extensions ( struct SetExtensionsContext sec = {0}; json_t *extensions; json_t *extensions_sigs; - struct GNUNET_JSON_Specification spec[] = { + struct GNUNET_JSON_Specification top_spec[] = { GNUNET_JSON_spec_json ("extensions", &extensions), GNUNET_JSON_spec_json ("extensions_sigs", &extensions_sigs), GNUNET_JSON_spec_end () }; - bool ok; MHD_RESULT ret; + // Parse the top level json structure { enum GNUNET_GenericReturnValue res; res = TALER_MHD_parse_json_data (connection, root, - spec); + top_spec); if (GNUNET_SYSERR == res) return MHD_NO; /* hard failure */ if (GNUNET_NO == res) return MHD_YES; /* failure */ } + // Ensure we have two arrays of the same size if (! (json_is_array (extensions) && json_is_array (extensions_sigs)) ) { GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); + GNUNET_JSON_parse_free (top_spec); return TALER_MHD_reply_with_error ( connection, MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PARAMETER_MALFORMED, - "array expected for extensions and extensions_sig"); + "array expected for extensions and extensions_sigs"); } sec.num_extensions = json_array_size (extensions_sigs); if (json_array_size (extensions) != sec.num_extensions) { GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); + GNUNET_JSON_parse_free (top_spec); return TALER_MHD_reply_with_error ( connection, MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PARAMETER_MALFORMED, - "arrays extensions and extensions_sig are not of equal size"); + "arrays extensions and extensions_sigs are not of the same size"); } GNUNET_log (GNUNET_ERROR_TYPE_INFO, @@ -145,116 +233,59 @@ TEH_handler_management_post_extensions ( struct Extension); sec.extensions_sigs = GNUNET_new_array (sec.num_extensions, struct TALER_MasterSignatureP); - ok = true; + // Now parse individual extensions and signatures from those arrays. for (unsigned int i = 0; i<sec.num_extensions; i++) { - - // 1. parse the extension + // 1. parse the extension out of the json + enum GNUNET_GenericReturnValue res; + const struct TALER_Extension *extension; + const char *name; + struct GNUNET_JSON_Specification ext_spec[] = { + GNUNET_JSON_spec_string ("extension", + &name), + GNUNET_JSON_spec_json ("config", + &sec.extensions[i].config), + GNUNET_JSON_spec_end () + }; + + res = TALER_MHD_parse_json_array (connection, + extensions, + ext_spec, + i, + -1); + if (GNUNET_SYSERR == res) { - enum GNUNET_GenericReturnValue res; - const char *name; - struct GNUNET_JSON_Specification ispec[] = { - GNUNET_JSON_spec_string ("extension", - &name), - GNUNET_JSON_spec_json ("config", - &sec.extensions[i].config_json), - GNUNET_JSON_spec_end () - }; - - res = TALER_MHD_parse_json_array (connection, - extensions, - ispec, - i, - -1); - if (GNUNET_SYSERR == res) - { - ret = MHD_NO; /* hard failure */ - ok = false; - break; - } - if (GNUNET_NO == res) - { - ret = MHD_YES; - ok = false; - break; - } - - // Make sure name refers to a supported extension - { - bool found = false; - for (unsigned int k = 0; k < TALER_Extension_Max; k++) - { - if (0 == strncmp (name, - TEH_extensions[k].name, - strlen (TEH_extensions[k].name))) - { - sec.extensions[i].type = TEH_extensions[k].type; - found = true; - break; - } - } - - if (! found) - { - GNUNET_free (sec.extensions); - GNUNET_free (sec.extensions_sigs); - GNUNET_JSON_parse_free (spec); - GNUNET_JSON_parse_free (ispec); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "invalid extension type"); - } - } - - // We have a JSON object for the extension. Increment its refcount and - // free the parser. - // TODO: is this correct? - json_incref (sec.extensions[i].config_json); - GNUNET_JSON_parse_free (ispec); + ret = MHD_NO; /* hard failure */ + goto CLEANUP; + } + if (GNUNET_NO == res) + { + ret = MHD_YES; + goto CLEANUP; + } - // Make sure the config is sound - { - switch (sec.extensions[i].type) - { - case TALER_Extension_AgeRestriction: - if (GNUNET_OK != TALER_agemask_parse_json ( - sec.extensions[i].config_json, - &sec.extensions[i].mask)) - { - GNUNET_free (sec.extensions); - GNUNET_free (sec.extensions_sigs); - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "invalid mask for age restriction"); - } - break; - - case TALER_Extension_Peer2Peer: /* TODO */ - ok = false; - ret = MHD_NO; - goto BREAK; - - default: - /* not reachable */ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "shouldn't be reached in handler for /management/extensions\n"); - ok = false; - ret = MHD_NO; - goto BREAK; - } - } + /* 2. Make sure name refers to a supported extension */ + if (GNUNET_OK != TALER_extension_get_by_name (name, + (const struct + TALER_Extension **) + TEH_extensions, + &extension)) + { + ret = TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "invalid extension type"); + goto CLEANUP; } - // 2. parse the signature + sec.extensions[i].type = extension->type; + + /* 3. Extract the signature out of the json array */ { enum GNUNET_GenericReturnValue res; - struct GNUNET_JSON_Specification ispec[] = { + struct GNUNET_JSON_Specification sig_spec[] = { GNUNET_JSON_spec_fixed_auto (NULL, &sec.extensions_sigs[i]), GNUNET_JSON_spec_end () @@ -262,81 +293,61 @@ TEH_handler_management_post_extensions ( res = TALER_MHD_parse_json_array (connection, extensions_sigs, - ispec, + sig_spec, i, -1); if (GNUNET_SYSERR == res) { ret = MHD_NO; /* hard failure */ - ok = false; - break; + goto CLEANUP; } if (GNUNET_NO == res) { ret = MHD_YES; - ok = false; - break; + goto CLEANUP; } } - // 3. verify the signature + /* 4. Verify the signature of the config */ + if (GNUNET_OK != config_verify ( + sec.extensions[i].config, + &TEH_master_public_key, + &sec.extensions_sigs[i])) { - enum GNUNET_GenericReturnValue res; + ret = TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "invalid signature for extension"); + goto CLEANUP; + } + + /* 5. Make sure the config is sound */ + if (GNUNET_OK != extension->test_config (sec.extensions[i].config)) + { + GNUNET_JSON_parse_free (ext_spec); + ret = TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "invalid configuration for extension"); + goto CLEANUP; - switch (sec.extensions[i].type) - { - case TALER_Extension_AgeRestriction: - res = TALER_exchange_offline_extension_agemask_verify ( - sec.extensions[i].mask, - &TEH_master_public_key, - &sec.extensions_sigs[i]); - if (GNUNET_OK != res) - { - GNUNET_free (sec.extensions); - GNUNET_free (sec.extensions_sigs); - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "invalid signature for age mask"); - } - break; - - case TALER_Extension_Peer2Peer: /* TODO */ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Peer2peer not yet supported in handler for /management/extensions\n"); - ok = false; - ret = MHD_NO; - goto BREAK; - - default: - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "shouldn't be reached in handler for /management/extensions\n"); - ok = false; - ret = MHD_NO; - /* not reachable */ - goto BREAK; - } } - } -BREAK: - if (! ok) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failure to handle /management/extensions\n"); - GNUNET_free (sec.extensions); - GNUNET_free (sec.extensions_sigs); - GNUNET_JSON_parse_free (spec); - return ret; - } + /* We have a validly signed JSON object for the extension. + * Increment its refcount and free the parser for the extension. + */ + json_incref (sec.extensions[i].config); + GNUNET_JSON_parse_free (ext_spec); + } /* for-loop */ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Received %u extensions\n", sec.num_extensions); + // now run the transaction to persist the configurations { enum GNUNET_GenericReturnValue res; @@ -347,19 +358,29 @@ BREAK: &set_extensions, &sec); - GNUNET_free (sec.extensions); - GNUNET_free (sec.extensions_sigs); - GNUNET_JSON_parse_free (spec); if (GNUNET_SYSERR == res) - return ret; + goto CLEANUP; } - return TALER_MHD_reply_static ( + ret = TALER_MHD_reply_static ( connection, MHD_HTTP_NO_CONTENT, NULL, NULL, 0); + +CLEANUP: + for (unsigned int i = 0; i < sec.num_extensions; i++) + { + if (NULL != sec.extensions[i].config) + { + json_decref (sec.extensions[i].config); + } + } + GNUNET_free (sec.extensions); + GNUNET_free (sec.extensions_sigs); + GNUNET_JSON_parse_free (top_spec); + return ret; } diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index 97782bd17..268279f3a 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -1,18 +1,18 @@ /* - This file is part of TALER - Copyright (C) 2014--2021 Taler Systems SA + 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 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. + 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 <http://www.gnu.org/licenses/> -*/ + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ /** * @file plugin_exchangedb_postgres.c @@ -211,7 +211,7 @@ prepare_statements (struct PostgresClosure *pg) enum GNUNET_GenericReturnValue ret; struct GNUNET_PQ_PreparedStatement ps[] = { /* Used in #postgres_insert_denomination_info() and - #postgres_add_denomination_key() */ + #postgres_add_denomination_key() */ GNUNET_PQ_make_prepare ( "denomination_insert", "INSERT INTO denominations " @@ -222,8 +222,8 @@ prepare_statements (struct PostgresClosure *pg) ",expire_withdraw" ",expire_deposit" ",expire_legal" - ",coin_val" /* value of this denom */ - ",coin_frac" /* fractional value of this denom */ + ",coin_val" /* value of this denom */ + ",coin_frac" /* fractional value of this denom */ ",fee_withdraw_val" ",fee_withdraw_frac" ",fee_deposit_val" @@ -245,8 +245,8 @@ prepare_statements (struct PostgresClosure *pg) ",expire_withdraw" ",expire_deposit" ",expire_legal" - ",coin_val" /* value of this denom */ - ",coin_frac" /* fractional value of this denom */ + ",coin_val" /* value of this denom */ + ",coin_frac" /* fractional value of this denom */ ",fee_withdraw_val" ",fee_withdraw_frac" ",fee_deposit_val" @@ -268,8 +268,8 @@ prepare_statements (struct PostgresClosure *pg) ",expire_withdraw" ",expire_deposit" ",expire_legal" - ",coin_val" /* value of this denom */ - ",coin_frac" /* fractional value of this denom */ + ",coin_val" /* value of this denom */ + ",coin_frac" /* fractional value of this denom */ ",fee_withdraw_val" ",fee_withdraw_frac" ",fee_deposit_val" @@ -332,8 +332,8 @@ prepare_statements (struct PostgresClosure *pg) ",expire_withdraw" ",expire_deposit" ",expire_legal" - ",coin_val" /* value of this denom */ - ",coin_frac" /* fractional value of this denom */ + ",coin_val" /* value of this denom */ + ",coin_frac" /* fractional value of this denom */ ",fee_withdraw_val" ",fee_withdraw_frac" ",fee_deposit_val" @@ -766,7 +766,7 @@ prepare_statements (struct PostgresClosure *pg) See also: https://stackoverflow.com/questions/34708509/how-to-use-returning-with-on-conflict-in-postgresql/37543015#37543015 - */ + */ GNUNET_PQ_make_prepare ( "insert_known_coin", "WITH dd" @@ -2743,6 +2743,23 @@ prepare_statements (struct PostgresClosure *pg) " AND start_row=$2" " AND end_row=$3", 3), + /* Used in #postgres_set_extension_config */ + GNUNET_PQ_make_prepare ( + "set_extension_config", + "WITH upsert AS " + " (UPDATE extensions " + " SET config=$2 " + " config_sig=$3 " + " WHERE name=$1 RETURNING *) " + "INSERT INTO extensions (config, config_sig) VALUES ($2, $3) " + "WHERE NOT EXISTS (SELECT * FROM upsert);", + 3), + /* Used in #postgres_get_extension_config */ + GNUNET_PQ_make_prepare ( + "get_extension_config", + "SELECT (config) FROM extensions" + " WHERE name=$1;", + 1), GNUNET_PQ_PREPARED_STATEMENT_END }; @@ -3396,11 +3413,11 @@ dominations_cb_helper (void *cls, /** -* Function called to invoke @a cb on every known denomination key (revoked -* and non-revoked) that has been signed by the master key. Runs in its own -* read-only transaction. -* -* + * Function called to invoke @a cb on every known denomination key (revoked + * and non-revoked) that has been signed by the master key. Runs in its own + * read-only transaction. + * + * * @param cls the @e cls of this struct with the plugin-specific state * @param cb function to call on each denomination key * @param cb_cls closure for @a cb @@ -3513,7 +3530,7 @@ postgres_iterate_active_signkeys (void *cls, void *cb_cls) { struct PostgresClosure *pg = cls; - struct GNUNET_TIME_Absolute now; + struct GNUNET_TIME_Absolute now = {0}; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_absolute_time (&now), GNUNET_PQ_query_param_end @@ -3600,7 +3617,7 @@ auditors_cb_helper (void *cls, /** * Function called to invoke @a cb on every active auditor. Disabled * auditors are skipped. Runs in its own read-only transaction. - * + * * @param cls the @e cls of this struct with the plugin-specific state * @param cb function to call on each active auditor * @param cb_cls closure for @a cb @@ -4470,6 +4487,8 @@ compute_shard (const struct TALER_MerchantPublicKeyP *merchant_pub) * Perform deposit operation, checking for sufficient balance * of the coin and possibly persisting the deposit details. * + * FIXME: parameters missing in description! + * * @param cls the `struct PostgresClosure` with the plugin-specific state * @param deposit deposit operation details * @param known_coin_id row of the coin in the known_coins table @@ -4908,7 +4927,7 @@ add_bank_to_exchange (void *cls, tail = append_rh (rhc); tail->type = TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE; tail->details.bank = bt; - } /* end of 'while (0 < rows)' */ + } /* end of 'while (0 < rows)' */ } @@ -5033,7 +5052,7 @@ add_recoup (void *cls, tail = append_rh (rhc); tail->type = TALER_EXCHANGEDB_RO_RECOUP_COIN; tail->details.recoup = recoup; - } /* end of 'while (0 < rows)' */ + } /* end of 'while (0 < rows)' */ } @@ -5093,7 +5112,7 @@ add_exchange_to_bank (void *cls, tail = append_rh (rhc); tail->type = TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK; tail->details.closing = closing; - } /* end of 'while (0 < rows)' */ + } /* end of 'while (0 < rows)' */ } @@ -5361,7 +5380,7 @@ postgres_get_ready_deposit (void *cls, void *deposit_cb_cls) { struct PostgresClosure *pg = cls; - struct GNUNET_TIME_Absolute now; + struct GNUNET_TIME_Absolute now = {0}; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_absolute_time (&now), GNUNET_PQ_query_param_uint64 (&start_shard_row), @@ -6260,7 +6279,7 @@ postgres_get_refresh_reveal (void *cls, case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: goto cleanup; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: - default: /* can have more than one result */ + default: /* can have more than one result */ break; } switch (grctx.qs) @@ -6269,7 +6288,7 @@ postgres_get_refresh_reveal (void *cls, case GNUNET_DB_STATUS_SOFT_ERROR: goto cleanup; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: /* should be impossible */ + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: /* should be impossible */ break; } @@ -11395,6 +11414,68 @@ postgres_delete_shard_locks (void *cls) /** + * Function called to save the configuration of an extension + * (age-restriction, peer2peer, ...). After succesfull storage of the + * configuration it triggers the corresponding event. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param extension_name the name of the extension + * @param config JSON object of the configuration as string + * @param config_sig signature of the configuration by the offline master key + * @return transaction status code + */ +enum GNUNET_DB_QueryStatus +postgres_set_extension_config (void *cls, + const char *extension_name, + const char *config, + const struct TALER_MasterSignatureP *config_sig) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (extension_name), + GNUNET_PQ_query_param_string (config), + GNUNET_PQ_query_param_auto_from_type (config_sig), + GNUNET_PQ_query_param_end + }; + + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "set_extension_config", + params); +} + + +/** + * Function called to get the configuration of an extension + * (age-restriction, peer2peer, ...) + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param extension_name the name of the extension + * @param[out] config JSON object of the configuration as string + * @return transaction status code + */ +enum GNUNET_DB_QueryStatus +postgres_get_extension_config (void *cls, + const char *extension_name, + char **config) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (extension_name), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_string ("config", config), + GNUNET_PQ_result_spec_end + }; + + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "get_extension_config", + params, + rs); +} + + +/** * Initialize Postgres database subsystem. * * @param cls a configuration instance @@ -11628,6 +11709,10 @@ libtaler_plugin_exchangedb_postgres_init (void *cls) = &postgres_release_revolving_shard; plugin->delete_shard_locks = &postgres_delete_shard_locks; + plugin->set_extension_config + = &postgres_set_extension_config; + plugin->get_extension_config + = &postgres_get_extension_config; return plugin; } diff --git a/src/include/taler_crypto_lib.h b/src/include/taler_crypto_lib.h index 4ffee54c9..e608effa6 100644 --- a/src/include/taler_crypto_lib.h +++ b/src/include/taler_crypto_lib.h @@ -542,6 +542,19 @@ struct TALER_PickupIdentifierP }; +/** + * @brief Salted hash over the JSON object representing the configuration of an + * extension. + */ +struct TALER_ExtensionConfigHash +{ + /** + * Actual hash value. + */ + struct GNUNET_HashCode hash; +}; + + GNUNET_NETWORK_STRUCT_END @@ -2521,30 +2534,31 @@ TALER_merchant_wire_signature_make ( /* **************** /management/extensions offline signing **************** */ /** - * Create a signature for age restriction groups + * Create a signature for the hash of the configuration of an extension * - * @param mask The bitmask representing age groups + * @param h_config hash of the JSON object representing the configuration * @param master_priv private key to sign with * @param[out] master_sig where to write the signature */ void -TALER_exchange_offline_extension_agemask_sign ( - const struct TALER_AgeMask mask, +TALER_exchange_offline_extension_config_hash_sign ( + const struct TALER_ExtensionConfigHash h_config, const struct TALER_MasterPrivateKeyP *master_priv, struct TALER_MasterSignatureP *master_sig); /** - * Verify the signature in @a master_sig. + * Verify the signature in @a master_sig of the given hash, taken over the JSON + * blob representing the configuration of an extension * - * @param mask bit mask representing an age group for age restriction + * @param h_config hash of the JSON blob of a configuration of an extension * @param master_pub master public key of the exchange * @param master_sig signature of the exchange * @return #GNUNET_OK if signature is valid */ enum GNUNET_GenericReturnValue -TALER_exchange_offline_extension_agemask_verify ( - const struct TALER_AgeMask mask, +TALER_exchange_offline_extension_config_hash_verify ( + const struct TALER_ExtensionConfigHash h_config, const struct TALER_MasterPublicKeyP *master_pub, const struct TALER_MasterSignatureP *master_sig ); diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index ee691084e..4aa80b674 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -4025,8 +4025,35 @@ struct TALER_EXCHANGEDB_Plugin (*delete_shard_locks)(void *cls); /** - * TODO-oec: add function for adding extension config + * Function called to save the configuration of an extension + * (age-restriction, peer2peer, ...) + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param extension_name the name of the extension + * @param config JSON object of the configuration as string + * @param config_sig signature of the configuration by the offline master key + * @return transaction status code */ + enum GNUNET_DB_QueryStatus + (*set_extension_config)(void *cls, + const char *extension_name, + const char *config, + const struct TALER_MasterSignatureP *config_sig); + + /** + * Function called to retrieve the configuration of an extension + * (age-restriction, peer2peer, ...) + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param extension_name the name of the extension + * @param[out] config JSON object of the configuration as string + * @param[out] config_sig signature of the configuration by the master key + * @return transaction status code + */ + enum GNUNET_DB_QueryStatus + (*get_extension_config)(void *cls, + const char *extension_name, + char **config); }; diff --git a/src/include/taler_extensions.h b/src/include/taler_extensions.h index b6d5c826c..199776eb7 100644 --- a/src/include/taler_extensions.h +++ b/src/include/taler_extensions.h @@ -23,6 +23,7 @@ #include <gnunet/gnunet_util_lib.h> #include "taler_crypto_lib.h" +#include "taler_json_lib.h" #define TALER_EXTENSION_SECTION_PREFIX "exchange-extension-" @@ -39,22 +40,42 @@ enum TALER_Extension_Type { TALER_Extension_AgeRestriction = 0, TALER_Extension_Peer2Peer = 1, - TALER_Extension_Max = 2 + TALER_Extension_Max = 2 // Must be last }; +/* + * TODO oec: documentation + */ struct TALER_Extension { enum TALER_Extension_Type type; char *name; bool critical; void *config; + + enum GNUNET_GenericReturnValue (*test_config)(const json_t *config); + enum GNUNET_GenericReturnValue (*parse_and_set_config)(struct + TALER_Extension *this, + const json_t *config); + json_t *(*config_to_json)(const struct TALER_Extension *this); }; -/* - * TALER Peer2Peer Extension - * FIXME oec +/** + * Generic functions for extensions */ +/** + * 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 an entry of type TALER_Extension_Max + * @param[out] ext set to the extension, if found, NULL otherwise + * @return GNUNET_OK if extension was found, GNUNET_NO otherwise + */ +enum GNUNET_GenericReturnValue +TALER_extension_get_by_name (const char *name, + const struct TALER_Extension **extensions, + const struct TALER_Extension **ext); /* * TALER Age Restriction Extension @@ -72,7 +93,19 @@ struct TALER_Extension << 21) /** - * @param groups String representation of age groups, like: "8:10:12:14:16:18:21" + * @brief Parses a string as a list of age groups. + * + * The string must consist of a colon-separated list of increasing integers + * between 0 and 31. Each entry represents the beginning of a new age group. + * F.e. the string "8:10:12:14:16:18:21" parses into the following list of age + * groups + * 0-7, 8-9, 10-11, 12-13, 14-15, 16-17, 18-20, 21-... + * which then is represented as bit mask with the corresponding bits set: + * 31 24 16 8 0 + * | | | | | + * oooooooo oo1oo1o1 o1o1o1o1 ooooooo1 + * + * @param groups String representation of age groups * @param[out] mask Mask representation for age restriction. * @return Error, if age groups were invalid, OK otherwise. */ @@ -81,6 +114,19 @@ TALER_parse_age_group_string (char *groups, struct TALER_AgeMask *mask); /** + * 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 *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. @@ -90,4 +136,11 @@ TALER_parse_age_group_string (char *groups, enum TALER_Extension_ReturnValue TALER_get_age_mask (const struct GNUNET_CONFIGURATION_Handle *cfg, struct TALER_AgeMask *mask); + + +/* + * TALER Peer2Peer Extension + * TODO oec + */ + #endif diff --git a/src/include/taler_json_lib.h b/src/include/taler_json_lib.h index ac8793ebc..102b3a6ff 100644 --- a/src/include/taler_json_lib.h +++ b/src/include/taler_json_lib.h @@ -532,7 +532,7 @@ TALER_JSON_wire_to_payto (const json_t *wire_s); /** - * Hash @a extensions. + * Hash @a extensions in deposits. * * @param extensions contract extensions to hash * @param[out] ech where to write the extension hash @@ -541,6 +541,16 @@ void TALER_deposit_extension_hash (const json_t *extensions, struct TALER_ExtensionContractHash *ech); +/** + * Hash the @a config of an extension, given as JSON + * + * @param config configuration of the extension + * @param[out] eh where to write the extension hash + * @return GNUNET_OK on success, GNUNET_SYSERR on failure + */ +enum GNUNET_GenericReturnValue +TALER_extension_config_hash (const json_t *config, + struct TALER_ExtensionConfigHash *eh); /** * Parses a JSON object { "extension": "age_restriction", "mask": <uint32> }. @@ -553,7 +563,6 @@ enum GNUNET_GenericReturnValue TALER_agemask_parse_json (const json_t *root, struct TALER_AgeMask *mask); - #endif /* TALER_JSON_LIB_H_ */ /* End of taler_json_lib.h */ diff --git a/src/include/taler_signatures.h b/src/include/taler_signatures.h index d9fa7065b..947c7e831 100644 --- a/src/include/taler_signatures.h +++ b/src/include/taler_signatures.h @@ -967,9 +967,9 @@ struct TALER_MasterDelWirePS /* * @brief Signature made by the exchange offline key over the - * configuration of the age restriction extension. + * configuration of an extension. */ -struct TALER_MasterExtensionAgeRestrictionPS +struct TALER_MasterExtensionConfigurationPS { /** * Purpose is #TALER_SIGNATURE_MASTER_EXTENSION. Signed @@ -978,29 +978,11 @@ struct TALER_MasterExtensionAgeRestrictionPS struct GNUNET_CRYPTO_EccSignaturePurpose purpose; /** - * Bit mask representing the lits of age groups, see TALER_AgeMask for a - * description. + * Hash of the JSON object that represents the configuration of an extension. */ - struct TALER_AgeMask mask; + struct TALER_ExtensionConfigHash h_config GNUNET_PACKED; }; -#if 0 -/* - * @brief Signature made by the exchange offline key over the - * configuration of the peer2peer extension. - */ -struct TALER_MasterExtensionPeer2PeerPS -{ - /** - * Purpose is #TALER_SIGNATURE_MASTER_EXTENSION. Signed - * by a `struct TALER_MasterPublicKeyP` using EdDSA. - */ - struct GNUNET_CRYPTO_EccSignaturePurpose purpose; - - // TODO oec -}; -#endif - /** * @brief Information about a denomination key. Denomination keys * are used to sign coins of a certain value into existence. diff --git a/src/json/json.c b/src/json/json.c index af2b84e27..956aad1a5 100644 --- a/src/json/json.c +++ b/src/json/json.c @@ -1009,4 +1009,14 @@ TALER_deposit_extension_hash (const json_t *extensions, } +enum GNUNET_GenericReturnValue +TALER_extension_config_hash (const json_t *config, + struct TALER_ExtensionConfigHash *ech) +{ + return dump_and_hash (config, + "taler-extension-configuration", + &ech->hash); +} + + /* End of json/json.c */ diff --git a/src/lib/exchange_api_management_post_extensions.c b/src/lib/exchange_api_management_post_extensions.c index a2de2454c..862ff7117 100644 --- a/src/lib/exchange_api_management_post_extensions.c +++ b/src/lib/exchange_api_management_post_extensions.c @@ -153,32 +153,17 @@ TALER_EXCHANGE_management_post_extensions ( GNUNET_assert (NULL != extensions); for (unsigned int i = 0; i<pkd->num_extensions; i++) { - json_t *config; - const struct TALER_AgeMask *mask; + const json_t *config; const struct TALER_Extension *ext = &pkd->extensions[i]; - switch (ext->type) - { - // TODO: case TALER_Extension_Peer2Peer - case TALER_Extension_AgeRestriction: - mask = (const struct TALER_AgeMask *) (&ext->config); - config = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("extension", - ext->name), - GNUNET_JSON_pack_data_auto ("mask", - &mask->mask)); - GNUNET_assert (NULL != config); - break; - default: - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Extension not supported.\n"); - } + config = ext->config_to_json (ext); + GNUNET_assert (NULL != config); GNUNET_assert (0 == json_array_append_new ( extensions, GNUNET_JSON_PACK ( - GNUNET_JSON_pack_data_auto ("name", + GNUNET_JSON_pack_data_auto ("extension", &ext->name), GNUNET_JSON_pack_data_auto ("config", config) diff --git a/src/util/Makefile.am b/src/util/Makefile.am index cae1a205e..55ebb4dff 100644 --- a/src/util/Makefile.am +++ b/src/util/Makefile.am @@ -72,6 +72,7 @@ libtalerutil_la_SOURCES = \ crypto_wire.c \ denom.c \ exchange_signatures.c \ + extensions.c \ extension_age_restriction.c \ getopt.c \ lang.c \ diff --git a/src/util/extension_age_restriction.c b/src/util/extension_age_restriction.c index c0efd7cd1..42a58b2e9 100644 --- a/src/util/extension_age_restriction.c +++ b/src/util/extension_age_restriction.c @@ -23,7 +23,6 @@ #include "taler_extensions.h" #include "stdint.h" - /** * * @param cfg Handle to the GNUNET configuration @@ -137,12 +136,14 @@ TALER_parse_age_group_string (char *groups, /** + * 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 (struct TALER_AgeMask *m) +TALER_age_mask_to_string (const struct TALER_AgeMask *m) { uint32_t mask = m->mask; unsigned int n = 0; diff --git a/src/util/extensions.c b/src/util/extensions.c new file mode 100644 index 000000000..87dd16b4d --- /dev/null +++ b/src/util/extensions.c @@ -0,0 +1,49 @@ +/* + 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 <http://www.gnu.org/licenses/> + */ +/** + * @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 */ diff --git a/src/util/offline_signatures.c b/src/util/offline_signatures.c index 7fbec826b..1240a8bc5 100644 --- a/src/util/offline_signatures.c +++ b/src/util/offline_signatures.c @@ -491,66 +491,40 @@ TALER_exchange_offline_wire_fee_verify ( void -TALER_exchange_offline_extension_agemask_sign ( - const struct TALER_AgeMask mask, +TALER_exchange_offline_extension_config_hash_sign ( + const struct TALER_ExtensionConfigHash h_config, const struct TALER_MasterPrivateKeyP *master_priv, struct TALER_MasterSignatureP *master_sig) { - struct TALER_MasterExtensionAgeRestrictionPS ar = { + struct TALER_MasterExtensionConfigurationPS ec = { .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_EXTENSION), - .purpose.size = htonl (sizeof(ar)), - .mask = mask + .purpose.size = htonl (sizeof(ec)), + .h_config = h_config }; GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv, - &ar, + &ec, &master_sig->eddsa_signature); } enum GNUNET_GenericReturnValue -TALER_exchange_offline_extension_agemask_verify ( - const struct TALER_AgeMask mask, +TALER_exchange_offline_extension_config_hash_verify ( + const struct TALER_ExtensionConfigHash h_config, const struct TALER_MasterPublicKeyP *master_pub, const struct TALER_MasterSignatureP *master_sig ) { - struct TALER_MasterExtensionAgeRestrictionPS ar = { + struct TALER_MasterExtensionConfigurationPS ec = { .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_EXTENSION), - .purpose.size = htonl (sizeof(ar)), - .mask = mask + .purpose.size = htonl (sizeof(ec)), + .h_config = h_config }; - return - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_EXTENSION, - &ar, - &master_sig->eddsa_signature, - &master_pub->eddsa_pub); -} - -#if 0 -/* TODO peer2peer */ -void -TALER_exchange_offline_extension_p2p_sign ( - // TODO - const struct TALER_MasterPrivateKeyP *master_priv, - struct TALER_MasterSignatureP *master_sig) -{ - // TODO -} - - -enum GNUNET_GenericReturnValue -TALER_exchange_offline_extension_p2p_verify ( - // TODO - const struct TALER_MasterPublicKeyP *master_pub, - const struct TALER_MasterSignatureP *master_sig, - ) -{ - // TODO - return GNUNET_FALSE; + return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_EXTENSION, + &ec, + &master_sig->eddsa_signature, + &master_pub->eddsa_pub); } -#endif - /* end of offline_signatures.c */ |