progress re: extension config upload

- taler-exchange-offline tool
  - now parses extensions
  - creates signed configuaration for upload
- echanged-handler for /management/extensions adjusted to new
  datastructures
This commit is contained in:
Özgür Kesim 2022-01-20 18:47:32 +01:00
parent 601c18caba
commit 6628f6a7af
Signed by: oec
GPG Key ID: 3D76A56D79EDD9D7
4 changed files with 313 additions and 287 deletions

View File

@ -95,6 +95,11 @@
*/
#define OP_SETUP "exchange-setup-0"
/**
* sign the enabled and configured extensions.
*/
#define OP_EXTENSIONS "exchange-extensions-0"
/**
* Our private key, initialized in #load_offline_key().
@ -1663,6 +1668,24 @@ upload_keys (const char *exchange_url,
}
/**
* Upload extension configuration
*
* @param exchange_url base URL of the exchange
* @param idx index of the operation we are performing (for logging)
* @param value arguments for POSTing configurations of extensions
*/
static void
upload_extensions (const char *exchange_url,
size_t idx,
const json_t *value)
{
GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, "uploading extensions %s\n",
json_dumps (value, JSON_INDENT (2)));
/* TODO */
}
/**
* Perform uploads based on the JSON in #out.
*
@ -1704,6 +1727,10 @@ trigger_upload (const char *exchange_url)
.key = OP_UPLOAD_SIGS,
.cb = &upload_keys
},
{
.key = OP_EXTENSIONS,
.cb = &upload_extensions
},
/* array termination */
{
.key = NULL
@ -3316,9 +3343,14 @@ do_setup (char *const *args)
}
/**
* struct extension carries the information about an extension together with
* callbacks to parse the configuration and marshal it as JSON
*/
struct extension
{
char *name;
bool enabled;
bool critical;
char *version;
void *config;
@ -3328,54 +3360,85 @@ struct extension
json_t *(*config_json)(const struct extension *this);
};
#define EXT_PREFIX "exchange-extension-"
#define DEFAULT_AGE_GROUPS "8:10:12:14:16:18:21"
static enum GNUNET_GenericReturnValue
age_restriction_parse_config (struct extension *this, const char *section)
{
char *age_groups;
struct TALER_AgeMask *mask = GNUNET_malloc (sizeof(struct TALER_AgeMask));
char *age_groups = NULL;
struct TALER_AgeMask mask = {0};
enum GNUNET_GenericReturnValue ret;
ret = GNUNET_CONFIGURATION_get_value_yesno (kcfg, section, "ENABLED");
this->enabled = (GNUNET_YES == ret);
if (! this->enabled)
return GNUNET_OK;
if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (kcfg,
section,
"AGE_GROUPS",
&age_groups))
goto ERROR;
age_groups = DEFAULT_AGE_GROUPS;
if (GNUNET_OK != TALER_parse_age_group_string (age_groups, mask))
goto ERROR;
if (GNUNET_OK != TALER_parse_age_group_string (age_groups, &mask))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
section,
"AGE_GROUPS");
test_shutdown ();
global_ret = EXIT_NOTCONFIGURED;
return GNUNET_SYSERR;
}
this->config = mask;
/* Don't look here. We just store the mask in/as the pointer .*/
this->config = (void *) (size_t) mask.mask;
return GNUNET_OK;
ERROR:
GNUNET_free (mask);
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
section,
"AGE_GROUPS");
test_shutdown ();
global_ret = EXIT_NOTCONFIGURED;
return GNUNET_SYSERR;
}
static json_t *
age_restriction_json (const struct extension *this)
{
/* Don't look here. We just restore the mask from/as the pointer .*/
struct TALER_AgeMask mask = { .mask = (uint32_t) (size_t) this->config };
char *groups = TALER_age_mask_to_string (&mask);
char *groups = TALER_age_mask_to_string (this->config);
return GNUNET_JSON_PACK (
GNUNET_JSON_pack_bool ("critical",
this->critical),
GNUNET_JSON_pack_string ("version",
this->version),
GNUNET_JSON_pack_string ("age_groups", groups)
);
json_t *obj = GNUNET_JSON_PACK (GNUNET_JSON_pack_bool ("enabled",
this->enabled));
if (this->enabled)
{
int ret = json_object_update (obj, GNUNET_JSON_PACK (
GNUNET_JSON_pack_bool ("enabled",
this->enabled),
GNUNET_JSON_pack_bool ("critical",
this->critical),
GNUNET_JSON_pack_string ("version",
this->version),
GNUNET_JSON_pack_string ("age_groups",
groups)
));
if (0>ret)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"couldn't update json object for age_restriction");
test_shutdown ();
global_ret = EXIT_FAILURE;
return NULL;
}
}
return obj;
}
static struct extension extensions[] = {
{
.name = "age-restriction",
.name = "age_restriction",
.version = "1",
.config = 0,
.parse_config = &age_restriction_parse_config,
.config_json = &age_restriction_json,
},
@ -3387,9 +3450,13 @@ static struct extension extensions[] = {
static const struct extension*
get_extension (const char *extension)
{
for (const struct extension *known = extensions; NULL != known->name; known++)
for (const struct extension *known = extensions;
NULL != known->name;
known++)
{
if (0 == strncasecmp (extension, known->name, strlen (known->name)))
if (0 == strncasecmp (extension,
known->name,
strlen (known->name)))
return known;
}
return NULL;
@ -3397,100 +3464,47 @@ get_extension (const char *extension)
static void
show_extensions (void *cls, const char *section)
collect_extensions (void *cls, const char *section)
{
json_t *obj = (json_t *) cls;
const char *name;
const struct extension *extension;
if (0 != global_ret)
return;
if (0 != strncasecmp (section,
EXT_PREFIX,
sizeof(EXT_PREFIX) - 1))
{
return;
}
if (0 == strncasecmp (section,
"exchange-extension-",
sizeof("exchange-extension-") - 1))
{
const char *name = section + sizeof("exchange-extension-") - 1;
const struct extension *extension = get_extension (name);
enum GNUNET_GenericReturnValue ret;
if (NULL == extension)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unsupported extension `%s` (section [%s]).\n", name,
section);
test_shutdown ();
global_ret = EXIT_INVALIDARGUMENT;
return;
}
GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, "found extension %s\n", name);
{
bool enabled = (GNUNET_YES ==
GNUNET_CONFIGURATION_get_value_yesno (
kcfg, section, "ENABLED"));
GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
"\textension is %s\n",
enabled?"ENABLED":"DISABLED");
if (! enabled)
return;
ret = extension->parse_config ((struct extension *) extension, section);
if (GNUNET_OK != ret)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Couldn't parse configuration for extension `%s` (section [%s]).\n",
name,
section);
test_shutdown ();
global_ret = EXIT_INVALIDARGUMENT;
return;
}
json_t *cfg = extension->config_json (extension);
GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
"\tconfig: %s\n",
json_dumps (cfg, JSON_INDENT (2)));
return;
}
}
}
static void
sign_extensions (void *cls, const char *section)
{
if (0 != global_ret)
name = section + sizeof(EXT_PREFIX) - 1;
if (NULL == (extension = get_extension (name)))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unsupported extension `%s` (section [%s]).\n", name,
section);
test_shutdown ();
global_ret = EXIT_NOTCONFIGURED;
return;
}
if (0 == strncasecmp (section,
"exchange-extension-",
sizeof("exchange-extension-") - 1))
if (GNUNET_OK != extension->parse_config ((struct extension *) extension,
section))
{
const char *name = section + sizeof("exchange-extension-") - 1;
const struct extension *extension = get_extension (name);
if (NULL == extension)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unsupported extension `%s` (section [%s]).\n", name,
section);
test_shutdown ();
global_ret = EXIT_INVALIDARGUMENT;
return;
}
if (GNUNET_YES ==
GNUNET_CONFIGURATION_get_value_yesno (
kcfg, section, "ENABLED"))
{
// TODO
GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, "TODO signing extension %s\n",
name);
}
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Couldn't parse configuration for extension `%s` (section [%s]).\n",
name,
section);
test_shutdown ();
global_ret = EXIT_NOTCONFIGURED;
return;
}
json_object_set (obj, name, extension->config_json (extension));
}
@ -3501,18 +3515,102 @@ static void
do_extensions_show (char *const *args)
{
json_t *obj = json_object ();
json_t *exts = json_object ();
GNUNET_CONFIGURATION_iterate_sections (kcfg,
&show_extensions,
NULL);
&collect_extensions,
exts);
json_object_set (obj, "extensions", exts);
GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, "%s\n",
json_dumps (obj, JSON_INDENT (2)));
json_decref (obj);
}
/*
* Sign the configurations of the enabled extensions
*/
static void
do_extensions_sign (char *const *args)
{
json_t *obj = json_object ();
json_t *exts = json_object ();
json_t *sigs = json_object ();
GNUNET_CONFIGURATION_iterate_sections (kcfg,
&collect_extensions,
exts);
json_object_set (obj, "extensions", exts);
/* sign the extensions */
{
const char *name;
json_t *config;
json_object_foreach (exts, name, config){
struct TALER_ExtensionConfigHash h_config;
struct TALER_MasterSignatureP sig;
if (GNUNET_OK != TALER_extension_config_hash (config, &h_config))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"error while hashing config for extension %s\n",
name);
return;
}
TALER_exchange_offline_extension_config_hash_sign (h_config, &master_priv,
&sig);
json_object_update (sigs,
GNUNET_JSON_PACK (
GNUNET_JSON_pack_data_auto (
name,
&sig)));
}
}
json_object_set (obj, "extensions_sigs", sigs);
output_operation (OP_EXTENSIONS, obj);
}
static void
do_extensions_sign (char *const *args)
cmd_handler (char *const *args, const struct SubCommand *cmds)
{
GNUNET_CONFIGURATION_iterate_sections (kcfg,
&sign_extensions,
NULL);
nxt = NULL;
for (unsigned int i = 0; NULL != cmds[i].name; i++)
{
if (0 == strcasecmp (cmds[i].name,
args[0]))
{
cmds[i].cb (&args[1]);
return;
}
}
if (0 != strcasecmp ("help",
args[0]))
{
GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
"Unexpected command `%s'\n",
args[0]);
global_ret = EXIT_INVALIDARGUMENT;
}
GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
"Supported subcommands:\n");
for (unsigned int i = 0; NULL != cmds[i].name; i++)
{
GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
"- %s: %s\n",
cmds[i].name,
cmds[i].help);
}
}
@ -3536,6 +3634,7 @@ do_work_extensions (char *const *args)
.name = NULL,
}
};
if (NULL == args[0])
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
@ -3544,34 +3643,9 @@ do_work_extensions (char *const *args)
global_ret = EXIT_INVALIDARGUMENT;
return;
}
nxt = NULL;
for (unsigned int i = 0; NULL != cmds[i].name; i++)
{
if (0 == strcasecmp (cmds[i].name,
args[0]))
{
cmds[i].cb (&args[1]);
return;
}
}
if (0 != strcasecmp ("help",
args[0]))
{
GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
"Unexpected command `%s'\n",
args[0]);
global_ret = EXIT_INVALIDARGUMENT;
}
GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
"Supported subcommands:\n");
for (unsigned int i = 0; NULL != cmds[i].name; i++)
{
GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
"- %s: %s\n",
cmds[i].name,
cmds[i].help);
}
cmd_handler (args, cmds);
next (args + 1);
}
@ -3663,34 +3737,7 @@ work (void *cls)
};
(void) cls;
nxt = NULL;
for (unsigned int i = 0; NULL != cmds[i].name; i++)
{
if (0 == strcasecmp (cmds[i].name,
args[0]))
{
cmds[i].cb (&args[1]);
return;
}
}
if (0 != strcasecmp ("help",
args[0]))
{
GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
"Unexpected command `%s'\n",
args[0]);
global_ret = EXIT_INVALIDARGUMENT;
}
GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
"Supported subcommands:\n");
for (unsigned int i = 0; NULL != cmds[i].name; i++)
{
GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
"- %s: %s\n",
cmds[i].name,
cmds[i].help);
}
cmd_handler (args, cmds);
}

