Towards a complete test with age restriction

- substantial amount of fixes in various parts
- slight refactoring of extensions
- fixes of post handler for /management/extensions
- fixes for offline tool extensions signing

State:
- compiles, runs and tests succeed when age restriction is not enabled
- compiles, runs and tests fail, when age restriction is enabled
This commit is contained in:
Özgür Kesim 2022-02-06 19:57:29 +01:00
parent f48ba6f043
commit 932dcde25c
Signed by: oec
GPG Key ID: 3D76A56D79EDD9D7
16 changed files with 382 additions and 131 deletions

View File

@ -1924,6 +1924,7 @@ trigger_upload (const char *exchange_url)
if (0 == strcasecmp (key,
uhs[i].key))
{
found = true;
uhs[i].cb (exchange_url,
index,

View File

@ -736,6 +736,12 @@ handle_post_management (struct TEH_RequestContext *rc,
return TEH_handler_management_post_wire_fees (rc->connection,
root);
}
if (0 == strcmp (args[0],
"extensions"))
{
return TEH_handler_management_post_extensions (rc->connection,
root);
}
GNUNET_break_op (0);
return r404 (rc->connection,
"/management/*");

View File

@ -1755,7 +1755,7 @@ create_krd (struct TEH_KeyStateHandle *ksh,
json_t *sig;
int r;
r = json_object_set_new (
r = json_object_set (
keys,
"extensions",
extensions);
@ -1778,7 +1778,7 @@ create_krd (struct TEH_KeyStateHandle *ksh,
{
GNUNET_assert (
0 ==
json_object_set_new (
json_object_set (
keys,
"age_restricted_denoms",
age_restricted_denoms));
@ -1858,6 +1858,8 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
struct GNUNET_TIME_Timestamp last_cpd;
struct GNUNET_CONTAINER_Heap *heap;
struct GNUNET_HashContext *hash_context;
bool age_restriction_active =
TALER_extensions_is_enabled_type (TALER_Extension_AgeRestriction);
sctx.signkeys = json_array ();
GNUNET_assert (NULL != sctx.signkeys);
@ -1887,7 +1889,7 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
// If age restriction is enabled, initialize the array of age restricted denoms.
/* TODO: optimize by putting this into global? */
if (TALER_extensions_is_enabled_type (TALER_Extension_AgeRestriction))
if (age_restriction_active)
{
age_restricted_denoms = json_array ();
GNUNET_assert (NULL != age_restricted_denoms);
@ -1972,15 +1974,14 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
/* 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)
{
if (age_restriction_active &&
(0 != dk->denom_pub.age_mask.mask))
{
GNUNET_log(GNUNET_ERROR_TYPE_WARNING, "got agerestricted denom %p with mask %d\n", &dk->denom_pub, dk->denom_pub.age_mask.mask);
array = age_restricted_denoms;
}
}
else
{
array = denoms;
}
GNUNET_assert (
0 ==
@ -1990,6 +1991,7 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
}
}
}
GNUNET_CONTAINER_heap_destroy (heap);
if (! GNUNET_TIME_absolute_is_zero (last_cpd.abs_time))
{
@ -2010,7 +2012,7 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
"Failed to generate key response data for %s\n",
GNUNET_TIME_timestamp2s (last_cpd));
json_decref (denoms);
if (NULL != age_restricted_denoms)
if (age_restriction_active && NULL != age_restricted_denoms)
json_decref (age_restricted_denoms);
json_decref (sctx.signkeys);
json_decref (recoup);

View File

@ -91,6 +91,8 @@ set_extensions (void *cls,
return GNUNET_DB_STATUS_HARD_ERROR;
}
GNUNET_assert(NULL != ext->config);
config = json_dumps (ext->config, JSON_COMPACT | JSON_SORT_KEYS);
if (NULL == config)
{
@ -140,6 +142,58 @@ set_extensions (void *cls,
}
static enum GNUNET_GenericReturnValue
verify_extensions_from_json (
json_t *extensions,
struct SetExtensionsContext *sec)
{
const char*name;
const struct TALER_Extension *extension;
size_t i=0;
json_t *blob;
GNUNET_assert (NULL != extensions);
GNUNET_assert (json_is_object (extensions));
sec->num_extensions = json_object_size (extensions);
sec->extensions = GNUNET_new_array (sec->num_extensions,
struct Extension);
json_object_foreach (extensions, name, blob)
{
int critical = 0;
json_t *config;
const char *version = NULL;
/* load and verify criticality, version, etc. */
extension = TALER_extensions_get_by_name (name);
if (NULL == extension)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"no such extension: %s\n", name);
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
TALER_extensions_is_json_config (
blob, &critical, &version, &config))
return GNUNET_SYSERR;
if (critical != extension->critical
|| 0 != strcmp (version, extension->version) // TODO: libtool compare?
|| NULL == config
|| GNUNET_OK != extension->test_json_config (config))
return GNUNET_SYSERR;
sec->extensions[i].type = extension->type;
sec->extensions[i].config = config;
}
return GNUNET_OK;
}
MHD_RESULT
TEH_handler_management_post_extensions (
struct MHD_Connection *connection,
@ -204,57 +258,18 @@ TEH_handler_management_post_extensions (
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Received /management/extensions\n");
sec.num_extensions = json_object_size (extensions);
sec.extensions = GNUNET_new_array (sec.num_extensions,
struct Extension);
/* Now parse individual extensions and signatures from those objects. */
if (GNUNET_OK !=
verify_extensions_from_json (extensions, &sec))
{
const struct TALER_Extension *extension = NULL;
const char *name;
json_t *config;
int idx = 0;
json_object_foreach (extensions, name, config){
/* 1. Make sure name refers to a supported extension */
extension = TALER_extensions_get_by_name (name);
if (NULL == extension)
{
ret = TALER_MHD_reply_with_error (
connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"invalid extension type");
goto CLEANUP;
}
sec.extensions[idx].config = config;
sec.extensions[idx].type = extension->type;
/* 2. Make sure the config is sound */
if (GNUNET_OK !=
extension->test_json_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;
}
/* We have a validly signed JSON object for the extension. Increment its
* refcount.
*/
json_incref (sec.extensions[idx].config);
idx++;
} /* json_object_foreach */
GNUNET_JSON_parse_free (top_spec);
return TALER_MHD_reply_with_error (
connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"invalid object");
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Received %u extensions\n",
sec.num_extensions);

View File

@ -247,7 +247,7 @@ age_restriction_load_json_config (
struct TALER_AgeMask mask = {0};
enum GNUNET_GenericReturnValue ret;
ret = TALER_JSON_parse_agemask (jconfig, &mask);
ret = TALER_JSON_parse_age_groups (jconfig, &mask);
if (GNUNET_OK != ret)
return ret;
@ -323,7 +323,7 @@ age_restriction_test_json_config (
{
struct TALER_AgeMask mask = {0};
return TALER_JSON_parse_agemask (config, &mask);
return TALER_JSON_parse_age_groups (config, &mask);
}
@ -357,4 +357,29 @@ TALER_extensions_age_restriction_num_groups ()
}
enum GNUNET_GenericReturnValue
TALER_JSON_parse_age_groups (const json_t *root,
struct TALER_AgeMask *mask)
{
enum GNUNET_GenericReturnValue ret;
const char *str;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("age_groups",
&str),
GNUNET_JSON_spec_end ()
};
ret = GNUNET_JSON_parse (root,
spec,
NULL,
NULL);
if (GNUNET_OK == ret)
TALER_parse_age_group_string (str, mask);
GNUNET_JSON_parse_free (spec);
return ret;
}
/* end of extension_age_restriction.c */

View File

@ -247,27 +247,31 @@ TALER_extensions_load_taler_config (
}
static enum GNUNET_GenericReturnValue
is_json_extension_config (
enum GNUNET_GenericReturnValue
TALER_extensions_is_json_config (
json_t *obj,
int *critical,
const char **version,
json_t **config)
{
enum GNUNET_GenericReturnValue ret;
json_t *cfg;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_boolean ("critical",
critical),
GNUNET_JSON_spec_string ("version",
version),
GNUNET_JSON_spec_json ("config",
config),
&cfg),
GNUNET_JSON_spec_end ()
};
ret = GNUNET_JSON_parse (obj, spec, NULL, NULL);
if (GNUNET_OK == ret)
{
*config = json_copy (cfg);
GNUNET_JSON_parse_free (spec);
}
return ret;
}
@ -300,7 +304,7 @@ TALER_extensions_load_json_config (
/* load and verify criticality, version, etc. */
if (GNUNET_OK !=
is_json_extension_config (
TALER_extensions_is_json_config (
blob, &critical, &version, &config))
return GNUNET_SYSERR;

View File

@ -85,6 +85,31 @@ enum GNUNET_GenericReturnValue
TALER_extensions_load_taler_config (
const struct GNUNET_CONFIGURATION_Handle *cfg);
/*
* Check the given obj to be a valid extension object and fill the fields
* accordingly.
*/
enum GNUNET_GenericReturnValue
TALER_extensions_is_json_config (
json_t *obj,
int *critical,
const char **version,
json_t **config);
/*
* Sets the configuration of the extensions from a given JSON object.
*
* he JSON object must be of type ExchangeKeysResponse as described in
* https://docs.taler.net/design-documents/006-extensions.html#exchange
*
* @param cfg JSON object containting the configuration for all extensions
* @return GNUNET_OK on success, GNUNET_SYSERR if unknown extensions were found
* or any particular configuration couldn't be parsed.
*/
enum GNUNET_GenericReturnValue
TALER_extensions_load_json_config (
json_t *cfg);
/*
* Returns the head of the linked list of extensions
*/
@ -156,20 +181,6 @@ TALER_extensions_verify_json_config_signature (
struct TALER_MasterSignatureP *extensions_sig,
struct TALER_MasterPublicKeyP *master_pub);
/*
* Sets the configuration of the extensions from a given JSON object.
*
* The JSON object must be of type ExchangeKeysResponse as described in
* https://docs.taler.net/design-documents/006-extensions.html#exchange
*
* @param cfg Handle to the TALER configuration
* @return GNUNET_OK on success, GNUNET_SYSERR if unknown extensions were found
* or any particular configuration couldn't be parsed.
*/
enum GNUNET_GenericReturnValue
TALER_extensions_load_json_config (
json_t *extensions);
/*
* TALER Age Restriction Extension
@ -234,6 +245,18 @@ TALER_extensions_age_restriction_enabled ();
size_t
TALER_extensions_age_restriction_num_groups ();
/**
* Parses a JSON object { "age_groups": "a:b:...y:z" }.
*
* @param root is the json object
* @param[out] mask on succes, will contain the age mask
* @return #GNUNET_OK on success and #GNUNET_SYSERR on failure.
*/
enum GNUNET_GenericReturnValue
TALER_JSON_parse_age_groups (const json_t *root,
struct TALER_AgeMask *mask);
/*
* TODO: Add Peer2Peer Extension
*/

View File

@ -552,17 +552,6 @@ enum GNUNET_GenericReturnValue
TALER_JSON_extensions_config_hash (const json_t *config,
struct TALER_ExtensionConfigHash *eh);
/**
* Parses a JSON object { "extension": "age_restriction", "mask": <uint32> }.
*
* @param root is the json object
* @param[out] mask on succes, will contain the age mask
* @return #GNUNET_OK on success and #GNUNET_SYSERR on failure.
*/
enum GNUNET_GenericReturnValue
TALER_JSON_parse_agemask (const json_t *root,
struct TALER_AgeMask *mask);
#endif /* TALER_JSON_LIB_H_ */
/* End of taler_json_lib.h */

View File

@ -2144,6 +2144,19 @@ TALER_TESTING_cmd_wire_del (const char *label,
unsigned int expected_http_status,
bool bad_sig);
/**
* Sign all extensions that the exchange has to offer, f. e. the extension for
* age restriction. This has to be run before any withdrawal of age restricted
* can be performed.
*
* @param label command label.
* @param config_filename configuration filename.
* @return the command
*/
struct TALER_TESTING_Command
TALER_TESTING_cmd_exec_offline_sign_extensions (const char *label,
const char *config_filename);
/**
* Sign all exchange denomination and online signing keys

View File

@ -658,37 +658,4 @@ TALER_JSON_spec_i18n_str (const char *name,
return ret;
}
enum GNUNET_GenericReturnValue
TALER_JSON_parse_agemask (const json_t *root,
struct TALER_AgeMask *mask)
{
const char *name;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("extension",
&name),
GNUNET_JSON_spec_uint32 ("mask",
&mask->mask),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK != GNUNET_JSON_parse (root,
spec,
NULL,
NULL))
{
return GNUNET_SYSERR;
}
if (! strncmp (name,
"age_restriction",
sizeof("age_restriction")))
{
return GNUNET_SYSERR;
}
return GNUNET_OK;
}
/* end of json/json_helper.c */

View File

@ -67,7 +67,7 @@
/**
* Set to 1 for extra debug logging.
*/
#define DEBUG 0
#define DEBUG 1 /* FIXME-oec */
/**
* Log error related to CURL operations.
@ -839,9 +839,10 @@ decode_keys_json (const json_t *resp_obj,
*/
struct
{ char *name;
bool is_optional_age_restriction;} hive[2] = {
bool is_optional_age_restriction;}
hive[2] = {
{ "denoms", false },
{ "age_restricted_denoms", true },
{ "age_restricted_denoms", true }
};
for (size_t s = 0; s < sizeof(hive) / sizeof(hive[0]); s++)

View File

@ -151,7 +151,7 @@ TALER_EXCHANGE_management_post_extensions (
body = GNUNET_JSON_PACK (
GNUNET_JSON_pack_object_steal ("extensions",
ped->extensions),
GNUNET_JSON_pack_data_auto ("extensions_sigs",
GNUNET_JSON_pack_data_auto ("extensions_sig",
&ped->extensions_sig));
eh = curl_easy_init ();
@ -168,7 +168,7 @@ TALER_EXCHANGE_management_post_extensions (
return NULL;
}
json_decref (body);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Requesting URL '%s'\n",
ph->url);
GNUNET_assert (CURLE_OK == curl_easy_setopt (eh,

View File

@ -68,6 +68,7 @@ libtalertesting_la_SOURCES = \
testing_api_cmd_oauth.c \
testing_api_cmd_offline_sign_fees.c \
testing_api_cmd_offline_sign_keys.c \
testing_api_cmd_offline_sign_extensions.c \
testing_api_cmd_set_wire_fee.c \
testing_api_cmd_recoup.c \
testing_api_cmd_recoup_refresh.c \

View File

@ -168,6 +168,42 @@ run (void *cls,
TALER_TESTING_cmd_end ()
};
/**
* Test withdrawal with age restriction. Success is expected, so it MUST be
* called _after_ TALER_TESTING_cmd_exec_offline_sign_extensions is called,
* i. e. age restriction is activated in the exchange!
*
* TODO: create a test that tries to withdraw coins with age restriction but
* (expectedly) fails because the exchange doesn't support age restriction
* yet.
*/
struct TALER_TESTING_Command withdraw_age[] = {
/**
* Move money to the exchange's bank account.
*/
CMD_TRANSFER_TO_EXCHANGE ("create-reserve-age",
"EUR:6.02"),
TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-age",
"EUR:6.02",
bc.user42_payto,
bc.exchange_payto,
"create-reserve-age"),
/**
* Make a reserve exist, according to the previous
* transfer.
*/
CMD_EXEC_WIREWATCH ("wirewatch-age"),
/**
* Withdraw EUR:5.
*/
TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-age",
"create-reserve-age",
"EUR:5",
13,
MHD_HTTP_OK),
TALER_TESTING_cmd_end ()
};
struct TALER_TESTING_Command spend[] = {
/**
* Spend the coin.
@ -951,6 +987,8 @@ run (void *cls,
"payto://x-taler-bank/localhost/2",
MHD_HTTP_NO_CONTENT,
false),
TALER_TESTING_cmd_exec_offline_sign_extensions ("offline-sign-extensions",
CONFIG_FILE),
TALER_TESTING_cmd_exec_offline_sign_keys ("offline-sign-future-keys",
CONFIG_FILE),
TALER_TESTING_cmd_exec_offline_sign_fees ("offline-sign-fees",
@ -963,6 +1001,8 @@ run (void *cls,
wire),
TALER_TESTING_cmd_batch ("withdraw",
withdraw),
TALER_TESTING_cmd_batch ("withdraw-age",
withdraw_age),
TALER_TESTING_cmd_batch ("spend",
spend),
TALER_TESTING_cmd_batch ("refresh",

View File

@ -150,7 +150,7 @@ fee_deposit = EUR:0.00
fee_refresh = EUR:0.01
fee_refund = EUR:0.01
rsa_keysize = 1024
age_restricted = true
age_restricted = YES
[coin_eur_ct_10_age_restricted]
value = EUR:0.10
@ -162,7 +162,7 @@ fee_deposit = EUR:0.01
fee_refresh = EUR:0.03
fee_refund = EUR:0.01
rsa_keysize = 1024
age_restricted = true
age_restricted = YES
[coin_eur_1_age_restricted]
value = EUR:1
@ -174,7 +174,7 @@ fee_deposit = EUR:0.01
fee_refresh = EUR:0.03
fee_refund = EUR:0.01
rsa_keysize = 1024
age_restricted = true
age_restricted = YES
[coin_eur_5_age_restricted]
value = EUR:5
@ -186,7 +186,7 @@ fee_deposit = EUR:0.01
fee_refresh = EUR:0.03
fee_refund = EUR:0.01
rsa_keysize = 1024
age_restricted = true
age_restricted = YES
[coin_eur_10_age_restricted]
value = EUR:10
@ -198,4 +198,4 @@ fee_deposit = EUR:0.01
fee_refresh = EUR:0.03
fee_refund = EUR:0.01
rsa_keysize = 1024
age_restricted = true
age_restricted = YES

View File

@ -0,0 +1,164 @@
/*
This file is part of TALER
Copyright (C) 2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published
by the Free Software Foundation; either version 3, or (at your
option) any later version.
TALER is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public
License along with TALER; see the file COPYING. If not,
see <http://www.gnu.org/licenses/>
*/
/**
* @file testing/testing_api_cmd_offline_sign_extensions.c
* @brief run the taler-exchange-offline command to sign extensions (and therefore activate them)
* @author Özgür Kesim
*/
#include "platform.h"
#include "taler_json_lib.h"
#include <gnunet/gnunet_curl_lib.h>
#include "taler_signatures.h"
#include "taler_testing_lib.h"
/**
* State for a "extensionssign" CMD.
*/
struct ExtensionsSignState
{
/**
* Process for the "extensionssign" command.
*/
struct GNUNET_OS_Process *extensionssign_proc;
/**
* Configuration file used by the command.
*/
const char *config_filename;
};
/**
* Run the command; calls the `taler-exchange-offline' program.
*
* @param cls closure.
* @param cmd the commaind being run.
* @param is interpreter state.
*/
static void
extensionssign_run (void *cls,
const struct TALER_TESTING_Command *cmd,
struct TALER_TESTING_Interpreter *is)
{
struct ExtensionsSignState *ks = cls;
ks->extensionssign_proc
= GNUNET_OS_start_process (
GNUNET_OS_INHERIT_STD_ALL,
NULL, NULL, NULL,
"taler-exchange-offline",
"taler-exchange-offline",
"-c", ks->config_filename,
"-L", "INFO",
"extensions",
"sign",
"upload",
NULL);
if (NULL == ks->extensionssign_proc)
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
return;
}
TALER_TESTING_wait_for_sigchld (is);
}
/**
* Free the state of a "extensionssign" CMD, and possibly kills its
* process if it did not terminate correctly.
*
* @param cls closure.
* @param cmd the command being freed.
*/
static void
extensionssign_cleanup (void *cls,
const struct TALER_TESTING_Command *cmd)
{
struct ExtensionsSignState *ks = cls;
(void) cmd;
if (NULL != ks->extensionssign_proc)
{
GNUNET_break (0 ==
GNUNET_OS_process_kill (ks->extensionssign_proc,
SIGKILL));
GNUNET_OS_process_wait (ks->extensionssign_proc);
GNUNET_OS_process_destroy (ks->extensionssign_proc);
ks->extensionssign_proc = NULL;
}
GNUNET_free (ks);
}
/**
* Offer "extensionssign" CMD internal data to other commands.
*
* @param cls closure.
* @param[out] ret result
* @param trait name of the trait.
* @param index index number of the object to offer.
* @return #GNUNET_OK on success.
*/
static enum GNUNET_GenericReturnValue
extensionssign_traits (void *cls,
const void **ret,
const char *trait,
unsigned int index)
{
struct ExtensionsSignState *ks = cls;
struct TALER_TESTING_Trait traits[] = {
TALER_TESTING_make_trait_process (&ks->extensionssign_proc),
TALER_TESTING_trait_end ()
};
return TALER_TESTING_get_trait (traits,
ret,
trait,
index);
}
struct TALER_TESTING_Command
TALER_TESTING_cmd_exec_offline_sign_extensions (const char *label,
const char *config_filename)
{
struct ExtensionsSignState *ks;
ks = GNUNET_new (struct ExtensionsSignState);
ks->config_filename = config_filename;
{
struct TALER_TESTING_Command cmd = {
.cls = ks,
.label = label,
.run = &extensionssign_run,
.cleanup = &extensionssign_cleanup,
.traits = &extensionssign_traits
};
return cmd;
}
}
/* end of testing_api_cmd_exec_offline_sign_extensions.c */