[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
This commit is contained in:
Özgür Kesim 2022-01-08 14:40:20 +01:00
parent b49fac3d58
commit cc7d7707ab
Signed by: oec
GPG Key ID: 3D76A56D79EDD9D7
18 changed files with 824 additions and 414 deletions

View File

@ -1,18 +1,18 @@
/* /*
This file is part of TALER This file is part of TALER
Copyright (C) 2014-2021 Taler Systems SA Copyright (C) 2014-2021 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the 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 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. 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 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 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 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 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/> TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
/** /**
* @file taler-exchange-httpd.c * @file taler-exchange-httpd.c
* @brief Serve the HTTP interface of the exchange * @brief Serve the HTTP interface of the exchange
@ -148,23 +148,9 @@ int TEH_check_invariants_flag;
bool TEH_suicide; bool TEH_suicide;
/** /**
* The global manifest with the list supported extensions, sorted by * Global register of extensions
* TALER_Extension_Type. */
**/ struct TALER_Extension **TEH_extensions;
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
},
};
/** /**
* Value to return from main() * Value to return from main()
@ -485,7 +471,7 @@ proceed_with_handler (struct TEH_RequestContext *rc,
if (GNUNET_SYSERR == res) if (GNUNET_SYSERR == res)
{ {
GNUNET_assert (NULL == root); 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) || if ( (GNUNET_NO == res) ||
(NULL == root) ) (NULL == root) )
@ -528,8 +514,8 @@ proceed_with_handler (struct TEH_RequestContext *rc,
sizeof (emsg), sizeof (emsg),
"Got %u/%u segments for %s request ('%s')", "Got %u/%u segments for %s request ('%s')",
(NULL == args[i - 1]) (NULL == args[i - 1])
? i - 1 ? i - 1
: i + ((NULL != fin) ? 1 : 0), : i + ((NULL != fin) ? 1 : 0),
rh->nargs, rh->nargs,
rh->url, rh->url,
url); url);
@ -553,7 +539,7 @@ proceed_with_handler (struct TEH_RequestContext *rc,
root, root,
args); args);
else /* We also only have "POST" or "GET" in the API for at this point 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, ret = rh->handler.get (rc,
args); args);
} }
@ -1120,7 +1106,7 @@ handle_mhd_request (void *cls,
if (0 == strcasecmp (method, if (0 == strcasecmp (method,
MHD_HTTP_METHOD_HEAD)) 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 */ /* parse first part of URL */
{ {
@ -1954,8 +1940,8 @@ run (void *cls,
MHD_OPTION_CONNECTION_TIMEOUT, MHD_OPTION_CONNECTION_TIMEOUT,
connection_timeout, connection_timeout,
(0 == allow_address_reuse) (0 == allow_address_reuse)
? MHD_OPTION_END ? MHD_OPTION_END
: MHD_OPTION_LISTENING_ADDRESS_REUSE, : MHD_OPTION_LISTENING_ADDRESS_REUSE,
(unsigned int) allow_address_reuse, (unsigned int) allow_address_reuse,
MHD_OPTION_END); MHD_OPTION_END);
if (NULL == mhd) if (NULL == mhd)

View File

@ -202,9 +202,12 @@ extern volatile bool MHD_terminating;
extern struct GNUNET_CURL_Context *TEH_curl_ctx; 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. * @brief Struct describing an URL and the handler for it.

View File

@ -24,8 +24,107 @@
#include "taler-exchange-httpd_extensions.h" #include "taler-exchange-httpd_extensions.h"
#include "taler_json_lib.h" #include "taler_json_lib.h"
#include "taler_mhd_lib.h" #include "taler_mhd_lib.h"
#include "taler_extensions.h"
#include <jansson.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 * Handler listening for extensions updates by other exchange
@ -33,7 +132,6 @@
*/ */
static struct GNUNET_DB_EventHandler *extensions_eh; static struct GNUNET_DB_EventHandler *extensions_eh;
/** /**
* Function called whenever another exchange process has updated * Function called whenever another exchange process has updated
* the extensions data in the database. * the extensions data in the database.
@ -48,30 +146,99 @@ extension_update_event_cb (void *cls,
size_t extra_size) size_t extra_size)
{ {
(void) cls; (void) cls;
(void) extra; enum TALER_Extension_Type type;
(void) extra_size;
GNUNET_log (GNUNET_ERROR_TYPE_INFO, 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 enum GNUNET_GenericReturnValue
TEH_extensions_init () TEH_extensions_init ()
{ {
struct GNUNET_DB_EventHeaderP es = { TEH_extensions = get_known_extensions ();
.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)
{ {
GNUNET_break (0); struct GNUNET_DB_EventHeaderP ev = {
return GNUNET_SYSERR; .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; 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 */ /* end of taler-exchange-httpd_extensions.c */

View File

@ -40,12 +40,4 @@ TEH_extensions_init (void);
void void
TEH_extensions_done (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 #endif

View File

@ -736,10 +736,6 @@ destroy_key_helpers (struct HelperState *hs)
* Looks up the AGE_RESTRICTED setting for a denomination in the config and * Looks up the AGE_RESTRICTED setting for a denomination in the config and
* returns the age restriction (mask) accordingly. * 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 * @param section_name Section in the configuration for the particular
* denomination. * denomination.
*/ */
@ -748,15 +744,13 @@ load_age_mask (const char*section_name)
{ {
static const struct TALER_AgeMask null_mask = {0}; static const struct TALER_AgeMask null_mask = {0};
struct TALER_AgeMask age_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 */ // Get the age mask from the extension, if configured
if (TALER_Extension_OK != TALER_get_age_mask (TEH_cfg, &age_mask)) if (NULL != age_ext->config)
{ {
GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, age_mask = *(struct TALER_AgeMask *) age_ext->config;
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;
} }
if (age_mask.mask == 0) if (age_mask.mask == 0)
@ -1450,7 +1444,6 @@ struct DenomKeyCtx
* valid denomination keys? * valid denomination keys?
*/ */
struct GNUNET_TIME_Relative min_dk_frequency; 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 signkeys list of sign keys to return
* @param recoup list of revoked keys to return * @param recoup list of revoked keys to return
* @param denoms list of denominations 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 * @return #GNUNET_OK on success
*/ */
static enum GNUNET_GenericReturnValue static enum GNUNET_GenericReturnValue
@ -1621,7 +1615,8 @@ create_krd (struct TEH_KeyStateHandle *ksh,
struct GNUNET_TIME_Timestamp last_cpd, struct GNUNET_TIME_Timestamp last_cpd,
json_t *signkeys, json_t *signkeys,
json_t *recoup, json_t *recoup,
json_t *denoms) json_t *denoms,
json_t *age_restricted_denoms)
{ {
struct KeysResponseData krd; struct KeysResponseData krd;
struct TALER_ExchangePublicKeyP exchange_pub; struct TALER_ExchangePublicKeyP exchange_pub;
@ -1693,6 +1688,8 @@ create_krd (struct TEH_KeyStateHandle *ksh,
GNUNET_JSON_pack_data_auto ("eddsa_sig", GNUNET_JSON_pack_data_auto ("eddsa_sig",
&exchange_sig)); &exchange_sig));
GNUNET_assert (NULL != keys); GNUNET_assert (NULL != keys);
// Set wallet limit if KYC is configured
if ( (TEH_KYC_NONE != TEH_kyc_config.mode) && if ( (TEH_KYC_NONE != TEH_kyc_config.mode) &&
(GNUNET_OK == (GNUNET_OK ==
TALER_amount_is_valid (&TEH_kyc_config.wallet_balance_limit)) ) 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))); &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; char *keys_json;
void *keys_jsonz; void *keys_jsonz;
@ -1772,7 +1803,8 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
{ {
json_t *recoup; json_t *recoup;
struct SignKeyCtx sctx; struct SignKeyCtx sctx;
json_t *denoms; json_t *denoms = NULL;
json_t *age_restricted_denoms = NULL;
struct GNUNET_TIME_Timestamp last_cpd; struct GNUNET_TIME_Timestamp last_cpd;
struct GNUNET_CONTAINER_Heap *heap; struct GNUNET_CONTAINER_Heap *heap;
struct GNUNET_HashContext *hash_context; struct GNUNET_HashContext *hash_context;
@ -1802,6 +1834,14 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
} }
denoms = json_array (); denoms = json_array ();
GNUNET_assert (NULL != denoms); 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; last_cpd = GNUNET_TIME_UNIT_ZERO_TS;
hash_context = GNUNET_CRYPTO_hash_context_start (); hash_context = GNUNET_CRYPTO_hash_context_start ();
{ {
@ -1826,7 +1866,8 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
last_cpd, last_cpd,
sctx.signkeys, sctx.signkeys,
recoup, recoup,
denoms)) denoms,
age_restricted_denoms))
{ {
GNUNET_log (GNUNET_ERROR_TYPE_WARNING, GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to generate key response data for %s\n", "Failed to generate key response data for %s\n",
@ -1837,6 +1878,8 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
/* intentionally empty */; /* intentionally empty */;
GNUNET_CONTAINER_heap_destroy (heap); GNUNET_CONTAINER_heap_destroy (heap);
json_decref (denoms); json_decref (denoms);
if (NULL != age_restricted_denoms)
json_decref (age_restricted_denoms);
json_decref (sctx.signkeys); json_decref (sctx.signkeys);
json_decref (recoup); json_decref (recoup);
return GNUNET_SYSERR; return GNUNET_SYSERR;
@ -1846,10 +1889,12 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
GNUNET_CRYPTO_hash_context_read (hash_context, GNUNET_CRYPTO_hash_context_read (hash_context,
&dk->h_denom_pub, &dk->h_denom_pub,
sizeof (struct GNUNET_HashCode)); sizeof (struct GNUNET_HashCode));
GNUNET_assert (
0 == {
json_array_append_new ( json_t *denom;
denoms, json_t *array;
denom =
GNUNET_JSON_PACK ( GNUNET_JSON_PACK (
GNUNET_JSON_pack_data_auto ("master_sig", GNUNET_JSON_pack_data_auto ("master_sig",
&dk->master_sig), &dk->master_sig),
@ -1872,7 +1917,26 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
TALER_JSON_pack_amount ("fee_refresh", TALER_JSON_pack_amount ("fee_refresh",
&dk->meta.fee_refresh), &dk->meta.fee_refresh),
TALER_JSON_pack_amount ("fee_refund", 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); GNUNET_CONTAINER_heap_destroy (heap);
@ -1888,12 +1952,15 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
last_cpd, last_cpd,
sctx.signkeys, sctx.signkeys,
recoup, recoup,
denoms)) denoms,
age_restricted_denoms))
{ {
GNUNET_log (GNUNET_ERROR_TYPE_WARNING, GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to generate key response data for %s\n", "Failed to generate key response data for %s\n",
GNUNET_TIME_timestamp2s (last_cpd)); GNUNET_TIME_timestamp2s (last_cpd));
json_decref (denoms); json_decref (denoms);
if (NULL != age_restricted_denoms)
json_decref (age_restricted_denoms);
json_decref (sctx.signkeys); json_decref (sctx.signkeys);
json_decref (recoup); json_decref (recoup);
return GNUNET_SYSERR; return GNUNET_SYSERR;
@ -1909,6 +1976,8 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
json_decref (sctx.signkeys); json_decref (sctx.signkeys);
json_decref (recoup); json_decref (recoup);
json_decref (denoms); json_decref (denoms);
if (NULL != age_restricted_denoms)
json_decref (age_restricted_denoms);
return GNUNET_OK; return GNUNET_OK;
} }

View File

@ -29,21 +29,17 @@
#include "taler-exchange-httpd_management.h" #include "taler-exchange-httpd_management.h"
#include "taler-exchange-httpd_responses.h" #include "taler-exchange-httpd_responses.h"
#include "taler_extensions.h" #include "taler_extensions.h"
#include "taler_dbevents.h"
/**
* Extension carries the necessary data for a particular extension.
*
*/
struct Extension struct Extension
{ {
enum TALER_Extension_Type type; enum TALER_Extension_Type type;
json_t *config_json; json_t *config;
// This union contains the parsed configuration for each extension.
union
{
// configuration for the age restriction
struct TALER_AgeMask mask;
/* TODO oec - peer2peer config */
};
}; };
/** /**
@ -56,6 +52,38 @@ struct SetExtensionsContext
struct TALER_MasterSignatureP *extensions_sigs; 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 * Function implementing database transaction to set the configuration of
* extensions. It runs the transaction logic. * extensions. It runs the transaction logic.
@ -77,9 +105,68 @@ set_extensions (void *cls,
struct MHD_Connection *connection, struct MHD_Connection *connection,
MHD_RESULT *mhd_ret) 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 */ 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}; struct SetExtensionsContext sec = {0};
json_t *extensions; json_t *extensions;
json_t *extensions_sigs; json_t *extensions_sigs;
struct GNUNET_JSON_Specification spec[] = { struct GNUNET_JSON_Specification top_spec[] = {
GNUNET_JSON_spec_json ("extensions", GNUNET_JSON_spec_json ("extensions",
&extensions), &extensions),
GNUNET_JSON_spec_json ("extensions_sigs", GNUNET_JSON_spec_json ("extensions_sigs",
&extensions_sigs), &extensions_sigs),
GNUNET_JSON_spec_end () GNUNET_JSON_spec_end ()
}; };
bool ok;
MHD_RESULT ret; MHD_RESULT ret;
// Parse the top level json structure
{ {
enum GNUNET_GenericReturnValue res; enum GNUNET_GenericReturnValue res;
res = TALER_MHD_parse_json_data (connection, res = TALER_MHD_parse_json_data (connection,
root, root,
spec); top_spec);
if (GNUNET_SYSERR == res) if (GNUNET_SYSERR == res)
return MHD_NO; /* hard failure */ return MHD_NO; /* hard failure */
if (GNUNET_NO == res) if (GNUNET_NO == res)
return MHD_YES; /* failure */ return MHD_YES; /* failure */
} }
// Ensure we have two arrays of the same size
if (! (json_is_array (extensions) && if (! (json_is_array (extensions) &&
json_is_array (extensions_sigs)) ) json_is_array (extensions_sigs)) )
{ {
GNUNET_break_op (0); GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec); GNUNET_JSON_parse_free (top_spec);
return TALER_MHD_reply_with_error ( return TALER_MHD_reply_with_error (
connection, connection,
MHD_HTTP_BAD_REQUEST, MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED, 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); sec.num_extensions = json_array_size (extensions_sigs);
if (json_array_size (extensions) != sec.num_extensions) if (json_array_size (extensions) != sec.num_extensions)
{ {
GNUNET_break_op (0); GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec); GNUNET_JSON_parse_free (top_spec);
return TALER_MHD_reply_with_error ( return TALER_MHD_reply_with_error (
connection, connection,
MHD_HTTP_BAD_REQUEST, MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED, 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, GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@ -145,116 +233,59 @@ TEH_handler_management_post_extensions (
struct Extension); struct Extension);
sec.extensions_sigs = GNUNET_new_array (sec.num_extensions, sec.extensions_sigs = GNUNET_new_array (sec.num_extensions,
struct TALER_MasterSignatureP); struct TALER_MasterSignatureP);
ok = true;
// Now parse individual extensions and signatures from those arrays.
for (unsigned int i = 0; i<sec.num_extensions; i++) for (unsigned int i = 0; i<sec.num_extensions; i++)
{ {
// 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 ()
};
// 1. parse the extension res = TALER_MHD_parse_json_array (connection,
extensions,
ext_spec,
i,
-1);
if (GNUNET_SYSERR == res)
{ {
enum GNUNET_GenericReturnValue res; ret = MHD_NO; /* hard failure */
const char *name; goto CLEANUP;
struct GNUNET_JSON_Specification ispec[] = { }
GNUNET_JSON_spec_string ("extension", if (GNUNET_NO == res)
&name), {
GNUNET_JSON_spec_json ("config", ret = MHD_YES;
&sec.extensions[i].config_json), goto CLEANUP;
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);
// 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. parse the signature /* 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;
}
sec.extensions[i].type = extension->type;
/* 3. Extract the signature out of the json array */
{ {
enum GNUNET_GenericReturnValue res; enum GNUNET_GenericReturnValue res;
struct GNUNET_JSON_Specification ispec[] = { struct GNUNET_JSON_Specification sig_spec[] = {
GNUNET_JSON_spec_fixed_auto (NULL, GNUNET_JSON_spec_fixed_auto (NULL,
&sec.extensions_sigs[i]), &sec.extensions_sigs[i]),
GNUNET_JSON_spec_end () GNUNET_JSON_spec_end ()
@ -262,81 +293,61 @@ TEH_handler_management_post_extensions (
res = TALER_MHD_parse_json_array (connection, res = TALER_MHD_parse_json_array (connection,
extensions_sigs, extensions_sigs,
ispec, sig_spec,
i, i,
-1); -1);
if (GNUNET_SYSERR == res) if (GNUNET_SYSERR == res)
{ {
ret = MHD_NO; /* hard failure */ ret = MHD_NO; /* hard failure */
ok = false; goto CLEANUP;
break;
} }
if (GNUNET_NO == res) if (GNUNET_NO == res)
{ {
ret = MHD_YES; ret = MHD_YES;
ok = false; goto CLEANUP;
break;
} }
} }
// 3. verify the signature /* 4. Verify the signature of the config */
{ if (GNUNET_OK != config_verify (
enum GNUNET_GenericReturnValue res; sec.extensions[i].config,
switch (sec.extensions[i].type)
{
case TALER_Extension_AgeRestriction:
res = TALER_exchange_offline_extension_agemask_verify (
sec.extensions[i].mask,
&TEH_master_public_key, &TEH_master_public_key,
&sec.extensions_sigs[i]); &sec.extensions_sigs[i]))
if (GNUNET_OK != res) {
{ ret = TALER_MHD_reply_with_error (
GNUNET_free (sec.extensions); connection,
GNUNET_free (sec.extensions_sigs); MHD_HTTP_BAD_REQUEST,
GNUNET_JSON_parse_free (spec); TALER_EC_GENERIC_PARAMETER_MALFORMED,
return TALER_MHD_reply_with_error ( "invalid signature for extension");
connection, goto CLEANUP;
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: /* 5. Make sure the config is sound */
if (! ok) if (GNUNET_OK != extension->test_config (sec.extensions[i].config))
{ {
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, GNUNET_JSON_parse_free (ext_spec);
"Failure to handle /management/extensions\n"); ret = TALER_MHD_reply_with_error (
GNUNET_free (sec.extensions); connection,
GNUNET_free (sec.extensions_sigs); MHD_HTTP_BAD_REQUEST,
GNUNET_JSON_parse_free (spec); TALER_EC_GENERIC_PARAMETER_MALFORMED,
return ret; "invalid configuration for extension");
} goto CLEANUP;
}
/* 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, GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Received %u extensions\n", "Received %u extensions\n",
sec.num_extensions); sec.num_extensions);
// now run the transaction to persist the configurations
{ {
enum GNUNET_GenericReturnValue res; enum GNUNET_GenericReturnValue res;
@ -347,19 +358,29 @@ BREAK:
&set_extensions, &set_extensions,
&sec); &sec);
GNUNET_free (sec.extensions);
GNUNET_free (sec.extensions_sigs);
GNUNET_JSON_parse_free (spec);
if (GNUNET_SYSERR == res) if (GNUNET_SYSERR == res)
return ret; goto CLEANUP;
} }
return TALER_MHD_reply_static ( ret = TALER_MHD_reply_static (
connection, connection,
MHD_HTTP_NO_CONTENT, MHD_HTTP_NO_CONTENT,
NULL, NULL,
NULL, NULL,
0); 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;
} }

View File

@ -1,18 +1,18 @@
/* /*
This file is part of TALER This file is part of TALER
Copyright (C) 2014--2021 Taler Systems SA Copyright (C) 2014--2021 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the 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 terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version. 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 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 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details. 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 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/> TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
/** /**
* @file plugin_exchangedb_postgres.c * @file plugin_exchangedb_postgres.c
@ -211,7 +211,7 @@ prepare_statements (struct PostgresClosure *pg)
enum GNUNET_GenericReturnValue ret; enum GNUNET_GenericReturnValue ret;
struct GNUNET_PQ_PreparedStatement ps[] = { struct GNUNET_PQ_PreparedStatement ps[] = {
/* Used in #postgres_insert_denomination_info() and /* Used in #postgres_insert_denomination_info() and
#postgres_add_denomination_key() */ #postgres_add_denomination_key() */
GNUNET_PQ_make_prepare ( GNUNET_PQ_make_prepare (
"denomination_insert", "denomination_insert",
"INSERT INTO denominations " "INSERT INTO denominations "
@ -222,8 +222,8 @@ prepare_statements (struct PostgresClosure *pg)
",expire_withdraw" ",expire_withdraw"
",expire_deposit" ",expire_deposit"
",expire_legal" ",expire_legal"
",coin_val" /* value of this denom */ ",coin_val" /* value of this denom */
",coin_frac" /* fractional value of this denom */ ",coin_frac" /* fractional value of this denom */
",fee_withdraw_val" ",fee_withdraw_val"
",fee_withdraw_frac" ",fee_withdraw_frac"
",fee_deposit_val" ",fee_deposit_val"
@ -245,8 +245,8 @@ prepare_statements (struct PostgresClosure *pg)
",expire_withdraw" ",expire_withdraw"
",expire_deposit" ",expire_deposit"
",expire_legal" ",expire_legal"
",coin_val" /* value of this denom */ ",coin_val" /* value of this denom */
",coin_frac" /* fractional value of this denom */ ",coin_frac" /* fractional value of this denom */
",fee_withdraw_val" ",fee_withdraw_val"
",fee_withdraw_frac" ",fee_withdraw_frac"
",fee_deposit_val" ",fee_deposit_val"
@ -268,8 +268,8 @@ prepare_statements (struct PostgresClosure *pg)
",expire_withdraw" ",expire_withdraw"
",expire_deposit" ",expire_deposit"
",expire_legal" ",expire_legal"
",coin_val" /* value of this denom */ ",coin_val" /* value of this denom */
",coin_frac" /* fractional value of this denom */ ",coin_frac" /* fractional value of this denom */
",fee_withdraw_val" ",fee_withdraw_val"
",fee_withdraw_frac" ",fee_withdraw_frac"
",fee_deposit_val" ",fee_deposit_val"
@ -332,8 +332,8 @@ prepare_statements (struct PostgresClosure *pg)
",expire_withdraw" ",expire_withdraw"
",expire_deposit" ",expire_deposit"
",expire_legal" ",expire_legal"
",coin_val" /* value of this denom */ ",coin_val" /* value of this denom */
",coin_frac" /* fractional value of this denom */ ",coin_frac" /* fractional value of this denom */
",fee_withdraw_val" ",fee_withdraw_val"
",fee_withdraw_frac" ",fee_withdraw_frac"
",fee_deposit_val" ",fee_deposit_val"
@ -766,7 +766,7 @@ prepare_statements (struct PostgresClosure *pg)
See also: See also:
https://stackoverflow.com/questions/34708509/how-to-use-returning-with-on-conflict-in-postgresql/37543015#37543015 https://stackoverflow.com/questions/34708509/how-to-use-returning-with-on-conflict-in-postgresql/37543015#37543015
*/ */
GNUNET_PQ_make_prepare ( GNUNET_PQ_make_prepare (
"insert_known_coin", "insert_known_coin",
"WITH dd" "WITH dd"
@ -2743,6 +2743,23 @@ prepare_statements (struct PostgresClosure *pg)
" AND start_row=$2" " AND start_row=$2"
" AND end_row=$3", " AND end_row=$3",
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 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 * 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 * and non-revoked) that has been signed by the master key. Runs in its own
* read-only transaction. * read-only transaction.
* *
* *
* @param cls the @e cls of this struct with the plugin-specific state * @param cls the @e cls of this struct with the plugin-specific state
* @param cb function to call on each denomination key * @param cb function to call on each denomination key
* @param cb_cls closure for @a cb * @param cb_cls closure for @a cb
@ -3513,7 +3530,7 @@ postgres_iterate_active_signkeys (void *cls,
void *cb_cls) void *cb_cls)
{ {
struct PostgresClosure *pg = cls; struct PostgresClosure *pg = cls;
struct GNUNET_TIME_Absolute now; struct GNUNET_TIME_Absolute now = {0};
struct GNUNET_PQ_QueryParam params[] = { struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_absolute_time (&now), GNUNET_PQ_query_param_absolute_time (&now),
GNUNET_PQ_query_param_end 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 * Function called to invoke @a cb on every active auditor. Disabled
* auditors are skipped. Runs in its own read-only transaction. * auditors are skipped. Runs in its own read-only transaction.
* *
* @param cls the @e cls of this struct with the plugin-specific state * @param cls the @e cls of this struct with the plugin-specific state
* @param cb function to call on each active auditor * @param cb function to call on each active auditor
* @param cb_cls closure for @a cb * @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 * Perform deposit operation, checking for sufficient balance
* of the coin and possibly persisting the deposit details. * 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 cls the `struct PostgresClosure` with the plugin-specific state
* @param deposit deposit operation details * @param deposit deposit operation details
* @param known_coin_id row of the coin in the known_coins table * @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 = append_rh (rhc);
tail->type = TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE; tail->type = TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE;
tail->details.bank = bt; 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 = append_rh (rhc);
tail->type = TALER_EXCHANGEDB_RO_RECOUP_COIN; tail->type = TALER_EXCHANGEDB_RO_RECOUP_COIN;
tail->details.recoup = recoup; 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 = append_rh (rhc);
tail->type = TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK; tail->type = TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK;
tail->details.closing = closing; 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) void *deposit_cb_cls)
{ {
struct PostgresClosure *pg = cls; struct PostgresClosure *pg = cls;
struct GNUNET_TIME_Absolute now; struct GNUNET_TIME_Absolute now = {0};
struct GNUNET_PQ_QueryParam params[] = { struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_absolute_time (&now), GNUNET_PQ_query_param_absolute_time (&now),
GNUNET_PQ_query_param_uint64 (&start_shard_row), 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: case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
goto cleanup; goto cleanup;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
default: /* can have more than one result */ default: /* can have more than one result */
break; break;
} }
switch (grctx.qs) switch (grctx.qs)
@ -6269,7 +6288,7 @@ postgres_get_refresh_reveal (void *cls,
case GNUNET_DB_STATUS_SOFT_ERROR: case GNUNET_DB_STATUS_SOFT_ERROR:
goto cleanup; goto cleanup;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 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; break;
} }
@ -11394,6 +11413,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. * Initialize Postgres database subsystem.
* *
@ -11628,6 +11709,10 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
= &postgres_release_revolving_shard; = &postgres_release_revolving_shard;
plugin->delete_shard_locks plugin->delete_shard_locks
= &postgres_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; return plugin;
} }

View File

@ -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 GNUNET_NETWORK_STRUCT_END
@ -2521,30 +2534,31 @@ TALER_merchant_wire_signature_make (
/* **************** /management/extensions offline signing **************** */ /* **************** /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 master_priv private key to sign with
* @param[out] master_sig where to write the signature * @param[out] master_sig where to write the signature
*/ */
void void
TALER_exchange_offline_extension_agemask_sign ( TALER_exchange_offline_extension_config_hash_sign (
const struct TALER_AgeMask mask, const struct TALER_ExtensionConfigHash h_config,
const struct TALER_MasterPrivateKeyP *master_priv, const struct TALER_MasterPrivateKeyP *master_priv,
struct TALER_MasterSignatureP *master_sig); 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_pub master public key of the exchange
* @param master_sig signature of the exchange * @param master_sig signature of the exchange
* @return #GNUNET_OK if signature is valid * @return #GNUNET_OK if signature is valid
*/ */
enum GNUNET_GenericReturnValue enum GNUNET_GenericReturnValue
TALER_exchange_offline_extension_agemask_verify ( TALER_exchange_offline_extension_config_hash_verify (
const struct TALER_AgeMask mask, const struct TALER_ExtensionConfigHash h_config,
const struct TALER_MasterPublicKeyP *master_pub, const struct TALER_MasterPublicKeyP *master_pub,
const struct TALER_MasterSignatureP *master_sig const struct TALER_MasterSignatureP *master_sig
); );

View File

@ -4025,8 +4025,35 @@ struct TALER_EXCHANGEDB_Plugin
(*delete_shard_locks)(void *cls); (*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);
}; };

View File

@ -23,6 +23,7 @@
#include <gnunet/gnunet_util_lib.h> #include <gnunet/gnunet_util_lib.h>
#include "taler_crypto_lib.h" #include "taler_crypto_lib.h"
#include "taler_json_lib.h"
#define TALER_EXTENSION_SECTION_PREFIX "exchange-extension-" #define TALER_EXTENSION_SECTION_PREFIX "exchange-extension-"
@ -39,22 +40,42 @@ enum TALER_Extension_Type
{ {
TALER_Extension_AgeRestriction = 0, TALER_Extension_AgeRestriction = 0,
TALER_Extension_Peer2Peer = 1, TALER_Extension_Peer2Peer = 1,
TALER_Extension_Max = 2 TALER_Extension_Max = 2 // Must be last
}; };
/*
* TODO oec: documentation
*/
struct TALER_Extension struct TALER_Extension
{ {
enum TALER_Extension_Type type; enum TALER_Extension_Type type;
char *name; char *name;
bool critical; bool critical;
void *config; 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 * Generic functions for extensions
* FIXME oec
*/ */
/**
* 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 * TALER Age Restriction Extension
@ -72,7 +93,19 @@ struct TALER_Extension
<< 21) << 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. * @param[out] mask Mask representation for age restriction.
* @return Error, if age groups were invalid, OK otherwise. * @return Error, if age groups were invalid, OK otherwise.
*/ */
@ -81,6 +114,19 @@ TALER_parse_age_group_string (char *groups,
struct TALER_AgeMask *mask); 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 cfg
* @param[out] mask for age restriction, will be set to 0 if age restriction is disabled. * @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 enum TALER_Extension_ReturnValue
TALER_get_age_mask (const struct GNUNET_CONFIGURATION_Handle *cfg, TALER_get_age_mask (const struct GNUNET_CONFIGURATION_Handle *cfg,
struct TALER_AgeMask *mask); struct TALER_AgeMask *mask);
/*
* TALER Peer2Peer Extension
* TODO oec
*/
#endif #endif

View File

@ -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 extensions contract extensions to hash
* @param[out] ech where to write the extension hash * @param[out] ech where to write the extension hash
@ -541,6 +541,16 @@ void
TALER_deposit_extension_hash (const json_t *extensions, TALER_deposit_extension_hash (const json_t *extensions,
struct TALER_ExtensionContractHash *ech); 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> }. * Parses a JSON object { "extension": "age_restriction", "mask": <uint32> }.
@ -553,7 +563,6 @@ enum GNUNET_GenericReturnValue
TALER_agemask_parse_json (const json_t *root, TALER_agemask_parse_json (const json_t *root,
struct TALER_AgeMask *mask); struct TALER_AgeMask *mask);
#endif /* TALER_JSON_LIB_H_ */ #endif /* TALER_JSON_LIB_H_ */
/* End of taler_json_lib.h */ /* End of taler_json_lib.h */

View File

@ -967,9 +967,9 @@ struct TALER_MasterDelWirePS
/* /*
* @brief Signature made by the exchange offline key over the * @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 * Purpose is #TALER_SIGNATURE_MASTER_EXTENSION. Signed
@ -978,29 +978,11 @@ struct TALER_MasterExtensionAgeRestrictionPS
struct GNUNET_CRYPTO_EccSignaturePurpose purpose; struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
/** /**
* Bit mask representing the lits of age groups, see TALER_AgeMask for a * Hash of the JSON object that represents the configuration of an extension.
* description.
*/ */
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 * @brief Information about a denomination key. Denomination keys
* are used to sign coins of a certain value into existence. * are used to sign coins of a certain value into existence.

View File

@ -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 */ /* End of json/json.c */

View File

@ -153,32 +153,17 @@ TALER_EXCHANGE_management_post_extensions (
GNUNET_assert (NULL != extensions); GNUNET_assert (NULL != extensions);
for (unsigned int i = 0; i<pkd->num_extensions; i++) for (unsigned int i = 0; i<pkd->num_extensions; i++)
{ {
json_t *config; const json_t *config;
const struct TALER_AgeMask *mask;
const struct TALER_Extension *ext = &pkd->extensions[i]; const struct TALER_Extension *ext = &pkd->extensions[i];
switch (ext->type) config = ext->config_to_json (ext);
{
// 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");
}
GNUNET_assert (NULL != config);
GNUNET_assert (0 == GNUNET_assert (0 ==
json_array_append_new ( json_array_append_new (
extensions, extensions,
GNUNET_JSON_PACK ( GNUNET_JSON_PACK (
GNUNET_JSON_pack_data_auto ("name", GNUNET_JSON_pack_data_auto ("extension",
&ext->name), &ext->name),
GNUNET_JSON_pack_data_auto ("config", GNUNET_JSON_pack_data_auto ("config",
config) config)

View File

@ -72,6 +72,7 @@ libtalerutil_la_SOURCES = \
crypto_wire.c \ crypto_wire.c \
denom.c \ denom.c \
exchange_signatures.c \ exchange_signatures.c \
extensions.c \
extension_age_restriction.c \ extension_age_restriction.c \
getopt.c \ getopt.c \
lang.c \ lang.c \

View File

@ -23,7 +23,6 @@
#include "taler_extensions.h" #include "taler_extensions.h"
#include "stdint.h" #include "stdint.h"
/** /**
* *
* @param cfg Handle to the GNUNET configuration * @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 * @param mask Age mask
* @return String representation of the age mask, allocated by GNUNET_malloc. * @return String representation of the age mask, allocated by GNUNET_malloc.
* Can be used as value in the TALER config. * Can be used as value in the TALER config.
*/ */
char * 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; uint32_t mask = m->mask;
unsigned int n = 0; unsigned int n = 0;

49
src/util/extensions.c Normal file
View File

@ -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 */

View File

@ -491,66 +491,40 @@ TALER_exchange_offline_wire_fee_verify (
void void
TALER_exchange_offline_extension_agemask_sign ( TALER_exchange_offline_extension_config_hash_sign (
const struct TALER_AgeMask mask, const struct TALER_ExtensionConfigHash h_config,
const struct TALER_MasterPrivateKeyP *master_priv, const struct TALER_MasterPrivateKeyP *master_priv,
struct TALER_MasterSignatureP *master_sig) struct TALER_MasterSignatureP *master_sig)
{ {
struct TALER_MasterExtensionAgeRestrictionPS ar = { struct TALER_MasterExtensionConfigurationPS ec = {
.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_EXTENSION), .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_EXTENSION),
.purpose.size = htonl (sizeof(ar)), .purpose.size = htonl (sizeof(ec)),
.mask = mask .h_config = h_config
}; };
GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv, GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
&ar, &ec,
&master_sig->eddsa_signature); &master_sig->eddsa_signature);
} }
enum GNUNET_GenericReturnValue enum GNUNET_GenericReturnValue
TALER_exchange_offline_extension_agemask_verify ( TALER_exchange_offline_extension_config_hash_verify (
const struct TALER_AgeMask mask, const struct TALER_ExtensionConfigHash h_config,
const struct TALER_MasterPublicKeyP *master_pub, const struct TALER_MasterPublicKeyP *master_pub,
const struct TALER_MasterSignatureP *master_sig const struct TALER_MasterSignatureP *master_sig
) )
{ {
struct TALER_MasterExtensionAgeRestrictionPS ar = { struct TALER_MasterExtensionConfigurationPS ec = {
.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_EXTENSION), .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_EXTENSION),
.purpose.size = htonl (sizeof(ar)), .purpose.size = htonl (sizeof(ec)),
.mask = mask .h_config = h_config
}; };
return
GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_EXTENSION, return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_EXTENSION,
&ar, &ec,
&master_sig->eddsa_signature, &master_sig->eddsa_signature,
&master_pub->eddsa_pub); &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;
}
#endif
/* end of offline_signatures.c */ /* end of offline_signatures.c */