summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
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
commitcc7d7707ab2bd43bc9e95c0eeec9ce95cdc0c523 (patch)
tree472e895b321e539f4675f016a285d6f6e6436b76 /src
parentb49fac3d5892f75a2eb7fbfbca0056965c6967c7 (diff)
downloadexchange-cc7d7707ab2bd43bc9e95c0eeec9ce95cdc0c523.tar.gz
exchange-cc7d7707ab2bd43bc9e95c0eeec9ce95cdc0c523.tar.bz2
exchange-cc7d7707ab2bd43bc9e95c0eeec9ce95cdc0c523.zip
[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
Diffstat (limited to 'src')
-rw-r--r--src/exchange/taler-exchange-httpd.c56
-rw-r--r--src/exchange/taler-exchange-httpd.h7
-rw-r--r--src/exchange/taler-exchange-httpd_extensions.c219
-rw-r--r--src/exchange/taler-exchange-httpd_extensions.h8
-rw-r--r--src/exchange/taler-exchange-httpd_keys.c111
-rw-r--r--src/exchange/taler-exchange-httpd_management_extensions.c377
-rw-r--r--src/exchangedb/plugin_exchangedb_postgres.c153
-rw-r--r--src/include/taler_crypto_lib.h30
-rw-r--r--src/include/taler_exchangedb_plugin.h29
-rw-r--r--src/include/taler_extensions.h63
-rw-r--r--src/include/taler_json_lib.h13
-rw-r--r--src/include/taler_signatures.h26
-rw-r--r--src/json/json.c10
-rw-r--r--src/lib/exchange_api_management_post_extensions.c23
-rw-r--r--src/util/Makefile.am1
-rw-r--r--src/util/extension_age_restriction.c5
-rw-r--r--src/util/extensions.c49
-rw-r--r--src/util/offline_signatures.c56
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 */