diff --git a/contrib/gana b/contrib/gana index b0dd85e81..94b3d826d 160000 --- a/contrib/gana +++ b/contrib/gana @@ -1 +1 @@ -Subproject commit b0dd85e8187f33a1f92dd5eb31082050d333e168 +Subproject commit 94b3d826de2aa442a985d03a52eaf526ef950d83 diff --git a/src/auditor/taler-helper-auditor-coins.c b/src/auditor/taler-helper-auditor-coins.c index 3473a8284..5bb9ae3ea 100644 --- a/src/auditor/taler-helper-auditor-coins.c +++ b/src/auditor/taler-helper-auditor-coins.c @@ -1227,6 +1227,7 @@ static int refresh_session_cb (void *cls, uint64_t rowid, const struct TALER_DenominationPublicKey *denom_pub, + const struct TALER_AgeCommitmentHash *h_age_commitment, const struct TALER_CoinSpendPublicKeyP *coin_pub, const struct TALER_CoinSpendSignatureP *coin_sig, const struct TALER_Amount *amount_with_fee, @@ -1289,6 +1290,7 @@ refresh_session_cb (void *cls, &fee_refresh, rc, &h_denom_pub, + h_age_commitment, coin_pub, coin_sig)) { @@ -1615,6 +1617,7 @@ deposit_cb (void *cls, struct TALER_MerchantWireHash h_wire; struct TALER_DenominationHash h_denom_pub; struct TALER_Amount deposit_fee; + struct TALER_AgeCommitmentHash *h_age_commitment = NULL; /* FIXME-oec */ TALER_denom_pub_hash (denom_pub, &h_denom_pub); @@ -1631,6 +1634,7 @@ deposit_cb (void *cls, &deposit_fee, &h_wire, &deposit->h_contract_terms, + h_age_commitment, /* FIXME-oec */ NULL /* h_extensions! */, &h_denom_pub, deposit->timestamp, diff --git a/src/benchmark/taler-aggregator-benchmark.c b/src/benchmark/taler-aggregator-benchmark.c index 411921000..365ae68dd 100644 --- a/src/benchmark/taler-aggregator-benchmark.c +++ b/src/benchmark/taler-aggregator-benchmark.c @@ -300,7 +300,7 @@ add_deposit (const struct Merchant *m) struct TALER_EXCHANGEDB_Deposit deposit; uint64_t known_coin_id; struct TALER_DenominationHash dph; - struct TALER_AgeHash agh; + struct TALER_AgeCommitmentHash agh; RANDOMIZE (&d.coin.coin_pub); d.coin.denom_pub_hash = h_denom_pub; diff --git a/src/benchmark/taler-exchange-benchmark.c b/src/benchmark/taler-exchange-benchmark.c index 25c3b0455..77ef94ebc 100644 --- a/src/benchmark/taler-exchange-benchmark.c +++ b/src/benchmark/taler-exchange-benchmark.c @@ -366,6 +366,7 @@ run (void *cls, (TALER_TESTING_cmd_withdraw_amount (wl, create_reserve_label, amount_5, + 0, /* age restriction off */ MHD_HTTP_OK)); unit[1] = TALER_TESTING_cmd_deposit_with_retry diff --git a/src/exchange-tools/taler-exchange-offline.c b/src/exchange-tools/taler-exchange-offline.c index 6ad345ebf..b7be2ddaa 100644 --- a/src/exchange-tools/taler-exchange-offline.c +++ b/src/exchange-tools/taler-exchange-offline.c @@ -152,6 +152,10 @@ static char *currency; */ static char *CFG_exchange_url; +/** + * If age restriction is enabled, the age mask to be used + */ +static struct TALER_AgeMask age_mask = {0}; /** * A subcommand supported by this program. @@ -1924,6 +1928,7 @@ trigger_upload (const char *exchange_url) if (0 == strcasecmp (key, uhs[i].key)) { + found = true; uhs[i].cb (exchange_url, index, @@ -3036,6 +3041,7 @@ do_show (char *const *args) keys = parse_keys_input ("show"); if (NULL == keys) return; + if (GNUNET_OK != load_offline_key (GNUNET_NO)) return; @@ -3196,6 +3202,43 @@ sign_signkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub, } +/** + * Looks up the AGE_RESTRICTED setting for a denomination in the config and + * returns the age restriction (mask) accordingly. + * + * @param section_name Section in the configuration for the particular + * denomination. + */ +static struct TALER_AgeMask +load_age_mask (const char*section_name) +{ + static const struct TALER_AgeMask null_mask = {0}; + enum GNUNET_GenericReturnValue ret; + + if (age_mask.mask == 0) + return null_mask; + + if (GNUNET_OK != (GNUNET_CONFIGURATION_have_value ( + kcfg, + section_name, + "AGE_RESTRICTED"))) + return null_mask; + + ret = GNUNET_CONFIGURATION_get_value_yesno (kcfg, + section_name, + "AGE_RESTRICTED"); + if (GNUNET_YES == ret) + return age_mask; + + if (GNUNET_SYSERR == ret) + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section_name, + "AGE_RESTRICTED", + "Value must be YES or NO\n"); + return null_mask; +} + + /** * Sign @a denomkeys with offline key. * @@ -3284,7 +3327,10 @@ sign_denomkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub, duration = GNUNET_TIME_absolute_get_difference ( stamp_start.abs_time, stamp_expire_withdraw.abs_time); - // FIXME-Oec: setup age mask here? + + /* Load the age mask, if applicable to this denomination */ + denom_pub.age_mask = load_age_mask (section_name); + TALER_denom_pub_hash (&denom_pub, &h_denom_pub); switch (denom_pub.cipher) @@ -3518,14 +3564,6 @@ do_extensions_show (char *const *args) json_t *exts = json_object (); const struct TALER_Extension *it; - TALER_extensions_init (); - if (GNUNET_OK != TALER_extensions_load_taler_config (kcfg)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "error while loading taler config for extensions\n"); - return; - } - for (it = TALER_extensions_get_head (); NULL != it; it = it->next) @@ -3779,6 +3817,17 @@ run (void *cls, global_ret = EXIT_NOTCONFIGURED; return; } + + /* load age mask, if age restriction is enabled */ + TALER_extensions_init (); + if (GNUNET_OK != TALER_extensions_load_taler_config (kcfg)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "error while loading taler config for extensions\n"); + return; + } + age_mask = TALER_extensions_age_restriction_ageMask (); + ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, &rc); rc = GNUNET_CURL_gnunet_rc_create (ctx); diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c index ae5847d11..52224655f 100644 --- a/src/exchange/taler-exchange-httpd.c +++ b/src/exchange/taler-exchange-httpd.c @@ -126,6 +126,12 @@ char *TEH_currency; */ char *TEH_base_url; +/** + * Age restriction flags and mask + */ +bool TEH_age_restriction_enabled = false; +struct TALER_AgeMask TEH_age_mask = {0}; + /** * Default timeout in seconds for HTTP requests. */ @@ -736,6 +742,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/*"); diff --git a/src/exchange/taler-exchange-httpd.h b/src/exchange/taler-exchange-httpd.h index d3b1ba84a..ffbce0e9b 100644 --- a/src/exchange/taler-exchange-httpd.h +++ b/src/exchange/taler-exchange-httpd.h @@ -186,6 +186,12 @@ extern struct TALER_EXCHANGEDB_Plugin *TEH_plugin; */ extern char *TEH_currency; +/* + * Age restriction extension state + */ +extern bool TEH_age_restriction_enabled; +extern struct TALER_AgeMask TEH_age_mask; + /** * Our (externally visible) base URL. */ diff --git a/src/exchange/taler-exchange-httpd_db.c b/src/exchange/taler-exchange-httpd_db.c index 3600d7931..f331e17d2 100644 --- a/src/exchange/taler-exchange-httpd_db.c +++ b/src/exchange/taler-exchange-httpd_db.c @@ -50,7 +50,7 @@ TEH_make_coin_known (const struct TALER_CoinPublicInfo *coin, { enum TALER_EXCHANGEDB_CoinKnownStatus cks; struct TALER_DenominationHash h_denom_pub; - struct TALER_AgeHash age_hash; + struct TALER_AgeCommitmentHash age_hash; /* make sure coin is 'known' in database */ cks = TEH_plugin->ensure_coin_known (TEH_plugin->cls, diff --git a/src/exchange/taler-exchange-httpd_deposit.c b/src/exchange/taler-exchange-httpd_deposit.c index 84741b5c3..c3efd9ff5 100644 --- a/src/exchange/taler-exchange-httpd_deposit.c +++ b/src/exchange/taler-exchange-httpd_deposit.c @@ -239,6 +239,9 @@ TEH_handler_deposit (struct MHD_Connection *connection, &deposit.merchant_pub), GNUNET_JSON_spec_fixed_auto ("h_contract_terms", &deposit.h_contract_terms), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("h_age_commitment", + &deposit.coin.age_commitment_hash)), GNUNET_JSON_spec_fixed_auto ("coin_sig", &deposit.csig), GNUNET_JSON_spec_timestamp ("timestamp", @@ -387,6 +390,7 @@ TEH_handler_deposit (struct MHD_Connection *connection, &deposit.deposit_fee, &h_wire, &deposit.h_contract_terms, + &deposit.coin.age_commitment_hash, NULL /* h_extensions! */, &deposit.coin.denom_pub_hash, deposit.timestamp, diff --git a/src/exchange/taler-exchange-httpd_extensions.c b/src/exchange/taler-exchange-httpd_extensions.c index 8edb24d40..6894a0762 100644 --- a/src/exchange/taler-exchange-httpd_extensions.c +++ b/src/exchange/taler-exchange-httpd_extensions.c @@ -127,6 +127,16 @@ extension_update_event_cb (void *cls, GNUNET_break (0); } } + + /* Special case age restriction: Update global flag and mask */ + if (TALER_Extension_AgeRestriction == type) + { + TEH_age_mask.mask = 0; + TEH_age_restriction_enabled = + TALER_extensions_age_restriction_is_enabled (); + if (TEH_age_restriction_enabled) + TEH_age_mask = TALER_extensions_age_restriction_ageMask (); + } } @@ -151,6 +161,12 @@ TEH_extensions_init () return GNUNET_SYSERR; } + /* FIXME: shall we load the extensions from the config right away? + * We do have to for now, as otherwise denominations with age restriction + * will not have the age mask set right upon initial generation. + */ + TALER_extensions_load_taler_config (TEH_cfg); + /* Trigger the initial load of configuration from the db */ for (const struct TALER_Extension *it = TALER_extensions_get_head (); NULL != it->next; diff --git a/src/exchange/taler-exchange-httpd_keys.c b/src/exchange/taler-exchange-httpd_keys.c index de9b81cdd..4a68ef56a 100644 --- a/src/exchange/taler-exchange-httpd_keys.c +++ b/src/exchange/taler-exchange-httpd_keys.c @@ -743,43 +743,30 @@ static struct TALER_AgeMask load_age_mask (const char*section_name) { static const struct TALER_AgeMask null_mask = {0}; - struct TALER_AgeMask age_mask = {0}; - /* TODO: optimize by putting this into global? */ - const struct TALER_Extension *age_ext = - TALER_extensions_get_by_type (TALER_Extension_AgeRestriction); - - // Get the age mask from the extension, if configured - /* TODO: optimize by putting this into global? */ - if (TALER_extensions_is_enabled (age_ext)) - age_mask = *(struct TALER_AgeMask *) age_ext->config; + enum GNUNET_GenericReturnValue ret; + struct TALER_AgeMask age_mask = TALER_extensions_age_restriction_ageMask (); if (age_mask.mask == 0) - { - /* Age restriction support is not enabled. Ignore the AGE_RESTRICTED field - * for the particular denomination and simply return the null_mask - */ return null_mask; - } - if (GNUNET_OK == (GNUNET_CONFIGURATION_have_value ( + if (GNUNET_OK != (GNUNET_CONFIGURATION_have_value ( TEH_cfg, section_name, "AGE_RESTRICTED"))) - { - enum GNUNET_GenericReturnValue ret; - if (GNUNET_SYSERR == (ret = GNUNET_CONFIGURATION_get_value_yesno (TEH_cfg, - section_name, - "AGE_RESTRICTED"))) - { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - section_name, - "AGE_RESTRICTED", - "Value must be YES or NO\n"); - return null_mask; - } - } + return null_mask; - return age_mask; + ret = GNUNET_CONFIGURATION_get_value_yesno (TEH_cfg, + section_name, + "AGE_RESTRICTED"); + if (GNUNET_YES == ret) + return age_mask; + + if (GNUNET_SYSERR == ret) + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section_name, + "AGE_RESTRICTED", + "Value must be YES or NO\n"); + return null_mask; } @@ -1187,6 +1174,8 @@ denomination_info_cb ( dk->meta = *meta; dk->master_sig = *master_sig; dk->recoup_possible = recoup_possible; + dk->denom_pub.age_mask = meta->age_mask; + GNUNET_assert ( GNUNET_OK == GNUNET_CONTAINER_multihashmap_put (ksh->denomkey_map, @@ -1601,7 +1590,7 @@ setup_general_response_headers (struct TEH_KeyStateHandle *ksh, * @a recoup and @a denoms. * * @param[in,out] ksh key state handle we build @a krd for - * @param[in] denom_keys_hash hash over all the denominatoin keys in @a denoms + * @param[in] denom_keys_hash hash over all the denominatoin keys in @a denoms and age_restricted_denoms * @param last_cpd timestamp to use * @param signkeys list of sign keys to return * @param recoup list of revoked keys to return @@ -1719,7 +1708,8 @@ create_krd (struct TEH_KeyStateHandle *ksh, int r; /* skip if not configured == disabled */ - if (NULL == extension->config) + if (NULL == extension->config || + NULL == extension->config_json) continue; /* flag our findings so far */ @@ -1755,7 +1745,7 @@ create_krd (struct TEH_KeyStateHandle *ksh, json_t *sig; int r; - r = json_object_set_new ( + r = json_object_set ( keys, "extensions", extensions); @@ -1771,14 +1761,14 @@ create_krd (struct TEH_KeyStateHandle *ksh, GNUNET_assert (0 == r); } - // Special case for age restrictions: if enabled, provide the lits of + // Special case for age restrictions: if enabled, provide the list of // age-restricted denominations. if (age_restriction_enabled && NULL != age_restricted_denoms) { GNUNET_assert ( 0 == - json_object_set_new ( + json_object_set ( keys, "age_restricted_denoms", age_restricted_denoms)); @@ -1857,7 +1847,9 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh) json_t *age_restricted_denoms = NULL; struct GNUNET_TIME_Timestamp last_cpd; struct GNUNET_CONTAINER_Heap *heap; - struct GNUNET_HashContext *hash_context; + struct GNUNET_HashContext *hash_context = NULL; + struct GNUNET_HashContext *hash_context_restricted = NULL; + bool have_age_restricted_denoms = false; sctx.signkeys = json_array (); GNUNET_assert (NULL != sctx.signkeys); @@ -1882,19 +1874,23 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh) = GNUNET_TIME_relative_min (dkc.min_dk_frequency, sctx.min_sk_frequency); } + denoms = json_array (); GNUNET_assert (NULL != denoms); + hash_context = GNUNET_CRYPTO_hash_context_start (); - // 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 is enabled, initialize the array of age restricted + denoms and prepare a hash for them, separate from the others. We will join + those hashes afterwards.*/ + if (TEH_age_restriction_enabled) { age_restricted_denoms = json_array (); GNUNET_assert (NULL != age_restricted_denoms); + hash_context_restricted = GNUNET_CRYPTO_hash_context_start (); } last_cpd = GNUNET_TIME_UNIT_ZERO_TS; - hash_context = GNUNET_CRYPTO_hash_context_start (); + { struct TEH_DenominationKey *dk; @@ -1908,6 +1904,11 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh) { struct GNUNET_HashCode hc; + /* FIXME-oec: Do we need to take hash_context_restricted into account + * in this if-branch!? Current tests suggests: no, (they don't fail). + * But something seems to be odd about only finishing hash_context. + */ + GNUNET_CRYPTO_hash_context_finish ( GNUNET_CRYPTO_hash_context_copy (hash_context), &hc); @@ -1936,14 +1937,14 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh) return GNUNET_SYSERR; } } + last_cpd = dk->meta.start; - GNUNET_CRYPTO_hash_context_read (hash_context, - &dk->h_denom_pub, - sizeof (struct GNUNET_HashCode)); { json_t *denom; json_t *array; + struct GNUNET_HashContext *hc; + denom = GNUNET_JSON_PACK ( @@ -1970,18 +1971,26 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh) TALER_JSON_pack_amount ("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) + /* Put the denom into the correct array depending on the settings and + * the properties of the denomination. Also, we build up the right + * hash for the corresponding array. */ + if (TEH_age_restriction_enabled && + (0 != dk->denom_pub.age_mask.mask)) { + have_age_restricted_denoms = true; array = age_restricted_denoms; + hc = hash_context_restricted; } else { array = denoms; + hc = hash_context; } + GNUNET_CRYPTO_hash_context_read (hc, + &dk->h_denom_pub, + sizeof (struct GNUNET_HashCode)); + GNUNET_assert ( 0 == json_array_append_new ( @@ -1990,13 +1999,27 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh) } } } + GNUNET_CONTAINER_heap_destroy (heap); if (! GNUNET_TIME_absolute_is_zero (last_cpd.abs_time)) { struct GNUNET_HashCode hc; + /* If age restriction is active and we had at least one denomination of + * that sort, we simply add the hash of all age restricted denominations at + * the end of the others. */ + if (TEH_age_restriction_enabled && have_age_restricted_denoms) + { + struct GNUNET_HashCode hcr; + GNUNET_CRYPTO_hash_context_finish (hash_context_restricted, &hcr); + GNUNET_CRYPTO_hash_context_read (hash_context, + &hcr, + sizeof (struct GNUNET_HashCode)); + } + GNUNET_CRYPTO_hash_context_finish (hash_context, &hc); + if (GNUNET_OK != create_krd (ksh, &hc, @@ -2010,7 +2033,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 (TEH_age_restriction_enabled && NULL != age_restricted_denoms) json_decref (age_restricted_denoms); json_decref (sctx.signkeys); json_decref (recoup); @@ -2667,7 +2690,9 @@ load_extension_data (const char *section_name, TEH_currency); return GNUNET_SYSERR; } - meta->age_restrictions = load_age_mask (section_name); + + meta->age_mask = load_age_mask (section_name); + return GNUNET_OK; } @@ -2794,7 +2819,7 @@ add_future_denomkey_cb (void *cls, struct FutureBuilderContext *fbc = cls; struct HelperDenomination *hd = value; struct TEH_DenominationKey *dk; - struct TALER_EXCHANGEDB_DenominationKeyMetaData meta; + struct TALER_EXCHANGEDB_DenominationKeyMetaData meta = {0}; dk = GNUNET_CONTAINER_multihashmap_get (fbc->ksh->denomkey_map, h_denom_pub); diff --git a/src/exchange/taler-exchange-httpd_management_extensions.c b/src/exchange/taler-exchange-httpd_management_extensions.c index 17b000067..ab0287e33 100644 --- a/src/exchange/taler-exchange-httpd_management_extensions.c +++ b/src/exchange/taler-exchange-httpd_management_extensions.c @@ -31,7 +31,6 @@ #include "taler_extensions.h" #include "taler_dbevents.h" - /** * Extension carries the necessary data for a particular extension. * @@ -91,6 +90,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 +141,57 @@ 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 +256,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); @@ -281,6 +294,7 @@ TEH_handler_management_post_extensions ( NULL, 0); + CLEANUP: for (unsigned int i = 0; i < sec.num_extensions; i++) { diff --git a/src/exchange/taler-exchange-httpd_management_post_keys.c b/src/exchange/taler-exchange-httpd_management_post_keys.c index f0c3f1f39..c353a9959 100644 --- a/src/exchange/taler-exchange-httpd_management_post_keys.c +++ b/src/exchange/taler-exchange-httpd_management_post_keys.c @@ -204,6 +204,7 @@ add_keys (void *cls, TALER_denom_pub_free (&denom_pub); return GNUNET_DB_STATUS_HARD_ERROR; } + if (is_active) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, @@ -211,6 +212,7 @@ add_keys (void *cls, GNUNET_h2s (&d->h_denom_pub.hash)); continue; /* skip, already known */ } + qs = TEH_plugin->add_denomination_key ( TEH_plugin->cls, &d->h_denom_pub, diff --git a/src/exchange/taler-exchange-httpd_melt.c b/src/exchange/taler-exchange-httpd_melt.c index 54f1385d7..334ec1287 100644 --- a/src/exchange/taler-exchange-httpd_melt.c +++ b/src/exchange/taler-exchange-httpd_melt.c @@ -278,6 +278,7 @@ check_melt_valid (struct MHD_Connection *connection, &mret); if (NULL == dk) return mret; + if (GNUNET_TIME_absolute_is_past (dk->meta.expire_legal.abs_time)) { /* Way too late now, even zombies have expired */ @@ -287,6 +288,7 @@ check_melt_valid (struct MHD_Connection *connection, TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED, "MELT"); } + if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time)) { /* This denomination is not yet valid */ @@ -299,6 +301,7 @@ check_melt_valid (struct MHD_Connection *connection, rmc->coin_refresh_fee = dk->meta.fee_refresh; rmc->coin_value = dk->meta.value; + /* sanity-check that "total melt amount > melt fee" */ if (0 < TALER_amount_cmp (&rmc->coin_refresh_fee, @@ -328,6 +331,7 @@ check_melt_valid (struct MHD_Connection *connection, &rmc->coin_refresh_fee, &rmc->refresh_session.rc, &rmc->refresh_session.coin.denom_pub_hash, + &rmc->refresh_session.coin.age_commitment_hash, &rmc->refresh_session.coin.coin_pub, &rmc->refresh_session.coin_sig)) { @@ -403,6 +407,9 @@ TEH_handler_melt (struct MHD_Connection *connection, &rmc.refresh_session.coin.denom_sig), GNUNET_JSON_spec_fixed_auto ("denom_pub_hash", &rmc.refresh_session.coin.denom_pub_hash), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("age_commitment_hash", + &rmc.refresh_session.coin.age_commitment_hash)), GNUNET_JSON_spec_fixed_auto ("confirm_sig", &rmc.refresh_session.coin_sig), TALER_JSON_spec_amount ("value_with_fee", diff --git a/src/exchange/taler-exchange-httpd_recoup-refresh.c b/src/exchange/taler-exchange-httpd_recoup-refresh.c index 78a454c85..ec5589926 100644 --- a/src/exchange/taler-exchange-httpd_recoup-refresh.c +++ b/src/exchange/taler-exchange-httpd_recoup-refresh.c @@ -248,7 +248,7 @@ verify_and_execute_recoup_refresh ( if (GNUNET_OK != TALER_denom_blind (&dk->denom_pub, coin_bks, - NULL, /* FIXME-Oec: TALER_AgeHash * */ + NULL, /* FIXME-Oec: TALER_AgeCommitmentHash * */ &coin->coin_pub, &c_hash, &coin_ev, diff --git a/src/exchange/taler-exchange-httpd_recoup.c b/src/exchange/taler-exchange-httpd_recoup.c index 0deaa8bbb..9b1ba8224 100644 --- a/src/exchange/taler-exchange-httpd_recoup.c +++ b/src/exchange/taler-exchange-httpd_recoup.c @@ -250,7 +250,7 @@ verify_and_execute_recoup ( if (GNUNET_OK != TALER_denom_blind (&dk->denom_pub, coin_bks, - NULL, /* FIXME-Oec: TALER_AgeHash * */ + NULL, /* FIXME-Oec: TALER_AgeCommitmentHash * */ &coin->coin_pub, &c_hash, &coin_ev, diff --git a/src/exchange/taler-exchange-httpd_refreshes_reveal.c b/src/exchange/taler-exchange-httpd_refreshes_reveal.c index 30a7294c1..aae87290e 100644 --- a/src/exchange/taler-exchange-httpd_refreshes_reveal.c +++ b/src/exchange/taler-exchange-httpd_refreshes_reveal.c @@ -191,6 +191,7 @@ check_commitment (struct RevealContext *rctx, GNUNET_assert (GNUNET_OK == TALER_planchet_prepare (rcd->dk, &ps, + NULL, /* FIXME-Oec, struct TALER_AgeCommitmentHash * */ &c_hash, &pd)); rcd->coin_ev = pd.coin_ev; @@ -285,6 +286,7 @@ check_commitment (struct RevealContext *rctx, * @param rctx context for the operation, partially built at this time * @param link_sigs_json link signatures in JSON format * @param new_denoms_h_json requests for fresh coins to be created + * @param old_age_commitment_json age commitment that went into the withdrawal, maybe NULL * @param coin_evs envelopes of gamma-selected coins to be signed * @return MHD result code */ @@ -293,6 +295,7 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection, struct RevealContext *rctx, const json_t *link_sigs_json, const json_t *new_denoms_h_json, + const json_t *old_age_commitment_json, const json_t *coin_evs) { unsigned int num_fresh_coins = json_array_size (new_denoms_h_json); @@ -317,6 +320,7 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection, TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, NULL); } + /* Parse denomination key hashes */ for (unsigned int i = 0; igamma_tp, &rrcs[i].coin_envelope_hash, &rctx->melt.session.coin.coin_pub, + NULL, // TODO-oec: calculate the correct h_age_commitment &rrcs[i].orig_coin_link_sig)) { GNUNET_break_op (0); @@ -489,6 +496,7 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection, rcd->coin_ev_size = rrc->coin_ev_size; rcd->dk = &dks[i]->denom_pub; } + rctx->dks = dks; rctx->rcds = rcds; if (GNUNET_OK != @@ -500,6 +508,7 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection, GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Creating %u signatures\n", (unsigned int) rctx->num_fresh_coins); + /* create fresh coin signatures */ for (unsigned int i = 0; inum_fresh_coins; i++) { @@ -520,8 +529,10 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection, goto cleanup; } } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Signatures ready, starting DB interaction\n"); + /* Persist operation result in DB */ { enum GNUNET_DB_QueryStatus qs; @@ -577,11 +588,18 @@ cleanup: * revealed information is valid then returns the signed refreshed * coins. * + * If the denomination has age restriction support, the array of EDDSA public + * keys, one for each age group that was activated during the withdrawal + * by the parent/ward, must be provided in old_age_commitment. The hash of + * this array must be the same as the h_age_commitment of the persisted reveal + * request. + * * @param connection the MHD connection to handle * @param rctx context for the operation, partially built at this time * @param tp_json private transfer keys in JSON format * @param link_sigs_json link signatures in JSON format * @param new_denoms_h_json requests for fresh coins to be created + * @param old_age_commitment_json array of EDDSA public keys in JSON, used for age restriction, maybe NULL * @param coin_evs envelopes of gamma-selected coins to be signed * @return MHD result code */ @@ -591,6 +609,7 @@ handle_refreshes_reveal_json (struct MHD_Connection *connection, const json_t *tp_json, const json_t *link_sigs_json, const json_t *new_denoms_h_json, + const json_t *old_age_commitment_json, const json_t *coin_evs) { unsigned int num_fresh_coins = json_array_size (new_denoms_h_json); @@ -626,6 +645,19 @@ handle_refreshes_reveal_json (struct MHD_Connection *connection, "new_denoms/link_sigs"); } + /* Sanity check of age commitment: If it was provided, it _must_ be an array + * of the size the # of age groups */ + if (NULL != old_age_commitment_json + && TALER_extensions_age_restriction_num_groups () != + json_array_size (old_age_commitment_json)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_REFRESHES_REVEAL_AGE_RESTRICTION_COMMITMENT_INVALID, + "old_age_commitment"); + } + /* Parse transfer private keys array */ for (unsigned int i = 0; ideposit_fee, &h_wire, &deposit->h_contract_terms, + NULL, /* h_age_commitment, FIXME-oec */ NULL /* h_extensions! */, &deposit->h_denom_pub, deposit->timestamp, @@ -122,6 +123,7 @@ TEH_RESPONSE_compile_transaction_history ( { const struct TALER_EXCHANGEDB_MeltListEntry *melt = pos->details.melt; + const struct TALER_AgeCommitmentHash *phac = NULL; #if ENABLE_SANITY_CHECKS if (GNUNET_OK != @@ -129,6 +131,7 @@ TEH_RESPONSE_compile_transaction_history ( &melt->melt_fee, &melt->rc, &melt->h_denom_pub, + &melt->h_age_commitment, coin_pub, &melt->coin_sig)) { @@ -137,6 +140,12 @@ TEH_RESPONSE_compile_transaction_history ( return NULL; } #endif + + /* Age restriction is optional. We communicate a NULL value to + * JSON_PACK below */ + if (! TALER_AgeCommitmentHash_isNullOrZero (&melt->h_age_commitment)) + phac = &melt->h_age_commitment; + if (0 != json_array_append_new ( history, @@ -151,6 +160,9 @@ TEH_RESPONSE_compile_transaction_history ( &melt->rc), GNUNET_JSON_pack_data_auto ("h_denom_pub", &melt->h_denom_pub), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_data_auto ("h_age_commitment", + phac)), GNUNET_JSON_pack_data_auto ("coin_sig", &melt->coin_sig)))) { diff --git a/src/exchangedb/exchange-0001.sql b/src/exchangedb/exchange-0001.sql index a8e79335b..d8f92b0bf 100644 --- a/src/exchangedb/exchange-0001.sql +++ b/src/exchangedb/exchange-0001.sql @@ -25,7 +25,7 @@ CREATE TABLE IF NOT EXISTS denominations (denominations_serial BIGSERIAL UNIQUE ,denom_pub_hash BYTEA PRIMARY KEY CHECK (LENGTH(denom_pub_hash)=64) ,denom_type INT4 NOT NULL DEFAULT (1) -- 1 == RSA (for now, remove default later!) - ,age_restrictions INT4 NOT NULL DEFAULT (0) + ,age_mask INT4 NOT NULL DEFAULT (0) ,denom_pub BYTEA NOT NULL ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64) ,valid_from INT8 NOT NULL @@ -47,7 +47,7 @@ COMMENT ON TABLE denominations IS 'Main denominations table. All the valid denominations the exchange knows about.'; COMMENT ON COLUMN denominations.denom_type IS 'determines cipher type for blind signatures used with this denomination; 0 is for RSA'; -COMMENT ON COLUMN denominations.age_restrictions +COMMENT ON COLUMN denominations.age_mask IS 'bitmask with the age restrictions that are being used for this denomination; 0 if denomination does not support the use of age restrictions'; COMMENT ON COLUMN denominations.denominations_serial IS 'needed for exchange-auditor replication logic'; @@ -342,6 +342,7 @@ CREATE TABLE IF NOT EXISTS refresh_commitments (melt_serial_id BIGSERIAL -- UNIQUE ,rc BYTEA PRIMARY KEY CHECK (LENGTH(rc)=64) ,old_coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) ON DELETE CASCADE + ,h_age_commitment BYTEA CHECK(LENGTH(h_age_commitment)=32) ,old_coin_sig BYTEA NOT NULL CHECK(LENGTH(old_coin_sig)=64) ,amount_with_fee_val INT8 NOT NULL ,amount_with_fee_frac INT4 NOT NULL @@ -356,6 +357,8 @@ COMMENT ON COLUMN refresh_commitments.rc IS 'Commitment made by the client, hash over the various client inputs in the cut-and-choose protocol'; COMMENT ON COLUMN refresh_commitments.old_coin_pub IS 'Coin being melted in the refresh process.'; +COMMENT ON COLUMN refresh_commitments.h_age_commitment + IS '(optional) age commitment that was involved in the minting process of the coin, may be NULL.'; CREATE TABLE IF NOT EXISTS refresh_commitments_default PARTITION OF refresh_commitments FOR VALUES WITH (MODULUS 1, REMAINDER 0); diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index f9f0ce412..c0ff9acdf 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -231,10 +231,11 @@ prepare_statements (struct PostgresClosure *pg) ",fee_refresh_frac" ",fee_refund_val" ",fee_refund_frac" + ",age_mask" ") VALUES " "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10," - " $11, $12, $13, $14, $15, $16, $17);", - 17), + " $11, $12, $13, $14, $15, $16, $17, $18);", + 18), /* Used in #postgres_iterate_denomination_info() */ GNUNET_PQ_make_prepare ( "denomination_iterate", @@ -255,6 +256,7 @@ prepare_statements (struct PostgresClosure *pg) ",fee_refund_val" ",fee_refund_frac" ",denom_pub" + ",age_mask" " FROM denominations;", 0), /* Used in #postgres_iterate_denominations() */ @@ -278,6 +280,7 @@ prepare_statements (struct PostgresClosure *pg) ",fee_refund_val" ",fee_refund_frac" ",denom_pub" + ",age_mask" " FROM denominations" " LEFT JOIN " " denomination_revocations USING (denominations_serial);", @@ -341,6 +344,7 @@ prepare_statements (struct PostgresClosure *pg) ",fee_refresh_frac" ",fee_refund_val" ",fee_refund_frac" + ",age_mask" " FROM denominations" " WHERE denom_pub_hash=$1;", 1), @@ -830,6 +834,7 @@ prepare_statements (struct PostgresClosure *pg) ",denoms.fee_refresh_frac" ",old_coin_pub" ",old_coin_sig" + ",h_age_commitment" ",amount_with_fee_val" ",amount_with_fee_frac" ",noreveal_index" @@ -848,6 +853,7 @@ prepare_statements (struct PostgresClosure *pg) "SELECT" " denom.denom_pub" ",kc.coin_pub AS old_coin_pub" + ",h_age_commitment" ",old_coin_sig" ",amount_with_fee_val" ",amount_with_fee_frac" @@ -1842,6 +1848,7 @@ prepare_statements (struct PostgresClosure *pg) ",fee_refresh_frac" ",fee_refund_val" ",fee_refund_frac" + ",age_mask" " FROM denominations" " WHERE denom_pub_hash=$1;", 1), @@ -2069,7 +2076,6 @@ prepare_statements (struct PostgresClosure *pg) "SELECT" " denominations_serial AS serial" ",denom_type" - ",age_restrictions" ",denom_pub" ",master_sig" ",valid_from" @@ -2086,6 +2092,7 @@ prepare_statements (struct PostgresClosure *pg) ",fee_refresh_frac" ",fee_refund_val" ",fee_refund_frac" + ",age_mask" " FROM denominations" " WHERE denominations_serial > $1" " ORDER BY denominations_serial ASC;", @@ -2388,10 +2395,11 @@ prepare_statements (struct PostgresClosure *pg) ",fee_refresh_frac" ",fee_refund_val" ",fee_refund_frac" + ",age_mask" ") VALUES " "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10," - " $11, $12, $13, $14, $15, $16, $17, $18);", - 18), + " $11, $12, $13, $14, $15, $16, $17, $18, $19);", + 19), GNUNET_PQ_make_prepare ( "insert_into_table_denomination_revocations", "INSERT INTO denomination_revocations" @@ -3094,9 +3102,12 @@ postgres_insert_denomination_info ( TALER_PQ_query_param_amount_nbo (&issue->properties.fee_deposit), TALER_PQ_query_param_amount_nbo (&issue->properties.fee_refresh), TALER_PQ_query_param_amount_nbo (&issue->properties.fee_refund), + GNUNET_PQ_query_param_uint32 (&denom_pub->age_mask.mask), GNUNET_PQ_query_param_end }; + GNUNET_assert (denom_pub->age_mask.mask == issue->age_mask.mask); + GNUNET_assert (! GNUNET_TIME_absolute_is_zero ( GNUNET_TIME_timestamp_ntoh ( issue->properties.start).abs_time)); @@ -3170,6 +3181,8 @@ postgres_get_denomination_info ( &issue->properties.fee_refresh), TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("fee_refund", &issue->properties.fee_refund), + GNUNET_PQ_result_spec_uint32 ("age_mask", + &issue->age_mask.mask), GNUNET_PQ_result_spec_end }; @@ -3256,12 +3269,15 @@ domination_cb_helper (void *cls, &issue.properties.fee_refund), TALER_PQ_result_spec_denom_pub ("denom_pub", &denom_pub), + GNUNET_PQ_result_spec_uint32 ("age_mask", + &issue.age_mask.mask), GNUNET_PQ_result_spec_end }; memset (&issue.properties.master, 0, sizeof (issue.properties.master)); + if (GNUNET_OK != GNUNET_PQ_extract_result (result, rs, @@ -3270,6 +3286,13 @@ domination_cb_helper (void *cls, GNUNET_break (0); return; } + + /* Unfortunately we have to carry the age mask in both, the + * TALER_DenominationPublicKey and + * TALER_EXCHANGEDB_DenominationKeyInformationP at different times. + * Here we use _both_ so let's make sure the values are the same. */ + denom_pub.age_mask = issue.age_mask; + issue.properties.purpose.size = htonl (sizeof (struct TALER_DenominationKeyValidityPS)); issue.properties.purpose.purpose @@ -3355,10 +3378,10 @@ dominations_cb_helper (void *cls, for (unsigned int i = 0; icb (dic->cb_cls, @@ -5730,11 +5759,13 @@ postgres_ensure_coin_known (void *cls, const struct TALER_CoinPublicInfo *coin, uint64_t *known_coin_id, struct TALER_DenominationHash *denom_hash, - struct TALER_AgeHash *age_hash) + struct TALER_AgeCommitmentHash *age_hash) { struct PostgresClosure *pg = cls; enum GNUNET_DB_QueryStatus qs; bool existed; + bool is_denom_pub_hash_null = false; + bool is_age_hash_null = false; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (&coin->coin_pub), GNUNET_PQ_query_param_auto_from_type (&coin->denom_pub_hash), @@ -5742,24 +5773,22 @@ postgres_ensure_coin_known (void *cls, TALER_PQ_query_param_denom_sig (&coin->denom_sig), GNUNET_PQ_query_param_end }; - bool is_null = false; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_bool ("existed", &existed), GNUNET_PQ_result_spec_uint64 ("known_coin_id", known_coin_id), - GNUNET_PQ_result_spec_allow_null ( - GNUNET_PQ_result_spec_auto_from_type ("age_hash", - age_hash), - &is_null), GNUNET_PQ_result_spec_allow_null ( GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash", denom_hash), - &is_null), + &is_denom_pub_hash_null), + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_auto_from_type ("age_hash", + age_hash), + &is_age_hash_null), GNUNET_PQ_result_spec_end }; - GNUNET_break (GNUNET_is_zero (&coin->age_commitment_hash)); // FIXME-OEC qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "insert_known_coin", params, @@ -5779,21 +5808,24 @@ postgres_ensure_coin_known (void *cls, return TALER_EXCHANGEDB_CKS_ADDED; break; /* continued below */ } - if ( (! is_null) && - (0 != GNUNET_memcmp (age_hash, - &coin->age_commitment_hash)) ) - { - GNUNET_break (GNUNET_is_zero (age_hash)); // FIXME-OEC - GNUNET_break_op (0); - return TALER_EXCHANGEDB_CKS_AGE_CONFLICT; - } - if ( (! is_null) && - (0 != GNUNET_memcmp (denom_hash, - &coin->denom_pub_hash)) ) + + if ( (! is_denom_pub_hash_null) && + (0 != GNUNET_memcmp (&denom_hash->hash, + &coin->denom_pub_hash.hash)) ) { GNUNET_break_op (0); return TALER_EXCHANGEDB_CKS_DENOM_CONFLICT; } + + if ( (! is_age_hash_null) && + (0 != GNUNET_memcmp (age_hash, + &coin->age_commitment_hash)) ) + { + GNUNET_break (GNUNET_is_zero (age_hash)); + GNUNET_break_op (0); + return TALER_EXCHANGEDB_CKS_AGE_CONFLICT; + } + return TALER_EXCHANGEDB_CKS_PRESENT; } @@ -6019,6 +6051,7 @@ postgres_get_melt (void *cls, uint64_t *melt_serial_id) { struct PostgresClosure *pg = cls; + bool h_age_commitment_is_null; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (rc), GNUNET_PQ_query_param_end @@ -6035,6 +6068,10 @@ postgres_get_melt (void *cls, &melt->session.coin.coin_pub), GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig", &melt->session.coin_sig), + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_auto_from_type ("h_age_commitment", + &melt->session.h_age_commitment), + &h_age_commitment_is_null), TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", &melt->session.amount_with_fee), GNUNET_PQ_result_spec_uint64 ("melt_serial_id", @@ -6050,6 +6087,11 @@ postgres_get_melt (void *cls, "get_melt", params, rs); + if (h_age_commitment_is_null) + memset (&melt->session.h_age_commitment, + 0, + sizeof(melt->session.h_age_commitment)); + melt->session.rc = *rc; return qs; } @@ -8202,6 +8244,8 @@ refreshs_serial_helper_cb (void *cls, struct TALER_DenominationPublicKey denom_pub; struct TALER_CoinSpendPublicKeyP coin_pub; struct TALER_CoinSpendSignatureP coin_sig; + struct TALER_AgeCommitmentHash h_age_commitment; + bool ac_isnull; struct TALER_Amount amount_with_fee; uint32_t noreveal_index; uint64_t rowid; @@ -8209,6 +8253,10 @@ refreshs_serial_helper_cb (void *cls, struct GNUNET_PQ_ResultSpec rs[] = { TALER_PQ_result_spec_denom_pub ("denom_pub", &denom_pub), + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_auto_from_type ("h_age_commitment", + &h_age_commitment), + &ac_isnull), GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub", &coin_pub), GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig", @@ -8234,9 +8282,11 @@ refreshs_serial_helper_cb (void *cls, rsc->status = GNUNET_SYSERR; return; } + ret = rsc->cb (rsc->cb_cls, rowid, &denom_pub, + ac_isnull ? NULL : &h_age_commitment, &coin_pub, &coin_sig, &amount_with_fee, @@ -10173,6 +10223,8 @@ postgres_lookup_denomination_key ( &meta->fee_refresh), TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund", &meta->fee_refund), + GNUNET_PQ_result_spec_uint32 ("age_mask", + &meta->age_mask.mask), GNUNET_PQ_result_spec_end }; @@ -10216,6 +10268,7 @@ postgres_add_denomination_key ( TALER_PQ_query_param_amount (&meta->fee_deposit), TALER_PQ_query_param_amount (&meta->fee_refresh), TALER_PQ_query_param_amount (&meta->fee_refund), + GNUNET_PQ_query_param_uint32 (&meta->age_mask.mask), GNUNET_PQ_query_param_end }; diff --git a/src/exchangedb/test_exchangedb.c b/src/exchangedb/test_exchangedb.c index cca7c3f47..ffd904f20 100644 --- a/src/exchangedb/test_exchangedb.c +++ b/src/exchangedb/test_exchangedb.c @@ -459,6 +459,7 @@ static unsigned int auditor_row_cnt; * @param cls closure * @param rowid unique serial ID for the refresh session in our DB * @param denom_pub denomination of the @a coin_pub + * @param h_age_commitment hash of age commitment that went into the minting, may be NULL * @param coin_pub public key of the coin * @param coin_sig signature from the coin * @param amount_with_fee amount that was deposited including fee @@ -471,6 +472,8 @@ static enum GNUNET_GenericReturnValue audit_refresh_session_cb (void *cls, uint64_t rowid, const struct TALER_DenominationPublicKey *denom_pub, + const struct + TALER_AgeCommitmentHash *h_age_commitment, const struct TALER_CoinSpendPublicKeyP *coin_pub, const struct TALER_CoinSpendSignatureP *coin_sig, const struct TALER_Amount *amount_with_fee, @@ -1467,8 +1470,8 @@ run (void *cls) { struct TALER_PlanchetDetail pd; struct TALER_CoinSpendPublicKeyP coin_pub; - struct TALER_AgeHash age_hash; - struct TALER_AgeHash *p_ah[2] = {NULL, &age_hash}; + struct TALER_AgeCommitmentHash age_hash; + struct TALER_AgeCommitmentHash *p_ah[2] = {NULL, &age_hash}; /* Call TALER_denom_blind()/TALER_denom_sign_blinded() twice, once without * age_hash, once with age_hash */ @@ -1576,7 +1579,7 @@ run (void *cls) deadline = GNUNET_TIME_timestamp_get (); { struct TALER_DenominationHash dph; - struct TALER_AgeHash agh; + struct TALER_AgeCommitmentHash agh; FAILIF (TALER_EXCHANGEDB_CKS_ADDED != plugin->ensure_coin_known (plugin->cls, @@ -1819,7 +1822,7 @@ run (void *cls) uint64_t new_known_coin_id; struct TALER_CoinPublicInfo new_coin; struct TALER_DenominationHash dph; - struct TALER_AgeHash agh; + struct TALER_AgeCommitmentHash agh; bool recoup_ok; bool internal_failure; @@ -2171,7 +2174,7 @@ run (void *cls) { uint64_t known_coin_id; struct TALER_DenominationHash dph; - struct TALER_AgeHash agh; + struct TALER_AgeCommitmentHash agh; FAILIF (TALER_EXCHANGEDB_CKS_ADDED != plugin->ensure_coin_known (plugin->cls, diff --git a/src/extensions/extension_age_restriction.c b/src/extensions/extension_age_restriction.c index a9ffb7f1a..28b2dbb1e 100644 --- a/src/extensions/extension_age_restriction.c +++ b/src/extensions/extension_age_restriction.c @@ -23,6 +23,19 @@ #include "taler_extensions.h" #include "stdint.h" +/** + * Carries all the information we need for age restriction + */ +struct age_restriction_config +{ + struct TALER_AgeMask mask; + size_t num_groups; +}; + +/** + * Global config for this extension + */ +static struct age_restriction_config _config = {0}; /** * @param groups String representation of the age groups. Must be of the form @@ -146,6 +159,9 @@ age_restriction_disable ( json_decref (this->config_json); this->config_json = NULL; } + + _config.mask.mask = 0; + _config.num_groups = 0; } @@ -197,7 +213,6 @@ age_restriction_load_taler_config ( mask.mask = TALER_EXTENSION_AGE_RESTRICTION_DEFAULT_AGE_MASK; - ret = GNUNET_OK; if (groups != NULL) @@ -208,7 +223,19 @@ age_restriction_load_taler_config ( } if (GNUNET_OK == ret) - this->config = (void *) (size_t) mask.mask; + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "setting age mask to %x with #groups: %d\n", mask.mask, + __builtin_popcount (mask.mask) - 1); + _config.mask.mask = mask.mask; + _config.num_groups = __builtin_popcount (mask.mask) - 1; /* no underflow, first bit always set */ + this->config = &_config; + + /* Note: we do now have _config set, however this->config_json is NOT set, + * i.e. the extension is not yet active! For age restriction to become + * active, load_json_config must have been called. */ + } + GNUNET_free (groups); return ret; @@ -223,12 +250,12 @@ age_restriction_load_taler_config ( static enum GNUNET_GenericReturnValue age_restriction_load_json_config ( struct TALER_Extension *this, - json_t *config) + json_t *jconfig) { struct TALER_AgeMask mask = {0}; enum GNUNET_GenericReturnValue ret; - ret = TALER_JSON_parse_agemask (config, &mask); + ret = TALER_JSON_parse_age_groups (jconfig, &mask); if (GNUNET_OK != ret) return ret; @@ -239,16 +266,28 @@ age_restriction_load_json_config ( if (TALER_Extension_AgeRestriction != this->type) return GNUNET_SYSERR; - if (NULL != this->config) - GNUNET_free (this->config); + _config.mask.mask = mask.mask; + _config.num_groups = 0; - this->config = GNUNET_malloc (sizeof(struct TALER_AgeMask)); - GNUNET_memcpy (this->config, &mask, sizeof(struct TALER_AgeMask)); + if (mask.mask > 0) + { + /* if the mask is not zero, the first bit MUST be set */ + if (0 == (mask.mask & 1)) + return GNUNET_SYSERR; + + _config.num_groups = __builtin_popcount (mask.mask) - 1; + } + + this->config = &_config; if (NULL != this->config_json) json_decref (this->config_json); - this->config_json = config; + this->config_json = jconfig; + + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "loaded new age restriction config with age groups: %s\n", + TALER_age_mask_to_string (&mask)); return GNUNET_OK; } @@ -263,7 +302,6 @@ json_t * age_restriction_config_to_json ( const struct TALER_Extension *this) { - struct TALER_AgeMask mask; char *mask_str; json_t *conf; @@ -275,8 +313,7 @@ age_restriction_config_to_json ( return json_copy (this->config_json); } - mask.mask = (uint32_t) (size_t) this->config; - mask_str = TALER_age_mask_to_string (&mask); + mask_str = TALER_age_mask_to_string (&_config.mask); conf = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("age_groups", mask_str) ); @@ -298,7 +335,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); } @@ -318,4 +355,50 @@ struct TALER_Extension _extension_age_restriction = { .load_taler_config = &age_restriction_load_taler_config, }; +bool +TALER_extensions_age_restriction_is_configured () +{ + return (0 != _config.mask.mask); +} + + +struct TALER_AgeMask +TALER_extensions_age_restriction_ageMask () +{ + return _config.mask; +} + + +size_t +TALER_extensions_age_restriction_num_groups () +{ + return _config.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 */ diff --git a/src/extensions/extensions.c b/src/extensions/extensions.c index 55d970c57..516c56a43 100644 --- a/src/extensions/extensions.c +++ b/src/extensions/extensions.c @@ -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; @@ -330,4 +334,16 @@ TALER_extensions_load_json_config ( } +bool +TALER_extensions_age_restriction_is_enabled () +{ + const struct TALER_Extension *age = + TALER_extensions_get_by_type (TALER_Extension_AgeRestriction); + + return (NULL != age && + NULL != age->config_json && + TALER_extensions_age_restriction_is_configured ()); +} + + /* end of extensions.c */ diff --git a/src/include/taler_crypto_lib.h b/src/include/taler_crypto_lib.h index 6a805b645..b755d26b5 100644 --- a/src/include/taler_crypto_lib.h +++ b/src/include/taler_crypto_lib.h @@ -280,37 +280,6 @@ struct TALER_MasterSignatureP struct GNUNET_CRYPTO_EddsaSignature eddsa_signature; }; -/* - * @brief Type of a list of age groups, represented as bit mask. - * - * The bits set in the mask mark the edges at the beginning of a next age - * group. F.e. for the age groups - * 0-7, 8-9, 10-11, 12-14, 14-15, 16-17, 18-21, 21-* - * the following bits are set: - * - * 31 24 16 8 0 - * | | | | | - * oooooooo oo1oo1o1 o1o1o1o1 ooooooo1 - * - * A value of 0 means that the exchange does not support the extension for - * age-restriction. - */ -struct TALER_AgeMask -{ - uint32_t mask; -}; - -/** - * @brief Age restriction commitment of a coin. - */ -struct TALER_AgeHash -{ - /** - * The commitment is a SHA-256 hash code. - */ - struct GNUNET_ShortHashCode shash; -}; - /** * @brief Type of public keys for Taler coins. The same key material is used @@ -338,6 +307,29 @@ struct TALER_CoinSpendPrivateKeyP struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv; }; +/** + * @brief Type of private keys for age commitment in coins. + */ +struct TALER_AgeCommitmentPrivateKeyP +{ + /** + * Taler uses EdDSA for coins when signing age verification attestation. + */ + struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv; +}; + + +/** + * @brief Type of public keys for age commitment in coins. + */ +struct TALER_AgeCommitmentPublicKeyP +{ + /** + * Taler uses EdDSA for coins when signing age verification attestation. + */ + struct GNUNET_CRYPTO_EddsaPublicKey eddsa_pub; +}; + /** * @brief Type of signatures made with Taler coins. @@ -635,6 +627,46 @@ struct TALER_BlindedDenominationSignature }; +/* *************** Age Restriction *********************************** */ + +/* + * @brief Type of a list of age groups, represented as bit mask. + * + * The bits set in the mask mark the edges at the beginning of a next age + * group. F.e. for the age groups + * 0-7, 8-9, 10-11, 12-14, 14-15, 16-17, 18-21, 21-* + * the following bits are set: + * + * 31 24 16 8 0 + * | | | | | + * oooooooo oo1oo1o1 o1o1o1o1 ooooooo1 + * + * A value of 0 means that the exchange does not support the extension for + * age-restriction. + */ +struct TALER_AgeMask +{ + uint32_t mask; +}; + +/** + * @brief Age commitment of a coin. + */ +struct TALER_AgeCommitmentHash +{ + /** + * The commitment is a SHA-256 hash code. + */ + struct GNUNET_ShortHashCode shash; +}; + +extern const struct TALER_AgeCommitmentHash TALER_ZeroAgeCommitmentHash; +#define TALER_AgeCommitmentHash_isNullOrZero(ph) ((NULL == ph) || \ + (0 == memcmp (ph, \ + & \ + TALER_ZeroAgeCommitmentHash, \ + sizeof(struct \ + TALER_AgeCommitmentHash)))) /** * @brief Type of public signing keys for verifying blindly signed coins. @@ -712,9 +744,10 @@ struct TALER_CoinPublicInfo struct TALER_DenominationHash denom_pub_hash; /** - * Hash of the age commitment. + * Hash of the age commitment. If no age commitment was provided, it must be + * set to all zeroes. */ - struct TALER_AgeHash age_commitment_hash; + struct TALER_AgeCommitmentHash age_commitment_hash; /** * (Unblinded) signature over @e coin_pub with @e denom_pub, @@ -824,7 +857,7 @@ TALER_denom_sig_free (struct TALER_DenominationSignature *denom_sig); enum GNUNET_GenericReturnValue TALER_denom_blind (const struct TALER_DenominationPublicKey *dk, const union TALER_DenominationBlindingKeyP *coin_bks, - const struct TALER_AgeHash *age_commitment_hash, + const struct TALER_AgeCommitmentHash *age_commitment_hash, const struct TALER_CoinSpendPublicKeyP *coin_pub, struct TALER_CoinPubHash *c_hash, void **coin_ev, @@ -1024,7 +1057,7 @@ TALER_coin_ev_hash (const void *coin_ev, */ void TALER_coin_pub_hash (const struct TALER_CoinSpendPublicKeyP *coin_pub, - const struct TALER_AgeHash *age_commitment_hash, + const struct TALER_AgeCommitmentHash *age_commitment_hash, struct TALER_CoinPubHash *coin_h); @@ -1110,8 +1143,9 @@ struct TALER_FreshCoin struct TALER_CoinSpendPrivateKeyP coin_priv; /** - * FIXME-Oec: Age-verification vector, as pointer: Dyn alloc! + * Optional hash of an age commitment bound to this coin, maybe NULL. */ + const struct TALER_AgeCommitmentHash *h_age_commitment; }; @@ -1232,6 +1266,7 @@ TALER_planchet_setup_random (struct TALER_PlanchetSecretsP *ps); * * @param dk denomination key for the coin to be created * @param ps secret planchet internals (for #TALER_planchet_to_coin) + * @param ach (optional) hash of age commitment to bind to this coin, maybe NULL * @param[out] c_hash set to the hash of the public key of the coin (needed later) * @param[out] pd set to the planchet detail for TALER_MERCHANT_tip_pickup() and * other withdraw operations @@ -1240,6 +1275,7 @@ TALER_planchet_setup_random (struct TALER_PlanchetSecretsP *ps); enum GNUNET_GenericReturnValue TALER_planchet_prepare (const struct TALER_DenominationPublicKey *dk, const struct TALER_PlanchetSecretsP *ps, + const struct TALER_AgeCommitmentHash *ach, struct TALER_CoinPubHash *c_hash, struct TALER_PlanchetDetail *pd); @@ -1251,6 +1287,7 @@ TALER_planchet_prepare (const struct TALER_DenominationPublicKey *dk, * @param dk denomination key, must match what was given to #TALER_planchet_prepare() * @param blind_sig blind signature from the exchange * @param ps secrets from #TALER_planchet_prepare() + * @param ach (optional) hash of age commitment that is bound to this coin, maybe NULL * @param c_hash hash of the coin's public key for verification of the signature * @param[out] coin set to the details of the fresh coin * @return #GNUNET_OK on success @@ -1260,6 +1297,7 @@ TALER_planchet_to_coin ( const struct TALER_DenominationPublicKey *dk, const struct TALER_BlindedDenominationSignature *blind_sig, const struct TALER_PlanchetSecretsP *ps, + const struct TALER_AgeCommitmentHash *ach, const struct TALER_CoinPubHash *c_hash, struct TALER_FreshCoin *coin); @@ -1682,6 +1720,7 @@ TALER_exchange_deposit_confirm_verify ( * @param deposit_fee the deposit fee we expect to pay * @param h_wire hash of the merchant’s account details * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the exchange) + * @param h_age_commitment hash over the age commitment, if applicable to the denomination (maybe NULL) * @param h_extensions hash over the extensions * @param h_denom_pub hash of the coin denomination's public key * @param coin_priv coin’s private key @@ -1696,6 +1735,7 @@ TALER_wallet_deposit_sign ( const struct TALER_Amount *deposit_fee, const struct TALER_MerchantWireHash *h_wire, const struct TALER_PrivateContractHash *h_contract_terms, + const struct TALER_AgeCommitmentHash *h_age_commitment, const struct TALER_ExtensionContractHash *h_extensions, const struct TALER_DenominationHash *h_denom_pub, struct GNUNET_TIME_Timestamp wallet_timestamp, @@ -1712,6 +1752,7 @@ TALER_wallet_deposit_sign ( * @param deposit_fee the deposit fee we expect to pay * @param h_wire hash of the merchant’s account details * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the exchange) + * @param h_age_commitment hash over the age commitment (maybe all zeroes, if not applicable to the denomination) * @param h_extensions hash over the extensions * @param h_denom_pub hash of the coin denomination's public key * @param wallet_timestamp timestamp when the contract was finalized, must not be too far in the future @@ -1727,6 +1768,7 @@ TALER_wallet_deposit_verify ( const struct TALER_Amount *deposit_fee, const struct TALER_MerchantWireHash *h_wire, const struct TALER_PrivateContractHash *h_contract_terms, + const struct TALER_AgeCommitmentHash *h_commitment_hash, const struct TALER_ExtensionContractHash *h_extensions, const struct TALER_DenominationHash *h_denom_pub, struct GNUNET_TIME_Timestamp wallet_timestamp, @@ -1763,6 +1805,7 @@ TALER_wallet_melt_sign ( * @param melt_fee the melt fee we expect to pay * @param rc refresh session we are committed to * @param h_denom_pub hash of the coin denomination's public key + * @param h_age_commitment hash of the age commitment (may be NULL) * @param coin_pub coin’s public key * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_MELT * @return #GNUNET_OK if the signature is valid @@ -1773,6 +1816,7 @@ TALER_wallet_melt_verify ( const struct TALER_Amount *melt_fee, const struct TALER_RefreshCommitmentP *rc, const struct TALER_DenominationHash *h_denom_pub, + const struct TALER_AgeCommitmentHash *h_age_commitment, const struct TALER_CoinSpendPublicKeyP *coin_pub, const struct TALER_CoinSpendSignatureP *coin_sig); @@ -1803,6 +1847,7 @@ TALER_wallet_link_sign (const struct TALER_DenominationHash *h_denom_pub, * @param transfer_pub transfer public key * @param h_coin_ev hash of the coin envelope * @param old_coin_pub old coin key that the link signature is for + * @param h_age_commitment hash of age commitment. Maybe NULL, if not applicable. * @param coin_sig resulting signature * @return #GNUNET_OK if the signature is valid */ @@ -1812,6 +1857,7 @@ TALER_wallet_link_verify ( const struct TALER_TransferPublicKeyP *transfer_pub, const struct TALER_BlindedCoinHash *h_coin_ev, const struct TALER_CoinSpendPublicKeyP *old_coin_pub, + const struct TALER_AgeCommitmentHash *h_age_commitment, const struct TALER_CoinSpendSignatureP *coin_sig); @@ -2557,5 +2603,100 @@ TALER_exchange_offline_extension_config_hash_verify ( const struct TALER_MasterSignatureP *master_sig ); +/* + * @brief Representation of an age commitment: one public key per age group. + * + * The number of keys must be be the same as the number of bits set in the + * corresponding age mask. + */ +struct TALER_AgeCommitment +{ + + /* The age mask defines the age groups that were a parameter during the + * generation of this age commitment */ + struct TALER_AgeMask mask; + + /* The number of public keys, which must be the same as the number of + * groups in the mask. + */ + size_t num_pub; + + /* The list of #num_pub public keys. In must have same size as the number of + * age groups defined in the mask. + * + * A hash of this list is the hashed commitment that goes into FDC + * calculation during the withdraw and refresh operations for new coins. That + * way, the particular age commitment becomes mandatory and bound to a coin. + * + * The list has been allocated via GNUNET_malloc. + */ + struct TALER_AgeCommitmentPublicKeyP *pub; + + /* The number of private keys, which must be at most num_pub_keys. One minus + * this number corresponds to the largest age group that is supported with + * this age commitment. + */ + size_t num_priv; + + /* List of #num_priv private keys. + * + * Note that the list can be _smaller_ than the corresponding list of public + * keys. In that case, the wallet can sign off only for a subset of the age + * groups. + * + * The list has been allocated via GNUNET_malloc. + */ + struct TALER_AgeCommitmentPrivateKeyP *priv; +}; + +/* + * @brief Generates a hash of the public keys in the age commitment. + * + * @param commitment the age commitment - one public key per age group + * @param[out] hash resulting hash + */ +void +TALER_age_commitment_hash ( + const struct TALER_AgeCommitment *commitment, + struct TALER_AgeCommitmentHash *hash); + +/* + * @brief Generates an age commitent for the given age. + * + * @param mask The age mask the defines the age groups + * @param age The actual age for which an age commitment is generated + * @param seed The seed that goes into the key generation. MUST be choosen uniformly random. + * @param commitment[out] The generated age commitment, ->priv and ->pub allocated via GNUNET_malloc on success + * @return GNUNET_OK on success, GNUNET_SYSERR otherwise + */ +enum GNUNET_GenericReturnValue +TALER_age_restriction_commit ( + const struct TALER_AgeMask *mask, + const uint8_t age, + const uint32_t seed, + struct TALER_AgeCommitment *commitment); + +/* + * @brief Derives another, equivalent age commitment for a given one. + * + * @param orig Original age commitment + * @param seed Used to move the points on the elliptic curve in order to generate another, equivalent commitment. + * @param derived[out] The resulting age commitment, ->priv and ->pub allocated via GNUNET_malloc on success. + * @return GNUNET_OK on success, GNUNET_SYSERR otherwise + */ +enum GNUNET_GenericReturnValue +TALER_age_commitment_derive ( + const struct TALER_AgeCommitment *orig, + const uint32_t seed, + struct TALER_AgeCommitment *derived); + +/* + * @brief helper function to free memory inside a struct TALER_AgeCommitment + * @param cmt the commitment from which internal memory should be freed. Note + * that cmt itself is NOT freed! + */ +void +TALER_age_restriction_commitment_free_inside ( + struct TALER_AgeCommitment *cmt); #endif diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h index caa61c5f1..8e361e5ec 100644 --- a/src/include/taler_exchange_service.h +++ b/src/include/taler_exchange_service.h @@ -159,11 +159,6 @@ struct TALER_EXCHANGE_DenomPublicKey * revoked by the exchange. */ bool revoked; - - /** - * Is the denomination age-restricted? - */ - bool age_restricted; }; @@ -785,6 +780,7 @@ TALER_EXCHANGE_wire_cancel (struct TALER_EXCHANGE_WireHandle *wh); * @param h_extensions hash over the extensions * @param h_denom_pub hash of the coin denomination's public key * @param coin_priv coin’s private key + * @param age_commitment age commitment that went into the making of the coin, might be NULL * @param wallet_timestamp timestamp when the contract was finalized, must not be too far in the future * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests) * @param refund_deadline date until which the merchant can issue a refund to the customer via the exchange (can be zero if refunds are not allowed); must not be after the @a wire_deadline @@ -799,6 +795,7 @@ TALER_EXCHANGE_deposit_permission_sign ( const struct TALER_ExtensionContractHash *h_extensions, const struct TALER_DenominationHash *h_denom_pub, const struct TALER_CoinSpendPrivateKeyP *coin_priv, + const struct TALER_AgeCommitment *age_commitment, struct GNUNET_TIME_Timestamp wallet_timestamp, const struct TALER_MerchantPublicKeyP *merchant_pub, struct GNUNET_TIME_Timestamp refund_deadline, @@ -924,6 +921,7 @@ TALER_EXCHANGE_deposit ( const char *merchant_payto_uri, const struct TALER_WireSalt *wire_salt, const struct TALER_PrivateContractHash *h_contract_terms, + const struct TALER_AgeCommitmentHash *h_age_commitment, const json_t *extension_details, const struct TALER_CoinSpendPublicKeyP *coin_pub, const struct TALER_DenominationSignature *denom_sig, @@ -1359,6 +1357,7 @@ typedef void * @param reserve_priv private key of the reserve to withdraw from * @param ps secrets of the planchet * caller must have committed this value to disk before the call (with @a pk) + * @param ach (optional) hash of the age commitment that should be bound to this coin. Maybe NULL. * @param res_cb the callback to call when the final result for this request is available * @param res_cb_cls closure for @a res_cb * @return NULL @@ -1371,6 +1370,7 @@ TALER_EXCHANGE_withdraw ( const struct TALER_EXCHANGE_DenomPublicKey *pk, const struct TALER_ReservePrivateKeyP *reserve_priv, const struct TALER_PlanchetSecretsP *ps, + const struct TALER_AgeCommitmentHash *ach, TALER_EXCHANGE_WithdrawCallback res_cb, void *res_cb_cls); @@ -1479,6 +1479,8 @@ TALER_EXCHANGE_withdraw2_cancel (struct TALER_EXCHANGE_Withdraw2Handle *wh); * @param melt_pk denomination key information * record corresponding to the @a melt_sig * validity of the keys + * @param age_commitment (optional) age commitment that went into the original + * coin. Maybe NULL, if no age commitment was provided. * @param fresh_pks_len length of the @a pks array * @param fresh_pks array of @a pks_len denominations of fresh coins to create * @return NULL @@ -1493,6 +1495,7 @@ TALER_EXCHANGE_refresh_prepare ( const struct TALER_Amount *melt_amount, const struct TALER_DenominationSignature *melt_sig, const struct TALER_EXCHANGE_DenomPublicKey *melt_pk, + const struct TALER_AgeCommitment *age_commitment, unsigned int fresh_pks_len, const struct TALER_EXCHANGE_DenomPublicKey *fresh_pks); diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index cd68e1edb..2bbf65b01 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -70,6 +70,12 @@ struct TALER_EXCHANGEDB_DenominationKeyInformationP * Signed properties of the denomination key. */ struct TALER_DenominationKeyValidityPS properties; + + /** + * If denomination was setup for age restriction, non-zero age mask. + * Note that the mask is not part of the signature. + */ + struct TALER_AgeMask age_mask; }; @@ -295,7 +301,7 @@ struct TALER_EXCHANGEDB_TableData struct { struct TALER_CoinSpendPublicKeyP coin_pub; - struct TALER_AgeHash age_hash; + struct TALER_AgeCommitmentHash age_hash; uint64_t denominations_serial; struct TALER_DenominationSignature denom_sig; } known_coins; @@ -643,7 +649,7 @@ struct TALER_EXCHANGEDB_DenominationKeyMetaData * A value of 0 means that the denomination does not support the extension for * age-restriction. */ - struct TALER_AgeMask age_restrictions; + struct TALER_AgeMask age_mask; }; @@ -1260,6 +1266,13 @@ struct TALER_EXCHANGEDB_Refresh */ struct TALER_CoinSpendSignatureP coin_sig; + /** + * Hash of the age commitment used to sign the coin, if age restriction was + * applicable to the denomination. May be all zeroes if no age restriction + * applies. + */ + struct TALER_AgeCommitmentHash h_age_commitment; + /** * Refresh commitment this coin is melted into. */ @@ -1305,6 +1318,13 @@ struct TALER_EXCHANGEDB_MeltListEntry */ struct TALER_DenominationHash h_denom_pub; + /** + * Hash of the age commitment used to sign the coin, if age restriction was + * applicable to the denomination. May be all zeroes if no age restriction + * applies. + */ + struct TALER_AgeCommitmentHash h_age_commitment; + /** * How much value is being melted? This amount includes the fees, * so the final amount contributed to the melt is this value minus @@ -1585,6 +1605,7 @@ typedef enum GNUNET_GenericReturnValue * @param cls closure * @param rowid unique serial ID for the refresh session in our DB * @param denom_pub denomination public key of @a coin_pub + * @param h_age_commitment age commitment that went into the signing of the coin, may be NULL * @param coin_pub public key of the coin * @param coin_sig signature from the coin * @param amount_with_fee amount that was deposited including fee @@ -1597,6 +1618,7 @@ typedef enum GNUNET_GenericReturnValue void *cls, uint64_t rowid, const struct TALER_DenominationPublicKey *denom_pub, + const struct TALER_AgeCommitmentHash *h_age_commitment, const struct TALER_CoinSpendPublicKeyP *coin_pub, const struct TALER_CoinSpendSignatureP *coin_sig, const struct TALER_Amount *amount_with_fee, @@ -2735,7 +2757,7 @@ struct TALER_EXCHANGEDB_Plugin const struct TALER_CoinPublicInfo *coin, uint64_t *known_coin_id, struct TALER_DenominationHash *denom_pub_hash, - struct TALER_AgeHash *age_hash); + struct TALER_AgeCommitmentHash *age_hash); /** diff --git a/src/include/taler_extensions.h b/src/include/taler_extensions.h index f00f3ed56..b7b93e178 100644 --- a/src/include/taler_extensions.h +++ b/src/include/taler_extensions.h @@ -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 @@ -221,6 +232,45 @@ char * TALER_age_mask_to_string ( const struct TALER_AgeMask *mask); +/** + * Returns true when age restriction is configured and enabled. + */ +bool +TALER_extensions_age_restriction_is_enabled (); + +/** + * Returns true when age restriction is configured (might not be _enabled_, + * though). + */ +bool +TALER_extensions_age_restriction_is_configured (); + +/** + * Returns the currently set age mask. Note that even if age restriction is + * not enabled, the age mask might be have a non-zero value. + */ +struct TALER_AgeMask +TALER_extensions_age_restriction_ageMask (); + + +/** + * Returns the amount of age groups defined. 0 means no 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 diff --git a/src/include/taler_json_lib.h b/src/include/taler_json_lib.h index 2a101d269..df95e042b 100644 --- a/src/include/taler_json_lib.h +++ b/src/include/taler_json_lib.h @@ -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": }. - * - * @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 */ diff --git a/src/include/taler_signatures.h b/src/include/taler_signatures.h index 3ad1121ca..7ea01f211 100644 --- a/src/include/taler_signatures.h +++ b/src/include/taler_signatures.h @@ -414,6 +414,11 @@ struct TALER_LinkDataPS */ struct TALER_TransferPublicKeyP transfer_pub; + /** + * Hash of the age commitment, if applicable. Can be all zero + */ + struct TALER_AgeCommitmentHash h_age_commitment; + /** * Hash of the blinded new coin. */ @@ -478,6 +483,12 @@ struct TALER_DepositRequestPS */ struct TALER_PrivateContractHash h_contract_terms GNUNET_PACKED; + /** + * Hash over the age commitment that went into the coin. Maybe all zero, if + * age commitment isn't applicable to the denomination. + */ + struct TALER_AgeCommitmentHash h_age_commitment GNUNET_PACKED; + /** * Hash over extension attributes shared with the exchange. */ @@ -711,6 +722,13 @@ struct TALER_RefreshMeltCoinAffirmationPS */ struct TALER_DenominationHash h_denom_pub GNUNET_PACKED; + /** + * If age commitment was provided during the withdrawal of the coin, this is + * the hash of the age commitment vector. It must be all zeroes if no age + * commitment was provided. + */ + struct TALER_AgeCommitmentHash h_age_commitment GNUNET_PACKED; + /** * How much of the value of the coin should be melted? This amount * includes the fees, so the final amount contributed to the melt is diff --git a/src/include/taler_testing_lib.h b/src/include/taler_testing_lib.h index 20e3145f0..f40232e2f 100644 --- a/src/include/taler_testing_lib.h +++ b/src/include/taler_testing_lib.h @@ -66,11 +66,13 @@ TALER_TESTING_make_wire_details (const char *payto); * * @param keys array of keys to search * @param amount coin value to look for + * @param age_restricted must the denomination be age restricted? * @return NULL if no matching key was found */ const struct TALER_EXCHANGE_DenomPublicKey * TALER_TESTING_find_pk (const struct TALER_EXCHANGE_Keys *keys, - const struct TALER_Amount *amount); + const struct TALER_Amount *amount, + bool age_restricted); /** @@ -1277,6 +1279,7 @@ TALER_TESTING_cmd_exec_transfer (const char *label, * @param label command label. * @param reserve_reference command providing us with a reserve to withdraw from * @param amount how much we withdraw. + * @param age if > 0, age restriction applies * @param expected_response_code which HTTP response code * we expect from the exchange. * @return the withdraw command to be executed by the interpreter. @@ -1285,6 +1288,7 @@ struct TALER_TESTING_Command TALER_TESTING_cmd_withdraw_amount (const char *label, const char *reserve_reference, const char *amount, + uint8_t age, unsigned int expected_response_code); @@ -1297,6 +1301,7 @@ TALER_TESTING_cmd_withdraw_amount (const char *label, * @param label command label. * @param reserve_reference command providing us with a reserve to withdraw from * @param amount how much we withdraw. + * @param age if > 0, age restriction applies. * @param coin_ref reference to (withdraw/reveal) command of a coin * from which we should re-use the private key * @param expected_response_code which HTTP response code @@ -1308,6 +1313,7 @@ TALER_TESTING_cmd_withdraw_amount_reuse_key ( const char *label, const char *reserve_reference, const char *amount, + uint8_t age, const char *coin_ref, unsigned int expected_response_code); @@ -2138,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 @@ -2442,8 +2461,8 @@ TALER_TESTING_get_trait (const struct TALER_TESTING_Trait *traits, */ #define TALER_TESTING_SIMPLE_TRAITS(op) \ op (bank_row, const uint64_t) \ - op (reserve_priv, const struct TALER_ReservePrivateKeyP) \ - op (reserve_pub, const struct TALER_ReservePublicKeyP) \ + op (reserve_priv, const struct TALER_ReservePrivateKeyP) \ + op (reserve_pub, const struct TALER_ReservePublicKeyP) \ op (merchant_priv, const struct TALER_MerchantPrivateKeyP) \ op (merchant_pub, const struct TALER_MerchantPublicKeyP) \ op (merchant_sig, const struct TALER_MerchantSignatureP) \ @@ -2456,8 +2475,8 @@ TALER_TESTING_get_trait (const struct TALER_TESTING_Trait *traits, op (exchange_bank_account_url, const char *) \ op (taler_uri, const char *) \ op (payto_uri, const char *) \ - op (kyc_url, const char *) \ - op (web_url, const char *) \ + op (kyc_url, const char *) \ + op (web_url, const char *) \ op (row, const uint64_t) \ op (payment_target_uuid, const uint64_t) \ op (array_length, const unsigned int) \ @@ -2482,6 +2501,8 @@ TALER_TESTING_get_trait (const struct TALER_TESTING_Trait *traits, #define TALER_TESTING_INDEXED_TRAITS(op) \ op (denom_pub, const struct TALER_EXCHANGE_DenomPublicKey) \ op (denom_sig, const struct TALER_DenominationSignature) \ + op (age_commitment, struct TALER_AgeCommitment) \ + op (h_age_commitment, struct TALER_AgeCommitmentHash) \ op (coin_priv, const struct TALER_CoinSpendPrivateKeyP) \ op (coin_pub, const struct TALER_CoinSpendPublicKeyP) \ op (absolute_time, const struct GNUNET_TIME_Absolute) \ diff --git a/src/json/json_helper.c b/src/json/json_helper.c index 1942d09bd..949354e24 100644 --- a/src/json/json_helper.c +++ b/src/json/json_helper.c @@ -659,36 +659,4 @@ TALER_JSON_spec_i18n_str (const char *name, } -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 */ diff --git a/src/lib/exchange_api_common.c b/src/lib/exchange_api_common.c index 399eb280a..37d359573 100644 --- a/src/lib/exchange_api_common.c +++ b/src/lib/exchange_api_common.c @@ -482,6 +482,7 @@ TALER_EXCHANGE_verify_coin_history ( struct TALER_MerchantPublicKeyP merchant_pub; struct GNUNET_TIME_Timestamp refund_deadline = {0}; struct TALER_CoinSpendSignatureP sig; + struct TALER_AgeCommitmentHash *hac = NULL; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_fixed_auto ("coin_sig", &sig), @@ -516,6 +517,7 @@ TALER_EXCHANGE_verify_coin_history ( &fee, &h_wire, &h_contract_terms, + hac, NULL /* h_extensions! */, h_denom_pub, wallet_timestamp, @@ -548,6 +550,7 @@ TALER_EXCHANGE_verify_coin_history ( { struct TALER_CoinSpendSignatureP sig; struct TALER_RefreshCommitmentP rc; + struct TALER_AgeCommitmentHash h_age_commitment = {0}; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_fixed_auto ("coin_sig", &sig), @@ -555,6 +558,9 @@ TALER_EXCHANGE_verify_coin_history ( &rc), GNUNET_JSON_spec_fixed_auto ("h_denom_pub", h_denom_pub), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("h_age_commitment", + &h_age_commitment)), TALER_JSON_spec_amount_any ("melt_fee", &fee), GNUNET_JSON_spec_end () @@ -568,6 +574,7 @@ TALER_EXCHANGE_verify_coin_history ( GNUNET_break_op (0); return GNUNET_SYSERR; } + if (NULL != dk) { /* check that melt fee matches our expectations from /keys! */ @@ -582,16 +589,25 @@ TALER_EXCHANGE_verify_coin_history ( return GNUNET_SYSERR; } } - if (GNUNET_OK != - TALER_wallet_melt_verify (&amount, - &fee, - &rc, - h_denom_pub, - coin_pub, - &sig)) + { - GNUNET_break_op (0); - return GNUNET_SYSERR; + const struct TALER_AgeCommitmentHash *ahc = &h_age_commitment; + + if (TALER_AgeCommitmentHash_isNullOrZero (ahc)) + ahc = NULL; + + if (GNUNET_OK != + TALER_wallet_melt_verify (&amount, + &fee, + &rc, + h_denom_pub, + ahc, + coin_pub, + &sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } } add = GNUNET_YES; } diff --git a/src/lib/exchange_api_deposit.c b/src/lib/exchange_api_deposit.c index de67bc5f2..153f46505 100644 --- a/src/lib/exchange_api_deposit.c +++ b/src/lib/exchange_api_deposit.c @@ -462,6 +462,7 @@ handle_deposit_finished (void *cls, * @param h_wire hash of the merchant’s account details * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the exchange) * @param coin_pub coin’s public key + * @param h_age_commitment coin’s hash of age commitment, might be NULL * @param denom_sig exchange’s unblinded signature of the coin * @param denom_pub denomination key with which the coin is signed * @param denom_pub_hash hash of @a denom_pub @@ -478,6 +479,7 @@ verify_signatures (const struct TALER_EXCHANGE_DenomPublicKey *dki, const struct TALER_PrivateContractHash *h_contract_terms, const struct TALER_ExtensionContractHash *ech, const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_AgeCommitmentHash *h_age_commitment, const struct TALER_DenominationSignature *denom_sig, const struct TALER_DenominationPublicKey *denom_pub, const struct TALER_DenominationHash *denom_pub_hash, @@ -491,6 +493,7 @@ verify_signatures (const struct TALER_EXCHANGE_DenomPublicKey *dki, &dki->fee_deposit, h_wire, h_contract_terms, + h_age_commitment, ech, denom_pub_hash, timestamp, @@ -514,8 +517,12 @@ verify_signatures (const struct TALER_EXCHANGE_DenomPublicKey *dki, .coin_pub = *coin_pub, .denom_pub_hash = *denom_pub_hash, .denom_sig = *denom_sig, - .age_commitment_hash = {{{0}}} /* FIXME-Oec */ + .age_commitment_hash = {{{0}}} }; + if (NULL != h_age_commitment) + { + coin_info.age_commitment_hash = *h_age_commitment; + } if (GNUNET_YES != TALER_test_coin_valid (&coin_info, @@ -547,6 +554,7 @@ TALER_EXCHANGE_deposit ( const char *merchant_payto_uri, const struct TALER_WireSalt *wire_salt, const struct TALER_PrivateContractHash *h_contract_terms, + const struct TALER_AgeCommitmentHash *h_age_commitment, const json_t *extension_details, const struct TALER_CoinSpendPublicKeyP *coin_pub, const struct TALER_DenominationSignature *denom_sig, @@ -599,11 +607,14 @@ TALER_EXCHANGE_deposit ( } GNUNET_assert (GNUNET_YES == TEAH_handle_is_ready (exchange)); + /* initialize h_wire */ TALER_merchant_wire_signature_hash (merchant_payto_uri, wire_salt, &h_wire); + key_state = TALER_EXCHANGE_get_keys (exchange); + dki = TALER_EXCHANGE_get_denomination_key (key_state, denom_pub); if (NULL == dki) @@ -612,6 +623,7 @@ TALER_EXCHANGE_deposit ( GNUNET_break_op (0); return NULL; } + if (0 > TALER_amount_subtract (&amount_without_fee, amount, @@ -621,17 +633,18 @@ TALER_EXCHANGE_deposit ( GNUNET_break_op (0); return NULL; } + TALER_denom_pub_hash (denom_pub, &denom_pub_hash); + if (GNUNET_OK != verify_signatures (dki, amount, &h_wire, h_contract_terms, - (NULL != extension_details) - ? &ech - : NULL, + (NULL != extension_details) ? &ech : NULL, coin_pub, + h_age_commitment, denom_sig, denom_pub, &denom_pub_hash, @@ -654,6 +667,9 @@ TALER_EXCHANGE_deposit ( wire_salt), GNUNET_JSON_pack_data_auto ("h_contract_terms", h_contract_terms), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_data_auto ("h_age_commitment", + h_age_commitment)), GNUNET_JSON_pack_data_auto ("denom_pub_hash", &denom_pub_hash), TALER_JSON_pack_denom_sig ("ub_sig", diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c index cf3d69d6a..3243f5e95 100644 --- a/src/lib/exchange_api_handle.c +++ b/src/lib/exchange_api_handle.c @@ -667,7 +667,9 @@ decode_keys_json (const json_t *resp_obj, enum TALER_EXCHANGE_VersionCompatibility *vc) { struct TALER_ExchangeSignatureP sig; - struct GNUNET_HashContext *hash_context; + struct GNUNET_HashContext *hash_context = NULL; + struct GNUNET_HashContext *hash_context_restricted = NULL; + bool have_age_restricted_denom = false; struct TALER_ExchangePublicKeyP pub; const char *currency; struct GNUNET_JSON_Specification mspec[] = { @@ -746,7 +748,6 @@ decode_keys_json (const json_t *resp_obj, key_data->version = GNUNET_strdup (ver); } - hash_context = NULL; EXITIF (GNUNET_OK != GNUNET_JSON_parse (resp_obj, (check_sig) ? mspec : &mspec[2], @@ -766,7 +767,10 @@ decode_keys_json (const json_t *resp_obj, /* parse the master public key and issue date of the response */ if (check_sig) + { hash_context = GNUNET_CRYPTO_hash_context_start (); + hash_context_restricted = GNUNET_CRYPTO_hash_context_start (); + } /* parse the signing keys */ { @@ -829,6 +833,9 @@ decode_keys_json (const json_t *resp_obj, EXITIF (GNUNET_OK != TALER_extensions_load_json_config (extensions)); } + + /* 4. assuming we might have now a new value for age_mask, set it in key_data */ + key_data->age_mask = TALER_extensions_age_restriction_ageMask (); } /* parse the denomination keys, merging with the @@ -839,9 +846,15 @@ decode_keys_json (const json_t *resp_obj, */ struct { char *name; - bool is_optional_age_restriction;} hive[2] = { - { "denoms", false }, - { "age_restricted_denoms", true }, + struct GNUNET_HashContext *hc; + bool is_optional_age_restriction;} + hive[2] = { + { "denoms", + hash_context, + false }, + { "age_restricted_denoms", + hash_context_restricted, + true } }; for (size_t s = 0; s < sizeof(hive) / sizeof(hive[0]); s++) @@ -853,25 +866,19 @@ decode_keys_json (const json_t *resp_obj, denom_keys_array = json_object_get (resp_obj, hive[s].name); - EXITIF (NULL == denom_keys_array && - ! hive[s].is_optional_age_restriction); - - if (NULL == denom_keys_array && - hive[s].is_optional_age_restriction) + if (NULL == denom_keys_array) continue; - /* if "age_restricted_denoms" exists, age-restriction better be enabled - * (that is: mask non-zero) */ - EXITIF (NULL != denom_keys_array && - hive[s].is_optional_age_restriction && - 0 == key_data->age_mask.mask); - EXITIF (JSON_ARRAY != json_typeof (denom_keys_array)); json_array_foreach (denom_keys_array, index, denom_key_obj) { struct TALER_EXCHANGE_DenomPublicKey dk; bool found = false; + /* mark that we have at least one age restricted denomination, needed + * for the hash calculation and signature verification below. */ + have_age_restricted_denom |= hive[s].is_optional_age_restriction; + memset (&dk, 0, sizeof (dk)); @@ -880,12 +887,7 @@ decode_keys_json (const json_t *resp_obj, check_sig, denom_key_obj, &key_data->master_pub, - hash_context)); - - /* Mark age restriction according where we got this denomination from, - * "denoms" or "age_restricted_denoms" */ - if (hive[s].is_optional_age_restriction) - dk.age_restricted = true; + hive[s].hc)); for (unsigned int j = 0; jnum_denom_keys; @@ -1044,6 +1046,18 @@ decode_keys_json (const json_t *resp_obj, .list_issue_date = GNUNET_TIME_timestamp_hton (key_data->list_issue_date) }; + /* If we had any age restricted denominations, add their hash to the end of + * the normal denominations. */ + if (have_age_restricted_denom) + { + struct GNUNET_HashCode hcr; + GNUNET_CRYPTO_hash_context_finish (hash_context_restricted, + &hcr); + GNUNET_CRYPTO_hash_context_read (hash_context, + &hcr, + sizeof(struct GNUNET_HashCode)); + } + GNUNET_CRYPTO_hash_context_finish (hash_context, &ks.hc); hash_context = NULL; diff --git a/src/lib/exchange_api_link.c b/src/lib/exchange_api_link.c index ec085b533..9d801c38b 100644 --- a/src/lib/exchange_api_link.c +++ b/src/lib/exchange_api_link.c @@ -105,6 +105,7 @@ parse_link_coin (const struct TALER_EXCHANGE_LinkHandle *lh, }; struct TALER_TransferSecretP secret; struct TALER_PlanchetSecretsP fc; + struct TALER_AgeCommitmentHash h_age_commitment = {0}; // TODO, see below. /* parse reply */ if (GNUNET_OK != @@ -145,6 +146,7 @@ parse_link_coin (const struct TALER_EXCHANGE_LinkHandle *lh, if (GNUNET_OK != TALER_planchet_prepare (&rpub, &fc, + NULL, /* FIXME-oec. struct TALER_AgeCommitmentHash * */ &c_hash, &pd)) { @@ -156,11 +158,21 @@ parse_link_coin (const struct TALER_EXCHANGE_LinkHandle *lh, pd.coin_ev_size, &coin_envelope_hash.hash); + /* + * TODO-oec: Derive the age commitment vector and hash it into + * h_age_commitment. + * Questions: + * - Where do we get the information about the support for age + * restriction of the denomination? + * - Where do we get the information bout the previous coin's age groups? + */ + if (GNUNET_OK != TALER_wallet_link_verify (&pd.denom_pub_hash, trans_pub, &coin_envelope_hash, &old_coin_pub, + &h_age_commitment, &link_sig)) { GNUNET_break_op (0); diff --git a/src/lib/exchange_api_management_get_keys.c b/src/lib/exchange_api_management_get_keys.c index e776082d3..1b6e0db8c 100644 --- a/src/lib/exchange_api_management_get_keys.c +++ b/src/lib/exchange_api_management_get_keys.c @@ -32,7 +32,7 @@ /** * Set to 1 for extra debug logging. */ -#define DEBUG 0 +#define DEBUG 1 /* FIXME-oec */ /** diff --git a/src/lib/exchange_api_management_post_extensions.c b/src/lib/exchange_api_management_post_extensions.c index c0ab143f6..87b0e0be8 100644 --- a/src/lib/exchange_api_management_post_extensions.c +++ b/src/lib/exchange_api_management_post_extensions.c @@ -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, diff --git a/src/lib/exchange_api_refresh_common.c b/src/lib/exchange_api_refresh_common.c index 3e367566d..ece9fe94f 100644 --- a/src/lib/exchange_api_refresh_common.c +++ b/src/lib/exchange_api_refresh_common.c @@ -79,6 +79,8 @@ serialize_melted_coin (const struct MeltedCoin *mc) return GNUNET_JSON_PACK ( GNUNET_JSON_pack_data_auto ("coin_priv", &mc->coin_priv), + GNUNET_JSON_pack_data_auto ("h_age_commitment", + &mc->h_age_commitment), TALER_JSON_pack_denom_sig ("denom_sig", &mc->sig), TALER_JSON_pack_denom_pub ("denom_pub", @@ -113,6 +115,9 @@ deserialize_melted_coin (struct MeltedCoin *mc, struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_fixed_auto ("coin_priv", &mc->coin_priv), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("h_age_commitment", + &mc->h_age_commitment)), TALER_JSON_spec_denom_sig ("denom_sig", &mc->sig), TALER_JSON_spec_denom_pub ("denom_pub", @@ -343,12 +348,14 @@ TALER_EXCHANGE_refresh_prepare ( const struct TALER_Amount *melt_amount, const struct TALER_DenominationSignature *melt_sig, const struct TALER_EXCHANGE_DenomPublicKey *melt_pk, + const struct TALER_AgeCommitment *age_commitment, unsigned int fresh_pks_len, const struct TALER_EXCHANGE_DenomPublicKey *fresh_pks) { struct MeltData md; json_t *ret; struct TALER_Amount total; + struct TALER_AgeCommitmentHash ach = {0}; struct TALER_CoinSpendPublicKeyP coin_pub; struct TALER_TransferSecretP trans_sec[TALER_CNC_KAPPA]; struct TALER_RefreshCommitmentEntry rce[TALER_CNC_KAPPA]; @@ -366,6 +373,10 @@ TALER_EXCHANGE_refresh_prepare ( md.melted_coin.original_value = melt_pk->value; md.melted_coin.expire_deposit = melt_pk->expire_deposit; + md.melted_coin.h_age_commitment = ach; + TALER_age_commitment_hash (age_commitment, + &md.melted_coin.h_age_commitment); + GNUNET_assert (GNUNET_OK == TALER_amount_set_zero (melt_amount->currency, &total)); @@ -375,6 +386,7 @@ TALER_EXCHANGE_refresh_prepare ( melt_sig); md.fresh_pks = GNUNET_new_array (fresh_pks_len, struct TALER_DenominationPublicKey); + for (unsigned int i = 0; imd->melted_coin.h_age_commitment)) + { + ach = &rrh->md->fresh_ach[rrh->noreveal_index][i]; + } + if (GNUNET_OK != GNUNET_JSON_parse (jsonai, spec, @@ -166,14 +173,14 @@ refresh_reveal_ok (struct TALER_EXCHANGE_RefreshesRevealHandle *rrh, hence recomputing it here... */ GNUNET_CRYPTO_eddsa_key_get_public (&fc->coin_priv.eddsa_priv, &coin_pub.eddsa_pub); - /* FIXME-Oec: Age commitment hash. */ TALER_coin_pub_hash (&coin_pub, - NULL, /* FIXME-Oec */ + ach, &coin_hash); if (GNUNET_OK != TALER_planchet_to_coin (pk, &blind_sig, fc, + ach, &coin_hash, &coin)) { @@ -359,6 +366,7 @@ TALER_EXCHANGE_refreshes_reveal ( if (GNUNET_OK != TALER_planchet_prepare (&md->fresh_pks[i], &md->fresh_coins[noreveal_index][i], + NULL, /* FIXME-oec */ &c_hash, &pd)) { diff --git a/src/lib/exchange_api_refund.c b/src/lib/exchange_api_refund.c index 94909470a..a9510715b 100644 --- a/src/lib/exchange_api_refund.c +++ b/src/lib/exchange_api_refund.c @@ -203,6 +203,7 @@ verify_conflict_history_ok (struct TALER_EXCHANGE_RefundHandle *rh, struct TALER_Amount deposit_fee; struct TALER_MerchantWireHash h_wire; struct TALER_PrivateContractHash h_contract_terms; + struct TALER_AgeCommitmentHash h_age_commitment = {{{0}}}; // struct TALER_ExtensionContractHash h_extensions; // FIXME! struct TALER_DenominationHash h_denom_pub; struct GNUNET_TIME_Timestamp wallet_timestamp; @@ -218,6 +219,9 @@ verify_conflict_history_ok (struct TALER_EXCHANGE_RefundHandle *rh, &h_wire), GNUNET_JSON_spec_fixed_auto ("h_denom_pub", &h_denom_pub), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("h_age_commitment", + &h_age_commitment)), GNUNET_JSON_spec_timestamp ("timestamp", &wallet_timestamp), GNUNET_JSON_spec_timestamp ("refund_deadline", @@ -243,6 +247,7 @@ verify_conflict_history_ok (struct TALER_EXCHANGE_RefundHandle *rh, &deposit_fee, &h_wire, &h_contract_terms, + &h_age_commitment, NULL /* h_extensions! */, &h_denom_pub, wallet_timestamp, diff --git a/src/lib/exchange_api_withdraw.c b/src/lib/exchange_api_withdraw.c index 5e823ee6d..92b2a567f 100644 --- a/src/lib/exchange_api_withdraw.c +++ b/src/lib/exchange_api_withdraw.c @@ -63,6 +63,11 @@ struct TALER_EXCHANGE_WithdrawHandle */ struct TALER_PlanchetSecretsP ps; + /** + * Hash of the age commitment for this coin, if applicable. Maybe NULL + */ + const struct TALER_AgeCommitmentHash *ach; + /** * Denomination key we are withdrawing. */ @@ -106,6 +111,7 @@ handle_reserve_withdraw_finished ( TALER_planchet_to_coin (&wh->pk.key, blind_sig, &wh->ps, + wh->ach, &wh->c_hash, &fc)) { @@ -159,6 +165,7 @@ handle_reserve_withdraw_finished ( * @param reserve_priv private key of the reserve to withdraw from * @param ps secrets of the planchet * caller must have committed this value to disk before the call (with @a pk) + * @param ach (optional) hash of the age commitment that should be bound to this coin. Maybe NULL. * @param res_cb the callback to call when the final result for this request is available * @param res_cb_cls closure for the above callback * @return handle for the operation on success, NULL on error, i.e. @@ -171,11 +178,13 @@ TALER_EXCHANGE_withdraw ( const struct TALER_EXCHANGE_DenomPublicKey *pk, const struct TALER_ReservePrivateKeyP *reserve_priv, const struct TALER_PlanchetSecretsP *ps, + const struct TALER_AgeCommitmentHash *ach, TALER_EXCHANGE_WithdrawCallback res_cb, void *res_cb_cls) { struct TALER_PlanchetDetail pd; struct TALER_EXCHANGE_WithdrawHandle *wh; + bool age_restricted = (0 != pk->key.age_mask.mask); wh = GNUNET_new (struct TALER_EXCHANGE_WithdrawHandle); wh->exchange = exchange; @@ -183,9 +192,14 @@ TALER_EXCHANGE_withdraw ( wh->cb_cls = res_cb_cls; wh->pk = *pk; wh->ps = *ps; + wh->ach = ach; + + GNUNET_assert (age_restricted == (NULL != ach)); + if (GNUNET_OK != TALER_planchet_prepare (&pk->key, ps, + ach, &wh->c_hash, &pd)) { diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am index bc78217b3..5b9e0294d 100644 --- a/src/testing/Makefile.am +++ b/src/testing/Makefile.am @@ -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 \ @@ -208,6 +209,7 @@ test_exchange_api_LDADD = \ -lgnunetcurl \ -lgnunetutil \ -ljansson \ + -ltalerextensions \ $(XLIB) test_exchange_management_api_SOURCES = \ diff --git a/src/testing/test_auditor_api.c b/src/testing/test_auditor_api.c index 90675dd92..f748ac291 100644 --- a/src/testing/test_auditor_api.c +++ b/src/testing/test_auditor_api.c @@ -128,6 +128,7 @@ run (void *cls, TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1", "create-reserve-1", "EUR:5", + 0, /* age restriction off */ MHD_HTTP_OK), TALER_TESTING_cmd_end () }; @@ -168,6 +169,7 @@ run (void *cls, TALER_TESTING_cmd_withdraw_amount ("refresh-withdraw-coin-1", "refresh-create-reserve-1", "EUR:5", + 0, /* age restriction off */ MHD_HTTP_OK), /** * Try to partially spend (deposit) 1 EUR of the 5 EUR coin (in @@ -315,6 +317,7 @@ run (void *cls, TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-unaggregated", "create-reserve-unaggregated", "EUR:5", + 0, /* age restriction off */ MHD_HTTP_OK), TALER_TESTING_cmd_deposit ("deposit-unaggregated", "withdraw-coin-unaggregated", @@ -347,6 +350,7 @@ run (void *cls, TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-r1", "create-reserve-r1", "EUR:5", + 0, /* age restriction off */ MHD_HTTP_OK), /** * Spend 5 EUR of the 5 EUR coin (in full). Merchant would @@ -402,6 +406,7 @@ run (void *cls, TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-1", "recoup-create-reserve-1", "EUR:5", + 0, /* age restriction off */ MHD_HTTP_OK), TALER_TESTING_cmd_revoke ("revoke-1", MHD_HTTP_OK, @@ -417,6 +422,7 @@ run (void *cls, TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2", "recoup-create-reserve-1", "EUR:1", + 0, /* age restriction off */ MHD_HTTP_OK), /** * These commands should close the reserve because the aggregator @@ -447,6 +453,7 @@ run (void *cls, TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2a", "recoup-create-reserve-2", "EUR:1", + 0, /* age restriction off */ MHD_HTTP_OK), /** * Withdraw a 1 EUR coin, at fee of 1 ct @@ -454,6 +461,7 @@ run (void *cls, TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2b", "recoup-create-reserve-2", "EUR:1", + 0, /* age restriction off */ MHD_HTTP_OK), TALER_TESTING_cmd_deposit ("recoup-deposit-partial", "recoup-withdraw-coin-2a", @@ -491,42 +499,52 @@ run (void *cls, TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-1", "massive-reserve", "EUR:1", + 0, /* age restriction off */ MHD_HTTP_OK), TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-2", "massive-reserve", "EUR:1", + 0, /* age restriction off */ MHD_HTTP_OK), TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-3", "massive-reserve", "EUR:1", + 0, /* age restriction off */ MHD_HTTP_OK), TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-4", "massive-reserve", "EUR:1", + 0, /* age restriction off */ MHD_HTTP_OK), TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-5", "massive-reserve", "EUR:1", + 0, /* age restriction off */ MHD_HTTP_OK), TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-6", "massive-reserve", "EUR:1", + 0, /* age restriction off */ MHD_HTTP_OK), TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-7", "massive-reserve", "EUR:1", + 0, /* age restriction off */ MHD_HTTP_OK), TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-8", "massive-reserve", "EUR:1", + 0, /* age restriction off */ MHD_HTTP_OK), TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-9", "massive-reserve", "EUR:1", + 0, /* age restriction off */ MHD_HTTP_OK), TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-10", "massive-reserve", "EUR:1", + 0, /* age restriction off */ MHD_HTTP_OK), TALER_TESTING_cmd_deposit ( "massive-deposit-1", @@ -708,7 +726,7 @@ main (int argc, GNUNET_break (0); return 1; case GNUNET_NO: - return 77; + return 78; case GNUNET_OK: if (GNUNET_OK != /* Set up event loop and reschedule context, plus @@ -718,11 +736,11 @@ main (int argc, TALER_TESTING_auditor_setup (&run, NULL, CONFIG_FILE)) - return 1; + return 2; break; default: GNUNET_break (0); - return 1; + return 3; } return 0; } diff --git a/src/testing/test_exchange_api.c b/src/testing/test_exchange_api.c index 59c2cb06d..0348b265c 100644 --- a/src/testing/test_exchange_api.c +++ b/src/testing/test_exchange_api.c @@ -33,6 +33,7 @@ #include "taler_bank_service.h" #include "taler_fakebank_lib.h" #include "taler_testing_lib.h" +#include "taler_extensions.h" /** * Configuration file we use. One (big) configuration is used @@ -138,6 +139,7 @@ run (void *cls, TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1", "create-reserve-1", "EUR:5", + 0, /* age restriction off */ MHD_HTTP_OK), /** * Withdraw EUR:1 using the SAME private coin key as for the previous coin @@ -146,6 +148,7 @@ run (void *cls, TALER_TESTING_cmd_withdraw_amount_reuse_key ("withdraw-coin-1x", "create-reserve-1", "EUR:1", + 0, /* age restriction off */ "withdraw-coin-1", MHD_HTTP_OK), /** @@ -161,6 +164,7 @@ run (void *cls, TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2", "create-reserve-1", "EUR:5", + 0, /* age restriction off */ MHD_HTTP_CONFLICT), TALER_TESTING_cmd_end () }; @@ -254,6 +258,7 @@ run (void *cls, TALER_TESTING_cmd_withdraw_amount ("refresh-withdraw-coin-1", "refresh-create-reserve-1", "EUR:5", + 0, /* age restriction off */ MHD_HTTP_OK), /* Try to partially spend (deposit) 1 EUR of the 5 EUR coin * (in full) (merchant would receive EUR:0.99 due to 1 ct @@ -330,6 +335,61 @@ 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:5.02"), + TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-age", + "EUR:5.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-1", + "create-reserve-age", + "EUR:5", + 13, + MHD_HTTP_OK), + + TALER_TESTING_cmd_end () + }; + + struct TALER_TESTING_Command spend_age[] = { + /** + * Spend the coin. + */ + TALER_TESTING_cmd_deposit ("deposit-simple-age", + "withdraw-coin-age-1", + 0, + bc.user42_payto, + "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}", + GNUNET_TIME_UNIT_ZERO, + "EUR:5", + MHD_HTTP_OK), + TALER_TESTING_cmd_deposit_replay ("deposit-simple-replay-age", + "deposit-simple-age", + MHD_HTTP_OK), + TALER_TESTING_cmd_end () + }; + struct TALER_TESTING_Command track[] = { /* Try resolving a deposit's WTID, as we never triggered * execution of transactions, the answer should be that @@ -372,6 +432,11 @@ run (void *cls, "EUR:4.98", bc.exchange_payto, bc.user42_payto), + TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-499c2", + ec.exchange_url, + "EUR:4.98", + bc.exchange_payto, + bc.user42_payto), TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-99c1", ec.exchange_url, "EUR:0.98", @@ -426,6 +491,7 @@ run (void *cls, TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-unaggregated", "create-reserve-unaggregated", "EUR:5", + 0, /* age restriction off */ MHD_HTTP_OK), TALER_TESTING_cmd_deposit ("deposit-unaggregated", "withdraw-coin-unaggregated", @@ -464,6 +530,7 @@ run (void *cls, TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-aggtest", "create-reserve-aggtest", "EUR:5", + 0, /* age restriction off */ MHD_HTTP_OK), TALER_TESTING_cmd_deposit ("deposit-aggtest-1", "withdraw-coin-aggtest", @@ -512,6 +579,7 @@ run (void *cls, TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-r1", "create-reserve-r1", "EUR:5", + 0, /* age restriction off */ MHD_HTTP_OK), /** * Spend 5 EUR of the 5 EUR coin (in full) (merchant would @@ -612,6 +680,7 @@ run (void *cls, TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-rb", "create-reserve-rb", "EUR:5", + 0, /* age restriction off */ MHD_HTTP_OK), TALER_TESTING_cmd_deposit ("deposit-refund-1b", "withdraw-coin-rb", @@ -661,11 +730,13 @@ run (void *cls, TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-1", "recoup-create-reserve-1", "EUR:5", + 0, /* age restriction off */ MHD_HTTP_OK), /* Withdraw a 10 EUR coin, at fee of 1 ct */ TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-1b", "recoup-create-reserve-1", "EUR:10", + 0, /* age restriction off */ MHD_HTTP_OK), /* melt 10 EUR coin to get 5 EUR refreshed coin */ TALER_TESTING_cmd_melt ("recoup-melt-coin-1b", @@ -756,6 +827,7 @@ run (void *cls, TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2", "recoup-create-reserve-1", "EUR:1", + 0, /* age restriction off */ MHD_HTTP_OK), /** * This withdrawal will test the logic to create a "recoup" @@ -764,6 +836,7 @@ run (void *cls, TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2-over", "recoup-create-reserve-1", "EUR:10", + 0, /* age restriction off */ MHD_HTTP_CONFLICT), TALER_TESTING_cmd_status ("recoup-reserve-status-2", "recoup-create-reserve-1", @@ -796,6 +869,7 @@ run (void *cls, TALER_TESTING_cmd_withdraw_amount ("expired-withdraw", "short-lived-reserve", "EUR:1", + 0, /* age restriction off */ MHD_HTTP_CONFLICT), TALER_TESTING_cmd_check_bank_transfer ("check_bank_short-lived_reimburse", ec.exchange_url, @@ -820,11 +894,13 @@ run (void *cls, TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2a", "recoup-create-reserve-2", "EUR:1", + 0, /* age restriction off */ MHD_HTTP_OK), /* Withdraw a 1 EUR coin, at fee of 1 ct */ TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2b", "recoup-create-reserve-2", "EUR:1", + 0, /* age restriction off */ MHD_HTTP_OK), TALER_TESTING_cmd_deposit ("recoup-deposit-partial", "recoup-withdraw-coin-2a", @@ -885,6 +961,7 @@ run (void *cls, TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-3-revoked", "recoup-create-reserve-3", "EUR:1", + 0, /* age restriction off */ MHD_HTTP_GONE), /* check that we are empty before the rejection test */ TALER_TESTING_cmd_check_bank_empty ("check-empty-again"), @@ -931,6 +1008,8 @@ run (void *cls, TALER_TESTING_cmd_auditor_add ("add-auditor-OK", MHD_HTTP_NO_CONTENT, false), + TALER_TESTING_cmd_exec_offline_sign_extensions ("offline-sign-extensions", + CONFIG_FILE), TALER_TESTING_cmd_wire_add ("add-wire-account", "payto://x-taler-bank/localhost/2", MHD_HTTP_NO_CONTENT, @@ -951,6 +1030,10 @@ run (void *cls, spend), TALER_TESTING_cmd_batch ("refresh", refresh), + TALER_TESTING_cmd_batch ("withdraw-age", + withdraw_age), + TALER_TESTING_cmd_batch ("spend-age", + spend_age), TALER_TESTING_cmd_batch ("track", track), TALER_TESTING_cmd_batch ("unaggregation", @@ -986,6 +1069,9 @@ main (int argc, GNUNET_log_setup ("test-exchange-api", "INFO", NULL); + + TALER_extensions_init (); + /* Check fakebank port is available and get config */ if (GNUNET_OK != TALER_TESTING_prepare_fakebank (CONFIG_FILE, @@ -1004,7 +1090,7 @@ main (int argc, GNUNET_break (0); return 1; case GNUNET_NO: - return 77; + return 78; case GNUNET_OK: if (GNUNET_OK != /* Set up event loop and reschedule context, plus @@ -1014,11 +1100,11 @@ main (int argc, TALER_TESTING_setup_with_exchange (&run, NULL, CONFIG_FILE)) - return 1; + return 2; break; default: GNUNET_break (0); - return 1; + return 3; } return 0; } diff --git a/src/testing/test_exchange_api.conf b/src/testing/test_exchange_api.conf index 48d5c2004..6bb7bd59f 100644 --- a/src/testing/test_exchange_api.conf +++ b/src/testing/test_exchange_api.conf @@ -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 diff --git a/src/testing/test_exchange_api_revocation.c b/src/testing/test_exchange_api_revocation.c index 0531c5b83..2ab72a8db 100644 --- a/src/testing/test_exchange_api_revocation.c +++ b/src/testing/test_exchange_api_revocation.c @@ -95,11 +95,13 @@ run (void *cls, TALER_TESTING_cmd_withdraw_amount ("withdraw-revocation-coin-1", "create-reserve-1", "EUR:5", + 0, /* age restriction off */ MHD_HTTP_OK), /* Withdraw another 5 EUR coin, at fee of 1 ct */ TALER_TESTING_cmd_withdraw_amount ("withdraw-revocation-coin-2", "create-reserve-1", "EUR:5", + 0, /* age restriction off */ MHD_HTTP_OK), /* Try to partially spend (deposit) 1 EUR of the 5 EUR coin (in full) * (merchant would receive EUR:0.99 due to 1 ct deposit fee) */// diff --git a/src/testing/test_kyc_api.c b/src/testing/test_kyc_api.c index b9e9a9f83..ca87edd83 100644 --- a/src/testing/test_kyc_api.c +++ b/src/testing/test_kyc_api.c @@ -105,10 +105,12 @@ run (void *cls, TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1-no-kyc", "create-reserve-1", "EUR:10", + 0, /* age restriction off */ MHD_HTTP_ACCEPTED), TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1", "create-reserve-1", "EUR:5", + 0, /* age restriction off */ MHD_HTTP_OK), TALER_TESTING_cmd_end () }; @@ -120,6 +122,7 @@ run (void *cls, TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1-lacking-kyc", "create-reserve-1", "EUR:5", + 0, /* age restriction off */ MHD_HTTP_ACCEPTED), TALER_TESTING_cmd_proof_kyc ("proof-kyc", "withdraw-coin-1-lacking-kyc", @@ -129,6 +132,7 @@ run (void *cls, TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1-with-kyc", "create-reserve-1", "EUR:5", + 0, /* age restriction off */ MHD_HTTP_OK), TALER_TESTING_cmd_end () }; diff --git a/src/testing/testing_api_cmd_deposit.c b/src/testing/testing_api_cmd_deposit.c index a0eb35f19..67c2c7021 100644 --- a/src/testing/testing_api_cmd_deposit.c +++ b/src/testing/testing_api_cmd_deposit.c @@ -287,6 +287,8 @@ deposit_run (void *cls, const struct TALER_TESTING_Command *coin_cmd; const struct TALER_CoinSpendPrivateKeyP *coin_priv; struct TALER_CoinSpendPublicKeyP coin_pub; + struct TALER_AgeCommitment *age_commitment = NULL; + struct TALER_AgeCommitmentHash h_age_commitment = {0}; const struct TALER_EXCHANGE_DenomPublicKey *denom_pub; const struct TALER_DenominationSignature *denom_pub_sig; struct TALER_CoinSpendSignatureP coin_sig; @@ -382,6 +384,10 @@ deposit_run (void *cls, TALER_TESTING_get_trait_coin_priv (coin_cmd, ds->coin_index, &coin_priv)) || + (GNUNET_OK != + TALER_TESTING_get_trait_age_commitment (coin_cmd, + ds->coin_index, + &age_commitment)) || (GNUNET_OK != TALER_TESTING_get_trait_denom_pub (coin_cmd, ds->coin_index, @@ -398,6 +404,12 @@ deposit_run (void *cls, TALER_TESTING_interpreter_fail (is); return; } + + if (NULL != age_commitment) + { + TALER_age_commitment_hash (age_commitment, &h_age_commitment); + } + ds->deposit_fee = denom_pub->fee_deposit; GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, &coin_pub.eddsa_pub); @@ -431,7 +443,8 @@ deposit_run (void *cls, &denom_pub->fee_deposit, &h_wire, &h_contract_terms, - NULL, /* FIXME: extension hash! */ + &h_age_commitment, + NULL, /* FIXME: add hash of extensions */ &denom_pub->h_key, ds->wallet_timestamp, &merchant_pub, @@ -445,7 +458,8 @@ deposit_run (void *cls, payto_uri, &wire_salt, &h_contract_terms, - NULL, /* FIXME: extension object */ + &h_age_commitment, + NULL, /* FIXME: add hash of extensions */ &coin_pub, denom_pub_sig, &denom_pub->key, @@ -520,6 +534,7 @@ deposit_traits (void *cls, const struct TALER_TESTING_Command *coin_cmd; /* Will point to coin cmd internals. */ const struct TALER_CoinSpendPrivateKeyP *coin_spent_priv; + struct TALER_AgeCommitment *age_commitment; if (GNUNET_YES != ds->command_initialized) { @@ -540,7 +555,11 @@ deposit_traits (void *cls, if (GNUNET_OK != TALER_TESTING_get_trait_coin_priv (coin_cmd, ds->coin_index, - &coin_spent_priv)) + &coin_spent_priv) || + (GNUNET_OK != + TALER_TESTING_get_trait_age_commitment (coin_cmd, + ds->coin_index, + &age_commitment))) { GNUNET_break (0); TALER_TESTING_interpreter_fail (ds->is); @@ -555,6 +574,8 @@ deposit_traits (void *cls, /* These traits are always available */ TALER_TESTING_make_trait_coin_priv (0, coin_spent_priv), + TALER_TESTING_make_trait_age_commitment (0, + age_commitment), TALER_TESTING_make_trait_wire_details (ds->wire_details), TALER_TESTING_make_trait_contract_terms (ds->contract_terms), TALER_TESTING_make_trait_merchant_priv (&ds->merchant_priv), diff --git a/src/testing/testing_api_cmd_insert_deposit.c b/src/testing/testing_api_cmd_insert_deposit.c index d45bd0c67..4e3ce2147 100644 --- a/src/testing/testing_api_cmd_insert_deposit.c +++ b/src/testing/testing_api_cmd_insert_deposit.c @@ -238,7 +238,7 @@ insert_deposit_run (void *cls, { uint64_t known_coin_id; struct TALER_DenominationHash dph; - struct TALER_AgeHash agh; + struct TALER_AgeCommitmentHash agh; if ( (GNUNET_OK != ids->dbc->plugin->start (ids->dbc->plugin->cls, diff --git a/src/testing/testing_api_cmd_offline_sign_extensions.c b/src/testing/testing_api_cmd_offline_sign_extensions.c new file mode 100644 index 000000000..f39679f97 --- /dev/null +++ b/src/testing/testing_api_cmd_offline_sign_extensions.c @@ -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 +*/ + +/** + * @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 +#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 */ diff --git a/src/testing/testing_api_cmd_refresh.c b/src/testing/testing_api_cmd_refresh.c index d2c2c714c..db2537a30 100644 --- a/src/testing/testing_api_cmd_refresh.c +++ b/src/testing/testing_api_cmd_refresh.c @@ -70,6 +70,11 @@ struct TALER_TESTING_FreshCoinData */ struct TALER_CoinSpendPrivateKeyP coin_priv; + /* + * Age commitment for the coin, NULL if not applicable. + */ + struct TALER_AgeCommitment *age_commitment; + /** * The blinding key (needed for recoup operations). */ @@ -121,6 +126,11 @@ struct RefreshMeltState */ const struct TALER_CoinSpendPrivateKeyP *melt_priv; + /* + * Age commitment for the coin, NULL if not applicable. + */ + struct TALER_AgeCommitment *age_commitment; + /** * Task scheduled to try later. */ @@ -992,6 +1002,7 @@ melt_run (void *cls, const struct TALER_DenominationSignature *melt_sig; const struct TALER_EXCHANGE_DenomPublicKey *melt_denom_pub; const struct TALER_TESTING_Command *coin_command; + bool age_restricted; if (NULL == (coin_command = TALER_TESTING_interpreter_lookup_command @@ -1012,6 +1023,16 @@ melt_run (void *cls, return; } + if (GNUNET_OK != + TALER_TESTING_get_trait_age_commitment (coin_command, + 0, + &rms->age_commitment)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (rms->is); + return; + } + if (GNUNET_OK != TALER_TESTING_get_trait_denom_sig (coin_command, 0, @@ -1021,6 +1042,7 @@ melt_run (void *cls, TALER_TESTING_interpreter_fail (rms->is); return; } + if (GNUNET_OK != TALER_TESTING_get_trait_denom_pub (coin_command, 0, @@ -1030,9 +1052,11 @@ melt_run (void *cls, TALER_TESTING_interpreter_fail (rms->is); return; } + /* Melt amount starts with the melt fee of the old coin; we'll add the values and withdraw fees of the fresh coins next */ melt_amount = melt_denom_pub->fee_refresh; + age_restricted = melt_denom_pub->key.age_mask.mask != 0; for (unsigned int i = 0; iis); return; } - fresh_pk = TALER_TESTING_find_pk - (TALER_EXCHANGE_get_keys (is->exchange), &fresh_amount); + fresh_pk = TALER_TESTING_find_pk (TALER_EXCHANGE_get_keys (is->exchange), + &fresh_amount, + age_restricted); if (NULL == fresh_pk) { GNUNET_break (0); @@ -1071,13 +1096,33 @@ melt_run (void *cls, &fresh_pk->key); } /* end for */ GNUNET_assert (NULL == rms->refresh_data); - rms->refresh_data - = TALER_EXCHANGE_refresh_prepare (rms->melt_priv, - &melt_amount, - melt_sig, - melt_denom_pub, - num_fresh_coins, - rms->fresh_pks); + { + struct TALER_AgeCommitment *ac = NULL; + + GNUNET_assert (age_restricted == (NULL != rms->age_commitment)); + + if (NULL != rms->age_commitment) + { + uint32_t seed = GNUNET_CRYPTO_random_u32 ( + GNUNET_CRYPTO_QUALITY_WEAK, + UINT32_MAX); + + GNUNET_assert (GNUNET_OK == + TALER_age_commitment_derive ( + rms->age_commitment, + seed, + ac)); + } + + rms->refresh_data + = TALER_EXCHANGE_refresh_prepare (rms->melt_priv, + &melt_amount, + melt_sig, + melt_denom_pub, + ac, + num_fresh_coins, + rms->fresh_pks); + } if (NULL == rms->refresh_data) { GNUNET_break (0); @@ -1168,6 +1213,8 @@ melt_traits (void *cls, &rms->fresh_pks[index]), TALER_TESTING_make_trait_coin_priv (0, rms->melt_priv), + TALER_TESTING_make_trait_age_commitment (index, + rms->age_commitment), TALER_TESTING_trait_end () }; @@ -1326,6 +1373,9 @@ refresh_reveal_traits (void *cls, TALER_TESTING_make_trait_coin_priv ( index, &rrs->fresh_coins[index].coin_priv), + TALER_TESTING_make_trait_age_commitment ( + index, + rrs->fresh_coins[index].age_commitment), TALER_TESTING_make_trait_denom_pub ( index, rrs->fresh_coins[index].pk), diff --git a/src/testing/testing_api_cmd_withdraw.c b/src/testing/testing_api_cmd_withdraw.c index 8e6cba704..e3c129172 100644 --- a/src/testing/testing_api_cmd_withdraw.c +++ b/src/testing/testing_api_cmd_withdraw.c @@ -27,6 +27,7 @@ #include #include #include "taler_signatures.h" +#include "taler_extensions.h" #include "taler_testing_lib.h" #include "backoff.h" @@ -115,6 +116,18 @@ struct WithdrawState */ struct TALER_PlanchetSecretsP ps; + /** + * An age > 0 signifies age restriction is required + */ + uint8_t age; + + /** + * If age > 0, put here the corresponding age commitment and its hash, + * respectivelly, NULL otherwise. + */ + struct TALER_AgeCommitment *age_commitment; + struct TALER_AgeCommitmentHash *h_age_commitment; + /** * Reserve history entry that corresponds to this operation. * Will be of type #TALER_EXCHANGE_RTT_WITHDRAWAL. @@ -363,12 +376,14 @@ withdraw_run (void *cls, = TALER_TESTING_interpreter_lookup_command ( is, ws->reserve_reference); + if (NULL == create_reserve) { GNUNET_break (0); TALER_TESTING_interpreter_fail (is); return; } + if (GNUNET_OK != TALER_TESTING_get_trait_reserve_priv (create_reserve, &rp)) @@ -377,6 +392,7 @@ withdraw_run (void *cls, TALER_TESTING_interpreter_fail (is); return; } + if (NULL == ws->exchange_url) ws->exchange_url = GNUNET_strdup (TALER_EXCHANGE_get_base_url (is->exchange)); @@ -386,6 +402,7 @@ withdraw_run (void *cls, ws->reserve_payto_uri = TALER_payto_from_reserve (ws->exchange_url, &ws->reserve_pub); + if (NULL == ws->reuse_coin_key_ref) { TALER_planchet_setup_random (&ws->ps); @@ -412,10 +429,12 @@ withdraw_run (void *cls, TALER_planchet_setup_random (&ws->ps); ws->ps.coin_priv = *coin_priv; } + if (NULL == ws->pk) { dpk = TALER_TESTING_find_pk (TALER_EXCHANGE_get_keys (is->exchange), - &ws->amount); + &ws->amount, + ws->age > 0); if (NULL == dpk) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, @@ -433,18 +452,24 @@ withdraw_run (void *cls, { ws->amount = ws->pk->value; } + ws->reserve_history.type = TALER_EXCHANGE_RTT_WITHDRAWAL; GNUNET_assert (0 <= TALER_amount_add (&ws->reserve_history.amount, &ws->amount, &ws->pk->fee_withdraw)); ws->reserve_history.details.withdraw.fee = ws->pk->fee_withdraw; - ws->wsh = TALER_EXCHANGE_withdraw (is->exchange, - ws->pk, - rp, - &ws->ps, - &reserve_withdraw_cb, - ws); + + { + + ws->wsh = TALER_EXCHANGE_withdraw (is->exchange, + ws->pk, + rp, + &ws->ps, + ws->h_age_commitment, + &reserve_withdraw_cb, + ws); + } if (NULL == ws->wsh) { GNUNET_break (0); @@ -486,6 +511,16 @@ withdraw_cleanup (void *cls, TALER_EXCHANGE_destroy_denomination_key (ws->pk); ws->pk = NULL; } + if (NULL != ws->age_commitment) + { + GNUNET_free (ws->age_commitment); + ws->age_commitment = NULL; + } + if (NULL != ws->h_age_commitment) + { + GNUNET_free (ws->h_age_commitment); + ws->h_age_commitment = NULL; + } GNUNET_free (ws->exchange_url); GNUNET_free (ws->reserve_payto_uri); GNUNET_free (ws); @@ -512,13 +547,13 @@ withdraw_traits (void *cls, struct TALER_TESTING_Trait traits[] = { /* history entry MUST be first due to response code logic below! */ TALER_TESTING_make_trait_reserve_history (&ws->reserve_history), - TALER_TESTING_make_trait_coin_priv (0 /* only one coin */, + TALER_TESTING_make_trait_coin_priv (index /* only one coin */, &ws->ps.coin_priv), - TALER_TESTING_make_trait_blinding_key (0 /* only one coin */, + TALER_TESTING_make_trait_blinding_key (index /* only one coin */, &ws->ps.blinding_key), - TALER_TESTING_make_trait_denom_pub (0 /* only one coin */, + TALER_TESTING_make_trait_denom_pub (index /* only one coin */, ws->pk), - TALER_TESTING_make_trait_denom_sig (0 /* only one coin */, + TALER_TESTING_make_trait_denom_sig (index /* only one coin */, &ws->sig), TALER_TESTING_make_trait_reserve_priv (&ws->reserve_priv), TALER_TESTING_make_trait_reserve_pub (&ws->reserve_pub), @@ -528,6 +563,8 @@ withdraw_traits (void *cls, (const char **) &ws->reserve_payto_uri), TALER_TESTING_make_trait_exchange_url ( (const char **) &ws->exchange_url), + TALER_TESTING_make_trait_age_commitment (index, ws->age_commitment), + TALER_TESTING_make_trait_h_age_commitment (index, ws->h_age_commitment), TALER_TESTING_trait_end () }; @@ -547,6 +584,7 @@ withdraw_traits (void *cls, * @param label command label. * @param reserve_reference command providing us with a reserve to withdraw from * @param amount how much we withdraw. + * @param age if > 0, age restriction is activated * @param expected_response_code which HTTP response code * we expect from the exchange. * @return the withdraw command to be executed by the interpreter. @@ -555,11 +593,45 @@ struct TALER_TESTING_Command TALER_TESTING_cmd_withdraw_amount (const char *label, const char *reserve_reference, const char *amount, + const uint8_t age, unsigned int expected_response_code) { struct WithdrawState *ws; ws = GNUNET_new (struct WithdrawState); + + ws->age = age; + if (0 < age) + { + struct TALER_AgeCommitment *ac; + struct TALER_AgeCommitmentHash *hac; + uint32_t seed; + struct TALER_AgeMask mask; + + ac = GNUNET_new (struct TALER_AgeCommitment); + hac = GNUNET_new (struct TALER_AgeCommitmentHash); + seed = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, UINT32_MAX); + mask = TALER_extensions_age_restriction_ageMask (); + + if (GNUNET_OK != + TALER_age_restriction_commit ( + &mask, + age, + seed, + ac)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to generate age commitment for age %d at %s\n", + age, + label); + GNUNET_assert (0); + } + + TALER_age_commitment_hash (ac,hac); + ws->age_commitment = ac; + ws->h_age_commitment = hac; + } + ws->reserve_reference = reserve_reference; if (GNUNET_OK != TALER_string_to_amount (amount, @@ -595,6 +667,7 @@ TALER_TESTING_cmd_withdraw_amount (const char *label, * @param label command label. * @param reserve_reference command providing us with a reserve to withdraw from * @param amount how much we withdraw. + * @param age if > 0, age restriction is activated * @param coin_ref reference to (withdraw/reveal) command of a coin * from which we should re-use the private key * @param expected_response_code which HTTP response code @@ -606,6 +679,7 @@ TALER_TESTING_cmd_withdraw_amount_reuse_key ( const char *label, const char *reserve_reference, const char *amount, + uint8_t age, const char *coin_ref, unsigned int expected_response_code) { @@ -614,6 +688,7 @@ TALER_TESTING_cmd_withdraw_amount_reuse_key ( cmd = TALER_TESTING_cmd_withdraw_amount (label, reserve_reference, amount, + age, expected_response_code); { struct WithdrawState *ws = cmd.cls; diff --git a/src/testing/testing_api_helpers_exchange.c b/src/testing/testing_api_helpers_exchange.c index fe7588107..a5ce7fc4a 100644 --- a/src/testing/testing_api_helpers_exchange.c +++ b/src/testing/testing_api_helpers_exchange.c @@ -27,6 +27,7 @@ #include "taler_json_lib.h" #include #include "taler_signatures.h" +#include "taler_extensions.h" #include "taler_testing_lib.h" /** @@ -312,6 +313,9 @@ sign_keys_for_exchange (void *cls, char *exchange_master_pub; int ret; + /* Load the age restriction mask from the configuration */ + TALER_extensions_load_taler_config (cfg); + if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, "exchange", @@ -416,11 +420,13 @@ TALER_TESTING_prepare_exchange (const char *config_filename, * * @param keys array of keys to search * @param amount coin value to look for + * @param age_restricted must denomination support age restriction? * @return NULL if no matching key was found */ const struct TALER_EXCHANGE_DenomPublicKey * TALER_TESTING_find_pk (const struct TALER_EXCHANGE_Keys *keys, - const struct TALER_Amount *amount) + const struct TALER_Amount *amount, + bool age_restricted) { struct GNUNET_TIME_Timestamp now; struct TALER_EXCHANGE_DenomPublicKey *pk; @@ -437,7 +443,8 @@ TALER_TESTING_find_pk (const struct TALER_EXCHANGE_Keys *keys, pk->valid_from)) && (GNUNET_TIME_timestamp_cmp (now, <, - pk->withdraw_valid_until)) ) + pk->withdraw_valid_until)) && + (age_restricted == (0 != pk->key.age_mask.mask)) ) return pk; } /* do 2nd pass to check if expiration times are to blame for @@ -453,7 +460,8 @@ TALER_TESTING_find_pk (const struct TALER_EXCHANGE_Keys *keys, pk->valid_from) || GNUNET_TIME_timestamp_cmp (now, >, - pk->withdraw_valid_until) ) ) + pk->withdraw_valid_until) ) && + (age_restricted == (0 != pk->key.age_mask.mask)) ) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, diff --git a/src/util/crypto.c b/src/util/crypto.c index 178db3aad..0071c2d72 100644 --- a/src/util/crypto.c +++ b/src/util/crypto.c @@ -20,11 +20,16 @@ * @author Florian Dold * @author Benedikt Mueller * @author Christian Grothoff + * @author Özgür Kesim */ #include "platform.h" #include "taler_util.h" #include +/** + * Used in TALER_AgeCommitmentHash_isNullOrZero for comparison + */ +const struct TALER_AgeCommitmentHash TALER_ZeroAgeCommitmentHash = {0}; /** * Function called by libgcrypt on serious errors. @@ -83,12 +88,11 @@ TALER_test_coin_valid (const struct TALER_CoinPublicInfo *coin_public_info, GNUNET_memcmp (&d_hash, &coin_public_info->denom_pub_hash)); #endif - // FIXME-Oec: replace with function that - // also hashes the age vector if we have - // one! - GNUNET_CRYPTO_hash (&coin_public_info->coin_pub, - sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey), - &c_hash.hash); + + TALER_coin_pub_hash (&coin_public_info->coin_pub, + &coin_public_info->age_commitment_hash, + &c_hash); + if (GNUNET_OK != TALER_denom_pub_verify (denom_pub, &coin_public_info->denom_sig, @@ -178,6 +182,7 @@ TALER_planchet_setup_random (struct TALER_PlanchetSecretsP *ps) enum GNUNET_GenericReturnValue TALER_planchet_prepare (const struct TALER_DenominationPublicKey *dk, const struct TALER_PlanchetSecretsP *ps, + const struct TALER_AgeCommitmentHash *ach, struct TALER_CoinPubHash *c_hash, struct TALER_PlanchetDetail *pd) { @@ -188,7 +193,7 @@ TALER_planchet_prepare (const struct TALER_DenominationPublicKey *dk, if (GNUNET_OK != TALER_denom_blind (dk, &ps->blinding_key, - NULL, /* FIXME-Oec */ + ach, &coin_pub, c_hash, &pd->coin_ev, @@ -208,6 +213,7 @@ TALER_planchet_to_coin ( const struct TALER_DenominationPublicKey *dk, const struct TALER_BlindedDenominationSignature *blind_sig, const struct TALER_PlanchetSecretsP *ps, + const struct TALER_AgeCommitmentHash *ach, const struct TALER_CoinPubHash *c_hash, struct TALER_FreshCoin *coin) { @@ -233,6 +239,7 @@ TALER_planchet_to_coin ( } coin->sig = sig; coin->coin_priv = ps->coin_priv; + coin->h_age_commitment = ach; return GNUNET_OK; } @@ -319,10 +326,10 @@ TALER_coin_ev_hash (const void *coin_ev, void TALER_coin_pub_hash (const struct TALER_CoinSpendPublicKeyP *coin_pub, - const struct TALER_AgeHash *age_commitment_hash, + const struct TALER_AgeCommitmentHash *ach, struct TALER_CoinPubHash *coin_h) { - if (NULL == age_commitment_hash) + if (TALER_AgeCommitmentHash_isNullOrZero (ach)) { /* No age commitment was set */ GNUNET_CRYPTO_hash (&coin_pub->eddsa_pub, @@ -334,7 +341,7 @@ TALER_coin_pub_hash (const struct TALER_CoinSpendPublicKeyP *coin_pub, /* Coin comes with age commitment. Take the hash of the age commitment * into account */ const size_t key_s = sizeof(struct GNUNET_CRYPTO_EcdsaPublicKey); - const size_t age_s = sizeof(struct TALER_AgeHash); + const size_t age_s = sizeof(struct TALER_AgeCommitmentHash); char data[key_s + age_s]; GNUNET_memcpy (&data[0], @@ -342,7 +349,7 @@ TALER_coin_pub_hash (const struct TALER_CoinSpendPublicKeyP *coin_pub, key_s); GNUNET_memcpy (&data[key_s], - age_commitment_hash, + ach, age_s); GNUNET_CRYPTO_hash (&data, @@ -352,4 +359,276 @@ TALER_coin_pub_hash (const struct TALER_CoinSpendPublicKeyP *coin_pub, } +void +TALER_age_commitment_hash ( + const struct TALER_AgeCommitment *commitment, + struct TALER_AgeCommitmentHash *ahash) +{ + struct GNUNET_HashContext *hash_context; + struct GNUNET_HashCode hash; + + GNUNET_assert (NULL != ahash); + if (NULL == commitment) + { + memset (ahash, 0, sizeof(struct TALER_AgeCommitmentHash)); + return; + } + + GNUNET_assert (__builtin_popcount (commitment->mask.mask) - 1 == + commitment->num_pub); + + hash_context = GNUNET_CRYPTO_hash_context_start (); + + for (size_t i = 0; i < commitment->num_pub; i++) + { + GNUNET_CRYPTO_hash_context_read (hash_context, + &commitment->pub[i], + sizeof(struct + GNUNET_CRYPTO_EddsaPublicKey)); + } + + GNUNET_CRYPTO_hash_context_finish (hash_context, + &hash); + GNUNET_memcpy (&ahash->shash.bits, + &hash.bits, + sizeof(ahash->shash.bits)); +} + + +/* To a given age value between 0 and 31, returns the index of the age group + * defined by the given mask. + */ +static uint8_t +get_age_group ( + const struct TALER_AgeMask *mask, + uint8_t age) +{ + uint32_t m = mask->mask; + uint8_t i = 0; + + while (m > 0) + { + if (0 >= age) + break; + m = m >> 1; + i += m & 1; + age--; + } + return i; +} + + +enum GNUNET_GenericReturnValue +TALER_age_restriction_commit ( + const struct TALER_AgeMask *mask, + const uint8_t age, + const uint32_t seed, + struct TALER_AgeCommitment *new) +{ + uint8_t num_pub = __builtin_popcount (mask->mask) - 1; + uint8_t num_priv = get_age_group (mask, age) - 1; + size_t i; + + GNUNET_assert (NULL != new); + GNUNET_assert (mask->mask & 1); /* fist bit must have been set */ + GNUNET_assert (0 <= num_priv); + GNUNET_assert (31 > num_priv); + + new->mask.mask = mask->mask; + new->num_pub = num_pub; + new->num_priv = num_priv; + + new->pub = GNUNET_new_array ( + num_pub, + struct TALER_AgeCommitmentPublicKeyP); + new->priv = GNUNET_new_array ( + num_priv, + struct TALER_AgeCommitmentPrivateKeyP); + + /* Create as many private keys as we need */ + for (i = 0; i < num_priv; i++) + { + uint32_t seedBE = htonl (seed + i); + + if (GNUNET_OK != + GNUNET_CRYPTO_kdf (&new->priv[i], + sizeof (new->priv[i]), + &seedBE, + sizeof (seedBE), + "taler-age-commitment-derivation", + strlen ( + "taler-age-commitment-derivation"), + NULL, 0)) + goto FAIL; + + GNUNET_CRYPTO_eddsa_key_get_public (&new->priv[i].eddsa_priv, + &new->pub[i].eddsa_pub); + } + + /* Fill the rest of the public keys with random values */ + for (; ipub[i], + sizeof(new->pub[i])); + + return GNUNET_OK; + +FAIL: + GNUNET_free (new->pub); + GNUNET_free (new->priv); + return GNUNET_SYSERR; +} + + +enum GNUNET_GenericReturnValue +TALER_age_commitment_derive ( + const struct TALER_AgeCommitment *orig, + const uint32_t seed, + struct TALER_AgeCommitment *new) +{ + struct GNUNET_CRYPTO_EccScalar val; + + /* + * age commitment consists of GNUNET_CRYPTO_Eddsa{Private,Public}Key + * + * GNUNET_CRYPTO_EddsaPrivateKey is a + * unsigned char d[256 / 8]; + * + * GNUNET_CRYPTO_EddsaPublicKey is a + * unsigned char q_y[256 / 8]; + * + * We want to multiply, both, the Private Key by an integer factor and the + * public key (point on curve) with the equivalent scalar. + * + * From the seed we will derive + * 1. a scalar to multiply the public keys with + * 2. a factor to multiply the private key with + * + * Invariants: + * point*scalar == public(private*factor) + * + * A point on a curve is GNUNET_CRYPTO_EccPoint which is + * unsigned char v[256 / 8]; + * + * A ECC scaler for use in point multiplications is a + * GNUNET_CRYPTO_EccScalar which is a + * unsigned car v[256 / 8]; + * */ + + GNUNET_assert (NULL != new); + GNUNET_assert (orig->num_pub == __builtin_popcount (orig->mask.mask) - 1); + GNUNET_assert (orig->num_priv <= orig->num_pub); + + new->mask = orig->mask; + new->num_pub = orig->num_pub; + new->num_priv = orig->num_priv; + new->pub = GNUNET_new_array ( + new->num_pub, + struct TALER_AgeCommitmentPublicKeyP); + new->priv = GNUNET_new_array ( + new->num_priv, + struct TALER_AgeCommitmentPrivateKeyP); + + + GNUNET_CRYPTO_ecc_scalar_from_int (seed, &val); + + /* scalar multiply the public keys on the curve */ + for (size_t i = 0; i < orig->num_pub; i++) + { + /* We shift all keys by the same scalar */ + struct GNUNET_CRYPTO_EccPoint *p = (struct + GNUNET_CRYPTO_EccPoint *) &orig->pub[i]; + struct GNUNET_CRYPTO_EccPoint *np = (struct + GNUNET_CRYPTO_EccPoint *) &new->pub[i]; + if (GNUNET_OK != + GNUNET_CRYPTO_ecc_pmul_mpi ( + p, + &val, + np)) + goto FAIL; + + } + + /* multiply the private keys */ + /* we borough ideas from GNUNET_CRYPTO_ecdsa_private_key_derive */ + { + uint32_t seedBE; + uint8_t dc[32]; + gcry_mpi_t f, x, d, n; + gcry_ctx_t ctx; + + GNUNET_assert (0==gcry_mpi_ec_new (&ctx,NULL, "Ed25519")); + n = gcry_mpi_ec_get_mpi ("n", ctx, 1); + + /* make the seed big endian */ + seedBE = GNUNET_htonll (seed); + + GNUNET_CRYPTO_mpi_scan_unsigned (&f, &seedBE, sizeof(seedBE)); + + for (size_t i = 0; i < orig->num_priv; i++) + { + + /* convert to big endian for libgrypt */ + for (size_t j = 0; j < 32; j++) + dc[i] = orig->priv[i].eddsa_priv.d[31 - j]; + GNUNET_CRYPTO_mpi_scan_unsigned (&x, dc, sizeof(dc)); + + d = gcry_mpi_new (256); + gcry_mpi_mulm (d, f, x, n); + gcry_mpi_release (x); + gcry_mpi_release (d); + gcry_mpi_release (n); + gcry_mpi_release (d); + GNUNET_CRYPTO_mpi_print_unsigned (dc, sizeof(dc), d); + + for (size_t j = 0; j <32; j++) + new->priv[i].eddsa_priv.d[j] = dc[31 - 1]; + + sodium_memzero (dc, sizeof(dc)); + + /* TODO: + * make sure that the calculated private key generate the same public + * keys */ + } + + gcry_mpi_release (f); + gcry_ctx_release (ctx); + } + + return GNUNET_OK; + +FAIL: + GNUNET_free (new->pub); + GNUNET_free (new->priv); + return GNUNET_SYSERR; +} + + +void +TALER_age_restriction_commmitment_free_inside ( + struct TALER_AgeCommitment *commitment) +{ + if (NULL == commitment) + return; + + if (NULL != commitment->priv) + { + GNUNET_CRYPTO_zero_keys ( + commitment->priv, + sizeof(*commitment->priv) * commitment->num_priv); + + GNUNET_free (commitment->priv); + commitment->priv = NULL; + } + + if (NULL != commitment->pub) + { + GNUNET_free (commitment->pub); + commitment->priv = NULL; + } + + /* Caller is responsible for commitment itself */ +} + + /* end of crypto.c */ diff --git a/src/util/denom.c b/src/util/denom.c index b6b3764da..f7ab6ad1e 100644 --- a/src/util/denom.c +++ b/src/util/denom.c @@ -235,7 +235,7 @@ TALER_denom_priv_to_pub (const struct TALER_DenominationPrivateKey *denom_priv, enum GNUNET_GenericReturnValue TALER_denom_blind (const struct TALER_DenominationPublicKey *dk, const union TALER_DenominationBlindingKeyP *coin_bks, - const struct TALER_AgeHash *age_commitment_hash, + const struct TALER_AgeCommitmentHash *ach, const struct TALER_CoinSpendPublicKeyP *coin_pub, struct TALER_CoinPubHash *c_hash, void **coin_ev, @@ -245,7 +245,7 @@ TALER_denom_blind (const struct TALER_DenominationPublicKey *dk, { case TALER_DENOMINATION_RSA: TALER_coin_pub_hash (coin_pub, - age_commitment_hash, + ach, c_hash); if (GNUNET_YES != GNUNET_CRYPTO_rsa_blind (&c_hash->hash, diff --git a/src/util/test_crypto.c b/src/util/test_crypto.c index 5ee06487b..c1f5d4e2a 100644 --- a/src/util/test_crypto.c +++ b/src/util/test_crypto.c @@ -101,6 +101,7 @@ test_planchets (void) GNUNET_assert (GNUNET_OK == TALER_planchet_prepare (&dk_pub, &ps, + NULL, /* no age commitment */ &c_hash, &pd)); GNUNET_assert (GNUNET_OK == @@ -112,6 +113,62 @@ test_planchets (void) TALER_planchet_to_coin (&dk_pub, &blind_sig, &ps, + NULL, /* no age commitment */ + &c_hash, + &coin)); + TALER_blinded_denom_sig_free (&blind_sig); + TALER_denom_sig_free (&coin.sig); + TALER_denom_priv_free (&dk_priv); + TALER_denom_pub_free (&dk_pub); + return 0; +} + + +/** + * Test the basic planchet functionality of creating a fresh planchet and + * extracting the respective signature, this time _with_ age commitment. + * + * @return 0 on success + */ +static int +test_planchets_with_age_commitment (void) +{ + struct TALER_PlanchetSecretsP ps; + struct TALER_AgeCommitmentHash ach; + struct TALER_DenominationPrivateKey dk_priv; + struct TALER_DenominationPublicKey dk_pub; + struct TALER_PlanchetDetail pd; + struct TALER_BlindedDenominationSignature blind_sig; + struct TALER_FreshCoin coin; + struct TALER_CoinPubHash c_hash; + + GNUNET_assert (GNUNET_OK == + TALER_denom_priv_create (&dk_priv, + &dk_pub, + TALER_DENOMINATION_RSA, + 1024)); + TALER_planchet_setup_random (&ps); + GNUNET_CRYPTO_random_block ( + GNUNET_CRYPTO_QUALITY_WEAK, + &ach, + sizeof(struct TALER_AgeCommitmentHash)); + + GNUNET_assert (GNUNET_OK == + TALER_planchet_prepare (&dk_pub, + &ps, + &ach, + &c_hash, + &pd)); + GNUNET_assert (GNUNET_OK == + TALER_denom_sign_blinded (&blind_sig, + &dk_priv, + pd.coin_ev, + pd.coin_ev_size)); + GNUNET_assert (GNUNET_OK == + TALER_planchet_to_coin (&dk_pub, + &blind_sig, + &ps, + &ach, &c_hash, &coin)); TALER_blinded_denom_sig_free (&blind_sig); @@ -221,10 +278,12 @@ main (int argc, return 1; if (0 != test_planchets ()) return 2; - if (0 != test_exchange_sigs ()) + if (0 != test_planchets_with_age_commitment ()) return 3; - if (0 != test_merchant_sigs ()) + if (0 != test_exchange_sigs ()) return 4; + if (0 != test_merchant_sigs ()) + return 5; return 0; } diff --git a/src/util/test_helper_rsa.c b/src/util/test_helper_rsa.c index ac4ae1dc0..ae9f62f52 100644 --- a/src/util/test_helper_rsa.c +++ b/src/util/test_helper_rsa.c @@ -268,9 +268,13 @@ test_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh) enum TALER_ErrorCode ec; bool success = false; struct TALER_PlanchetSecretsP ps; + struct TALER_AgeCommitmentHash ach; struct TALER_CoinPubHash c_hash; TALER_planchet_setup_random (&ps); + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + &ach, + sizeof(ach)); for (unsigned int i = 0; i