View File

@ -176,7 +176,7 @@ TEH_handler_management_post_extensions (
struct MHD_Connection *connection,
const json_t *root)
{
struct SetExtensionsContext sec = {0};
MHD_RESULT ret;
json_t *extensions;
json_t *extensions_sigs;
struct GNUNET_JSON_Specification top_spec[] = {
@ -186,9 +186,9 @@ TEH_handler_management_post_extensions (
&extensions_sigs),
GNUNET_JSON_spec_end ()
};
MHD_RESULT ret;
struct SetExtensionsContext sec = {0};
// Parse the top level json structure
/* Parse the top level json structure */
{
enum GNUNET_GenericReturnValue res;
@ -201,9 +201,9 @@ TEH_handler_management_post_extensions (
return MHD_YES; /* failure */
}
// Ensure we have two arrays of the same size
if (! (json_is_array (extensions) &&
json_is_array (extensions_sigs)) )
/* Ensure we have two objects of the same size */
if (! (json_is_object (extensions) &&
json_is_object (extensions_sigs)) )
{
GNUNET_break_op (0);
GNUNET_JSON_parse_free (top_spec);
@ -211,11 +211,11 @@ TEH_handler_management_post_extensions (
connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"array expected for extensions and extensions_sigs");
"objects expected for extensions and extensions_sigs");
}
sec.num_extensions = json_array_size (extensions_sigs);
if (json_array_size (extensions) != sec.num_extensions)
sec.num_extensions = json_object_size (extensions);
if (json_object_size (extensions_sigs) != sec.num_extensions)
{
GNUNET_break_op (0);
GNUNET_JSON_parse_free (top_spec);
@ -223,7 +223,7 @@ TEH_handler_management_post_extensions (
connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"arrays extensions and extensions_sigs are not of the same size");
"objects extensions and extensions_sigs are not of the same size");
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@ -234,120 +234,98 @@ TEH_handler_management_post_extensions (
sec.extensions_sigs = GNUNET_new_array (sec.num_extensions,
struct TALER_MasterSignatureP);
// Now parse individual extensions and signatures from those arrays.
for (unsigned int i = 0; i<sec.num_extensions; i++)
/* Now parse individual extensions and signatures from those objects. */
{
// 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 ()
};
json_t *config;
int idx = 0;
res = TALER_MHD_parse_json_array (connection,
extensions,
ext_spec,
i,
-1);
if (GNUNET_SYSERR == res)
{
ret = MHD_NO; /* hard failure */
goto CLEANUP;
}
if (GNUNET_NO == res)
{
ret = MHD_YES;
goto CLEANUP;
}
json_object_foreach (extensions, name, config){
/* 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;
struct GNUNET_JSON_Specification sig_spec[] = {
GNUNET_JSON_spec_fixed_auto (NULL,
&sec.extensions_sigs[i]),
GNUNET_JSON_spec_end ()
};
res = TALER_MHD_parse_json_array (connection,
extensions_sigs,
sig_spec,
i,
-1);
if (GNUNET_SYSERR == res)
/* 1. Make sure name refers to a supported extension */
if (GNUNET_OK != TALER_extension_get_by_name (name,
(const struct
TALER_Extension **)
TEH_extensions,
&extension))
{
ret = MHD_NO; /* hard failure */
ret = TALER_MHD_reply_with_error (
connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"invalid extension type");
goto CLEANUP;
}
if (GNUNET_NO == res)
sec.extensions[idx].config = config;
sec.extensions[idx].type = extension->type;
/* 2. Extract the corresponding signature */
{
ret = MHD_YES;
struct GNUNET_JSON_Specification sig_spec[] = {
GNUNET_JSON_spec_fixed_auto (name,
&sec.extensions_sigs[idx]),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK != TALER_MHD_parse_json_data (connection,
extensions_sigs,
sig_spec))
{
GNUNET_JSON_parse_free (sig_spec);
ret = TALER_MHD_reply_with_error (
connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"extension signature missing");
goto CLEANUP;
}
GNUNET_JSON_parse_free (sig_spec);
}
/* 3. Verify the signature of the config */
if (GNUNET_OK != config_verify (
sec.extensions[idx].config,
&TEH_master_public_key,
&sec.extensions_sigs[idx]))
{
ret = TALER_MHD_reply_with_error (
connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"invalid signature for extension");
goto CLEANUP;
}
}
/* 4. Verify the signature of the config */
if (GNUNET_OK != config_verify (
sec.extensions[i].config,
&TEH_master_public_key,
&sec.extensions_sigs[i]))
{
ret = TALER_MHD_reply_with_error (
connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"invalid signature for extension");
goto CLEANUP;
}
/* 4. Make sure the config is sound */
if (GNUNET_OK != extension->test_config (sec.extensions[idx].config))
{
ret = TALER_MHD_reply_with_error (
connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"invalid configuration 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;
}
}
/* We have a validly signed JSON object for the extension. Increment its
* refcount.
*/
json_incref (sec.extensions[idx].config);
idx++;
/* 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);
} /* json_object_foreach */
}
} /* for-loop */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Received %u extensions\n",
sec.num_extensions);
// now run the transaction to persist the configurations
/* now run the transaction to persist the configurations */
{
enum GNUNET_GenericReturnValue res;

View File

@ -78,9 +78,10 @@ WIRE_GATEWAY_URL = "http://localhost:9081/2/"
HTTP_PORT = 9081
# Enabled extensions
[exchange-extension-age-restriction]
[exchange-extension-age_restriction]
ENABLED = YES
AGE_GROUPS = "8:10:12:14:16:18:21"
# default age groups:
#AGE_GROUPS = "8:10:12:14:16:18:21"
# Sections starting with "coin_" specify which denominations
# the exchange should support (and their respective fee structure)

View File

@ -84,14 +84,14 @@ TALER_parse_age_group_string (const char *groups,
struct TALER_AgeMask *mask)
{
const char *end = groups + strlen (groups);
const char *pos = groups;
unsigned int prev = 0;
unsigned int val = 0;
char c;
while (pos < end)
while (*pos)
{
char c = *pos++;
c = *pos++;
if (':' == c)
{
if (prev >= val)
@ -100,23 +100,23 @@ TALER_parse_age_group_string (const char *groups,
mask->mask |= 1 << val;
prev = val;
val = 0;
continue;
}
else
{
if ('0'>c || '9'<c)
return GNUNET_SYSERR;
val = 10 * val + c - '0';
if ('0'>c || '9'<c)
return GNUNET_SYSERR;
if (0>=val || 32<=val)
return GNUNET_SYSERR;
}
val = 10 * val + c - '0';
if (0>=val || 32<=val)
return GNUNET_SYSERR;
}
if (0>=val || 32<=val || prev>=val)
if (0>val || 32<=val || prev>=val)
return GNUNET_SYSERR;
mask->mask |= 1 << val;
mask->mask |= (1 << val);
mask->mask |= 1; // mark zeroth group, too
return GNUNET_OK;
}