exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

commit 313a7c671094ff33aafda645d582774a534a1501
parent 352453ae63cda873f4535e83da96298937495392
Author: Christian Grothoff <grothoff@gnunet.org>
Date:   Tue,  3 Feb 2026 12:31:30 +0100

Merge branch 'master' of git+ssh://git.taler.net/exchange

Diffstat:
Msrc/bank-lib/bank_api_account_token.c | 18+++---------------
Msrc/bank-lib/bank_api_parse.c | 17++++++++++++++++-
Msrc/bank-lib/taler-exchange-wire-gateway-client.c | 130++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Msrc/include/taler/taler_bank_service.h | 9+++++++++
Msrc/testing/test_exchange_api.conf | 20++++++++++++++++++++
Msrc/util/secmod_cs.c | 509+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Msrc/util/secmod_rsa.c | 518+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Msrc/util/test_helper_cs.conf | 2+-
Msrc/util/test_helper_rsa.c | 5+++--
Msrc/util/test_helper_rsa.conf | 2+-
10 files changed, 813 insertions(+), 417 deletions(-)

diff --git a/src/bank-lib/bank_api_account_token.c b/src/bank-lib/bank_api_account_token.c @@ -206,17 +206,9 @@ TALER_BANK_account_token ( ath = GNUNET_new (struct TALER_BANK_AccountTokenHandle); ath->cb = res_cb; ath->cb_cls = res_cb_cls; - { - char *path; - - GNUNET_asprintf (&path, - "accounts/%s/token", - account_name); - ath->request_url = TALER_url_join (auth->wire_gateway_url, - path, - NULL); - GNUNET_free (path); - } + ath->request_url = TALER_url_join (auth->core_bank_url, + "token", + NULL); if (NULL == ath->request_url) { GNUNET_free (ath); @@ -226,10 +218,6 @@ TALER_BANK_account_token ( GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Requesting access token at `%s'\n", ath->request_url); - ath->post_ctx.headers - = curl_slist_append ( - ath->post_ctx.headers, - "Content-Type: application/json"); eh = curl_easy_init (); if ( (NULL == eh) || (GNUNET_OK != diff --git a/src/bank-lib/bank_api_parse.c b/src/bank-lib/bank_api_parse.c @@ -40,6 +40,22 @@ TALER_BANK_auth_parse_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg, }; char *method; + auth->core_bank_url = NULL; + if (GNUNET_OK == + GNUNET_CONFIGURATION_get_value_string (cfg, + section, + "CORE_BANK_URL", + &auth->core_bank_url)) + { + if (! TALER_is_web_url (auth->core_bank_url)) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section, + "CORE_BANK_URL", + "Not a valid URL"); + return GNUNET_SYSERR; + } + } if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, section, @@ -51,7 +67,6 @@ TALER_BANK_auth_parse_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg, "WIRE_GATEWAY_URL"); return GNUNET_SYSERR; } - if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, section, diff --git a/src/bank-lib/taler-exchange-wire-gateway-client.c b/src/bank-lib/taler-exchange-wire-gateway-client.c @@ -93,6 +93,11 @@ static struct TALER_BANK_CreditHistoryHandle *chh; static struct TALER_BANK_DebitHistoryHandle *dhh; /** + * Handle to fetch an access token. + */ +static struct TALER_BANK_AccountTokenHandle *ath; + +/** * Handle for executing the wire transfer. */ static struct TALER_BANK_TransferHandle *eh; @@ -133,6 +138,11 @@ do_shutdown (void *cls) TALER_BANK_debit_history_cancel (dhh); dhh = NULL; } + if (NULL != ath) + { + TALER_BANK_account_token_cancel (ath); + ath = NULL; + } if (NULL != eh) { TALER_BANK_transfer_cancel (eh); @@ -554,6 +564,76 @@ execute_admin_transfer (void) /** + * Run the actual main operation requested by the user. + */ +static void +execute_tasks (void) +{ + if (GNUNET_YES == incoming_history) + { + execute_credit_history (); + return; + } + if (GNUNET_YES == outgoing_history) + { + execute_debit_history (); + return; + } + if (NULL != credit_account.full_payto) + { + execute_wire_transfer (); + return; + } + if (NULL != debit_account.full_payto) + { + execute_admin_transfer (); + return; + } + + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "No operation specified.\n"); + global_ret = 0; + GNUNET_SCHEDULER_shutdown (); +} + + +/** + * Receives an access token to the bank. + * + * @param cls closure + * @param atr response details + */ +static void +access_token_cb ( + void *cls, + const struct TALER_BANK_AccountTokenResponse *atr) +{ + (void) cls; + ath = NULL; + switch (atr->ec) + { + case TALER_EC_NONE: + break; /* continued below */ + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to get access token: %s (%u/%d)\n", + TALER_ErrorCode_get_hint (atr->ec), + atr->http_status, + (int) atr->ec); + global_ret = EXIT_NOPERMISSION; + GNUNET_SCHEDULER_shutdown (); + return; + } + GNUNET_assert (TALER_BANK_AUTH_BASIC == auth.method); + GNUNET_free (auth.details.basic.username); + GNUNET_free (auth.details.basic.password); + auth.method = TALER_BANK_AUTH_BEARER; + auth.details.bearer.token = GNUNET_strdup (atr->details.ok.access_token); + execute_tasks (); +} + + +/** * Main function that will be run. * * @param cls closure @@ -567,6 +647,7 @@ run (void *cls, const char *cfgfile, const struct GNUNET_CONFIGURATION_Handle *cfg) { + enum TALER_BANK_TokenScope scope; (void) cls; (void) args; (void) cfgfile; @@ -653,31 +734,38 @@ run (void *cls, GNUNET_SCHEDULER_shutdown (); return; } - if (GNUNET_YES == incoming_history) - { - execute_credit_history (); - return; - } - if (GNUNET_YES == outgoing_history) + if ( (NULL != auth.core_bank_url) && + (TALER_BANK_AUTH_BASIC == auth.method) ) { - execute_debit_history (); - return; - } - if (NULL != credit_account.full_payto) - { - execute_wire_transfer (); - return; + scope = TALER_BANK_TOKEN_SCOPE_READONLY; + if (NULL != credit_account.full_payto) + scope = TALER_BANK_TOKEN_SCOPE_WIREGATEWAY; + if (NULL != debit_account.full_payto) + scope = TALER_BANK_TOKEN_SCOPE_READWRITE; + ath = TALER_BANK_account_token (ctx, + &auth, + auth.details.basic.username, // FIXME: why? correct? + scope, + false, /* refreshable */ + "taler-exchange-wire-gateway-client CLI token", + GNUNET_TIME_UNIT_MINUTES, + &access_token_cb, + NULL); + if (NULL == ath) + { + GNUNET_break (0); + GNUNET_SCHEDULER_shutdown (); + return; + } } - if (NULL != debit_account.full_payto) + else { - execute_admin_transfer (); - return; + if (TALER_BANK_AUTH_BASIC == auth.method) + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "No CORE_BANK_URL given in `%s' and using basic authentication. Not all taler-wire-gateway implementations allow this.\n", + account_section); + execute_tasks (); } - - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "No operation specified.\n"); - global_ret = 0; - GNUNET_SCHEDULER_shutdown (); } diff --git a/src/include/taler/taler_bank_service.h b/src/include/taler/taler_bank_service.h @@ -69,6 +69,15 @@ struct TALER_BANK_AuthenticationData char *wire_gateway_url; /** + * Base URL including "/accpunts/$USERNAME/" to use + * to talk to the core bank API. Useful to get a more + * specific access token instead of using basic authentication + * the whole time. Optional, can be NULL (as we do not + * require the core bank API to actually always be available). + */ + char *core_bank_url; + + /** * Which authentication method should we use? */ enum TALER_BANK_AuthenticationMethod method; diff --git a/src/testing/test_exchange_api.conf b/src/testing/test_exchange_api.conf @@ -27,6 +27,7 @@ SERVE = tcp PORT = 8082 PWD_HASH_CONFIG = { "cost": 4 } PWD_AUTH_COMPAT = yes +BASE_URL = http://localhost:8082/ [libeufin-bankdb-postgres] CONFIG = postgresql:///talercheck @@ -113,6 +114,7 @@ WIRE_GATEWAY_AUTH_METHOD = basic USERNAME = Exchange PASSWORD = password WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/" +CORE_BANK_URL = "http://localhost:8082/accounts/2/" [admin-accountcredentials-2] WIRE_GATEWAY_AUTH_METHOD = basic @@ -121,6 +123,24 @@ USERNAME = Exchange PASSWORD = password WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/" +[exchange-account-3] +PAYTO_URI = "payto://x-taler-bank/localhost/exchange?receiver-name=Exchange" +ENABLE_DEBIT = YES +ENABLE_CREDIT = YES + +[exchange-accountcredentials-3] +WIRE_GATEWAY_AUTH_METHOD = basic +USERNAME = exchange +PASSWORD = password +WIRE_GATEWAY_URL = "http://localhost:8082/accounts/exchange/taler-wire-gateway/" +CORE_BANK_URL = "http://localhost:8082/accounts/exchange/" + +[admin-accountcredentials-3] +WIRE_GATEWAY_AUTH_METHOD = basic +USERNAME = exchange +PASSWORD = password +WIRE_GATEWAY_URL = "http://localhost:8082/accounts/exchange/taler-wire-gateway/" + [exchange-offline] diff --git a/src/util/secmod_cs.c b/src/util/secmod_cs.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2024 Taler Systems SA + Copyright (C) 2014-2026 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 @@ -98,7 +98,12 @@ struct DenominationKey /** * Time at which this key is supposed to become valid. */ - struct GNUNET_TIME_Timestamp anchor; + struct GNUNET_TIME_Timestamp anchor_start; + + /** + * Time at which this key is supposed to expire (exclusive). + */ + struct GNUNET_TIME_Timestamp anchor_end; /** * Generation when this key was created or revoked. @@ -384,6 +389,7 @@ generate_response (struct DenominationKey *dk) struct TALER_CRYPTO_CsKeyAvailableNotification *an; void *p; size_t tlen; + struct GNUNET_TIME_Relative effective_duration; GNUNET_assert (sizeof(dk->denom_pub) < UINT16_MAX); GNUNET_assert (nlen < UINT16_MAX); @@ -393,13 +399,16 @@ generate_response (struct DenominationKey *dk) an->header.size = htons ((uint16_t) tlen); an->header.type = htons (TALER_HELPER_CS_MT_AVAIL); an->section_name_len = htons ((uint16_t) nlen); - an->anchor_time = GNUNET_TIME_timestamp_hton (dk->anchor); - an->duration_withdraw = GNUNET_TIME_relative_hton (denom->duration_withdraw); + an->anchor_time = GNUNET_TIME_timestamp_hton (dk->anchor_start); + effective_duration = GNUNET_TIME_absolute_get_difference ( + dk->anchor_start.abs_time, + dk->anchor_end.abs_time); + an->duration_withdraw = GNUNET_TIME_relative_hton (effective_duration); an->denom_pub = dk->denom_pub; TALER_exchange_secmod_cs_sign (&dk->h_cs, denom->section, - dk->anchor, - denom->duration_withdraw, + dk->anchor_start, + effective_duration, &TES_smpriv, &an->secm_sig); an->secm_pub = TES_smpub; @@ -440,7 +449,7 @@ do_sign (const struct TALER_CsPubHashP *h_cs, GNUNET_h2s (&h_cs->hash)); return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN; } - if (GNUNET_TIME_absolute_is_future (dk->anchor.abs_time)) + if (GNUNET_TIME_absolute_is_future (dk->anchor_start.abs_time)) { GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); GNUNET_log (GNUNET_ERROR_TYPE_INFO, @@ -448,6 +457,18 @@ do_sign (const struct TALER_CsPubHashP *h_cs, GNUNET_h2s (&h_cs->hash)); return TALER_EC_EXCHANGE_DENOMINATION_HELPER_TOO_EARLY; } + if (GNUNET_TIME_absolute_is_past (dk->anchor_end.abs_time)) + { + /* it is too late; now, usually we should never get here + as we delete upon expiration, so this is just conservative */ + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Signing request failed, denomination key %s is expired (%llu)\n", + GNUNET_h2s (&h_cs->hash), + (unsigned long long) dk->anchor_end.abs_time.abs_value_us); + /* usually we delete upon expiratoin, hence same EC */ + return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN; + } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Received request to sign over bytes with key %s\n", GNUNET_h2s (&h_cs->hash)); @@ -603,7 +624,7 @@ do_derive (const struct TALER_CsPubHashP *h_cs, GNUNET_h2s (&h_cs->hash)); return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN; } - if (GNUNET_TIME_absolute_is_future (dk->anchor.abs_time)) + if (GNUNET_TIME_absolute_is_future (dk->anchor_start.abs_time)) { GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); GNUNET_log (GNUNET_ERROR_TYPE_INFO, @@ -611,6 +632,18 @@ do_derive (const struct TALER_CsPubHashP *h_cs, GNUNET_h2s (&h_cs->hash)); return TALER_EC_EXCHANGE_DENOMINATION_HELPER_TOO_EARLY; } + if (GNUNET_TIME_absolute_is_past (dk->anchor_end.abs_time)) + { + /* it is too late; now, usually we should never get here + as we delete upon expiration, so this is just conservative */ + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Signing request failed, denomination key %s is expired (%llu)\n", + GNUNET_h2s (&h_cs->hash), + (unsigned long long) dk->anchor_end.abs_time.abs_value_us); + /* usually we delete upon expiratoin, hence same EC */ + return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN; + } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Received request to derive R with key %s\n", GNUNET_h2s (&h_cs->hash)); @@ -1076,13 +1109,17 @@ setup_key (struct DenominationKey *dk, GNUNET_CRYPTO_hash (&pub, sizeof (pub), &dk->h_cs.hash); - GNUNET_asprintf (&dk->filename, - "%s/%s/%llu", - keydir, - denom->section, - (unsigned long long) (dk->anchor.abs_time.abs_value_us - / GNUNET_TIME_UNIT_SECONDS.rel_value_us - )); + GNUNET_asprintf ( + &dk->filename, + "%s/%s/%llu-%llu", + keydir, + denom->section, + (unsigned long long) (dk->anchor_start.abs_time.abs_value_us + / GNUNET_TIME_UNIT_SECONDS.rel_value_us + ), + (unsigned long long) (dk->anchor_end.abs_time.abs_value_us + / GNUNET_TIME_UNIT_SECONDS.rel_value_us + )); if (GNUNET_OK != GNUNET_DISK_fn_write (dk->filename, &priv, @@ -1097,7 +1134,7 @@ setup_key (struct DenominationKey *dk, GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Setup fresh private key %s at %s in `%s' (generation #%llu)\n", GNUNET_h2s (&dk->h_cs.hash), - GNUNET_TIME_timestamp2s (dk->anchor), + GNUNET_TIME_timestamp2s (dk->anchor_start), dk->filename, (unsigned long long) key_gen); dk->denom_priv = priv; @@ -1194,7 +1231,8 @@ handle_revoke_request (struct TES_Client *client, denom = dk->denom; ndk = GNUNET_new (struct DenominationKey); ndk->denom = denom; - ndk->anchor = dk->anchor; + ndk->anchor_start = dk->anchor_start; + ndk->anchor_end = dk->anchor_end; if (GNUNET_OK != setup_key (ndk, dk)) @@ -1485,34 +1523,26 @@ cs_update_client_keys (struct TES_Client *client) /** * Create a new denomination key (we do not have enough). * - * @param[in] denom denomination key to create - * @param now current time to use (to get many keys to use the exact same time) + * @param[in,out] denom denomination key to create + * @param anchor_start when to start key signing validity + * @param anchor_end when to end key signing validity * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue create_key (struct Denomination *denom, - struct GNUNET_TIME_Timestamp now) + struct GNUNET_TIME_Timestamp anchor_start, + struct GNUNET_TIME_Timestamp anchor_end) { struct DenominationKey *dk; - struct GNUNET_TIME_Timestamp anchor; - anchor = now; - if (NULL != denom->keys_tail) - { - struct GNUNET_TIME_Absolute abs; - - abs = GNUNET_TIME_absolute_add (denom->keys_tail->anchor.abs_time, - GNUNET_TIME_relative_subtract ( - denom->duration_withdraw, - overlap_duration)); - if (GNUNET_TIME_absolute_cmp (now.abs_time, - <, - abs)) - anchor = GNUNET_TIME_absolute_to_timestamp (abs); - } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Creating new key for `%s' with start date %s\n", + denom->section, + GNUNET_TIME_timestamp2s (anchor_start)); dk = GNUNET_new (struct DenominationKey); dk->denom = denom; - dk->anchor = anchor; + dk->anchor_start = anchor_start; + dk->anchor_end = anchor_end; if (GNUNET_OK != setup_key (dk, denom->keys_tail)) @@ -1528,95 +1558,61 @@ create_key (struct Denomination *denom, /** - * At what time does this denomination require its next action? - * Basically, the minimum of the withdraw expiration time of the - * oldest denomination key, and the withdraw expiration time of - * the newest denomination key minus the #lookahead_sign time. + * Obtain the maximum withdraw duration of all denominations. * - * @param denom denomination to compute action time for + * Must only be called while the #keys_lock is held. + * + * @return maximum withdraw duration, zero if there are no denominations + */ +static struct GNUNET_TIME_Relative +get_maximum_duration (void) +{ + struct GNUNET_TIME_Relative ret + = GNUNET_TIME_UNIT_ZERO; + + for (struct Denomination *denom = denom_head; + NULL != denom; + denom = denom->next) + { + ret = GNUNET_TIME_relative_max (ret, + denom->duration_withdraw); + } + return ret; +} + + +/** + * At what time do we need to next create keys if we just did? + * + * @return time when to next create keys if we just finished key generation */ static struct GNUNET_TIME_Absolute -denomination_action_time (const struct Denomination *denom) +action_time (void) { - struct DenominationKey *head = denom->keys_head; - struct DenominationKey *tail = denom->keys_tail; - struct GNUNET_TIME_Absolute tt; - - if (NULL == head) - return GNUNET_TIME_UNIT_ZERO_ABS; - tt = GNUNET_TIME_absolute_subtract ( - GNUNET_TIME_absolute_subtract ( - GNUNET_TIME_absolute_add (tail->anchor.abs_time, - denom->duration_withdraw), - lookahead_sign), - overlap_duration); - if (head->rc > 0) - return tt; /* head expiration does not count due to rc > 0 */ - return GNUNET_TIME_absolute_min ( - GNUNET_TIME_absolute_add (head->anchor.abs_time, - denom->duration_withdraw), - tt); + struct GNUNET_TIME_Relative md = get_maximum_duration (); + struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); + uint64_t mod; + + if (GNUNET_TIME_relative_is_zero (md)) + return GNUNET_TIME_UNIT_FOREVER_ABS; + mod = now.abs_value_us % md.rel_value_us; + now.abs_value_us -= mod; + return GNUNET_TIME_absolute_add (now, + md); } /** - * Create new keys and expire ancient keys of the given denomination @a denom. - * Removes the @a denom from the #denom_head DLL and re-insert its at the - * correct location sorted by next maintenance activity. + * Remove all denomination keys of @a denom that have expired. * - * @param[in,out] denom denomination to update material for - * @param now current time to use (to get many keys to use the exact same time) - * @param[in,out] wake set to true if we should wake the clients - * @return #GNUNET_OK on success + * @param[in,out] denom denomination family to remove keys for */ -static enum GNUNET_GenericReturnValue -update_keys (struct Denomination *denom, - struct GNUNET_TIME_Timestamp now, - bool *wake) +static void +remove_expired_denomination_keys (struct Denomination *denom) { - /* create new denomination keys */ - if (NULL != denom->keys_tail) - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Updating keys of denomination `%s', last key %s valid for another %s\n", - denom->section, - GNUNET_h2s (&denom->keys_tail->h_cs.hash), - GNUNET_TIME_relative2s ( - GNUNET_TIME_absolute_get_remaining ( - GNUNET_TIME_absolute_subtract ( - GNUNET_TIME_absolute_add ( - denom->keys_tail->anchor.abs_time, - denom->duration_withdraw), - overlap_duration)), - GNUNET_YES)); - while ( (NULL == denom->keys_tail) || - GNUNET_TIME_absolute_is_past ( - GNUNET_TIME_absolute_subtract ( - GNUNET_TIME_absolute_subtract ( - GNUNET_TIME_absolute_add (denom->keys_tail->anchor.abs_time, - denom->duration_withdraw), - lookahead_sign), - overlap_duration)) ) - { - if (! *wake) - { - key_gen++; - *wake = true; - } - if (GNUNET_OK != - create_key (denom, - now)) - { - GNUNET_break (0); - globals->global_ret = EXIT_FAILURE; - GNUNET_SCHEDULER_shutdown (); - return GNUNET_SYSERR; - } - } - /* remove expired denomination keys */ while ( (NULL != denom->keys_head) && - GNUNET_TIME_absolute_is_past - (GNUNET_TIME_absolute_add (denom->keys_head->anchor.abs_time, - denom->duration_withdraw)) ) + GNUNET_TIME_absolute_is_past ( + denom->keys_head->anchor_end.abs_time) ) { struct DenominationKey *key = denom->keys_head; struct DenominationKey *nxt = key->next; @@ -1641,70 +1637,156 @@ update_keys (struct Denomination *denom, GNUNET_free (key); key = nxt; } +} + - /* Update position of 'denom' in #denom_head DLL: sort by action time */ +/** + * Obtain the end anchor to use at this point. Uses the + * #lookahead_sign and then rounds it up by the maximum + * duration of any denomination to arrive at a globally + * valid end-date. + * + * Must only be called while the #keys_lock is held. + * + * @return end anchor + */ +static struct GNUNET_TIME_Timestamp +get_anchor_end (void) +{ + struct GNUNET_TIME_Relative md = get_maximum_duration (); + struct GNUNET_TIME_Absolute end + = GNUNET_TIME_relative_to_absolute (lookahead_sign); + uint64_t mod; + + if (GNUNET_TIME_relative_is_zero (md)) + return GNUNET_TIME_UNIT_ZERO_TS; + /* Round up 'end' to a multiple of 'md' */ + mod = end.abs_value_us % md.rel_value_us; + end.abs_value_us -= mod; + return GNUNET_TIME_absolute_to_timestamp ( + GNUNET_TIME_absolute_add (end, + md)); +} + + +/** + * Create all denomination keys that are required for our + * desired lookahead and that we do not yet have. + * + * @param[in,out] opt our options + * @param[in,out] wake set to true if we should wake the clients + */ +static void +create_missing_keys (struct TALER_SECMOD_Options *opt, + bool *wake) +{ + struct GNUNET_TIME_Timestamp start; + struct GNUNET_TIME_Timestamp end; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Updating denominations ...\n"); + start = opt->global_now; + GNUNET_assert (0 == pthread_mutex_lock (&keys_lock)); + end = get_anchor_end (); + for (struct Denomination *denom = denom_head; + NULL != denom; + denom = denom->next) { - struct Denomination *before; - struct GNUNET_TIME_Absolute at; + struct GNUNET_TIME_Timestamp anchor_start; + struct GNUNET_TIME_Timestamp anchor_end; + struct GNUNET_TIME_Timestamp next_end; + bool finished = false; - at = denomination_action_time (denom); - GNUNET_CONTAINER_DLL_remove (denom_head, - denom_tail, - denom); - before = NULL; - for (struct Denomination *pos = denom_head; - NULL != pos; - pos = pos->next) + if (NULL != denom->keys_tail) { - if (GNUNET_TIME_absolute_cmp (denomination_action_time (pos), >=, at)) - break; - before = pos; + anchor_start = denom->keys_tail->anchor_end; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Expanding keys of denomination `%s', last key %s valid for another %s\n", + denom->section, + GNUNET_h2s (&denom->keys_tail->h_cs.hash), + GNUNET_TIME_relative2s ( + GNUNET_TIME_absolute_get_remaining ( + anchor_start.abs_time), + true)); } - GNUNET_CONTAINER_DLL_insert_after (denom_head, - denom_tail, - before, - denom); + else + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Starting keys of denomination `%s'\n", + denom->section); + anchor_start = start; + } + finished = GNUNET_TIME_timestamp_cmp (anchor_start, + >=, + end); + while (! finished) + { + anchor_end = GNUNET_TIME_absolute_to_timestamp ( + GNUNET_TIME_absolute_add (anchor_start.abs_time, + denom->duration_withdraw)); + next_end = GNUNET_TIME_absolute_to_timestamp ( + GNUNET_TIME_absolute_add (anchor_end.abs_time, + denom->duration_withdraw)); + if (GNUNET_TIME_timestamp_cmp (next_end, + >, + end)) + { + anchor_end = end; /* extend period to align end periods */ + finished = true; + } + /* adjust start time down to ensure overlap */ + anchor_start = GNUNET_TIME_absolute_to_timestamp ( + GNUNET_TIME_absolute_subtract (anchor_start.abs_time, + overlap_duration)); + if (! *wake) + { + key_gen++; + *wake = true; + } + if (GNUNET_OK != + create_key (denom, + anchor_start, + anchor_end)) + { + GNUNET_break (0); + return; + } + anchor_start = anchor_end; + } + remove_expired_denomination_keys (denom); } - return GNUNET_OK; + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Updating denominations finished ...\n"); } /** * Task run periodically to expire keys and/or generate fresh ones. * - * @param cls NULL + * @param cls the `struct TALER_SECMOD_Options *` */ static void update_denominations (void *cls) { - struct Denomination *denom; - struct GNUNET_TIME_Absolute now; - struct GNUNET_TIME_Timestamp t; + struct TALER_SECMOD_Options *opt = cls; + struct GNUNET_TIME_Absolute at; bool wake = false; (void) cls; keygen_task = NULL; - now = GNUNET_TIME_absolute_get (); - t = GNUNET_TIME_absolute_to_timestamp (now); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Updating denominations ...\n"); - GNUNET_assert (0 == pthread_mutex_lock (&keys_lock)); - do { - denom = denom_head; - if (GNUNET_OK != - update_keys (denom, - t, - &wake)) - return; - } while (denom != denom_head); - GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Updating denominations finished ...\n"); + opt->global_now = GNUNET_TIME_timestamp_get (); + create_missing_keys (opt, + &wake); if (wake) TES_wake_clients (); - keygen_task = GNUNET_SCHEDULER_add_at (denomination_action_time (denom), + at = action_time (); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Next key generation due at %s\n", + GNUNET_TIME_absolute2s (at)); + keygen_task = GNUNET_SCHEDULER_add_at (at, &update_denominations, - NULL); + opt); } @@ -1722,8 +1804,11 @@ parse_key (struct Denomination *denom, { char *anchor_s; char dummy; - unsigned long long anchor_ll; - struct GNUNET_TIME_Timestamp anchor; + unsigned long long anchor_start_ll; + unsigned long long anchor_end_ll; + struct GNUNET_TIME_Timestamp anchor_start; + struct GNUNET_TIME_Timestamp anchor_end; + char *nf = NULL; anchor_s = strrchr (filename, '/'); @@ -1734,28 +1819,77 @@ parse_key (struct Denomination *denom, return; } anchor_s++; - if (1 != sscanf (anchor_s, - "%llu%c", - &anchor_ll, + if (2 != sscanf (anchor_s, + "%llu-%llu%c", + &anchor_start_ll, + &anchor_end_ll, &dummy)) { - /* Filenames in KEYDIR must ONLY be the anchor time in seconds! */ - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Filename `%s' invalid for key file, skipping\n", - filename); - return; + /* try legacy mode */ + if (1 != sscanf (anchor_s, + "%llu%c", + &anchor_start_ll, + &dummy)) + { + /* Filenames in KEYDIR must ONLY be the anchor time in seconds! */ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Filename `%s' invalid for key file, skipping\n", + anchor_s); + return; + } + anchor_start.abs_time.abs_value_us + = anchor_start_ll * GNUNET_TIME_UNIT_SECONDS.rel_value_us; + if (anchor_start_ll != anchor_start.abs_time.abs_value_us + / GNUNET_TIME_UNIT_SECONDS.rel_value_us) + { + /* Integer overflow. Bad, invalid filename. */ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Integer overflow. Filename `%s' invalid for key file, skipping\n", + anchor_s); + return; + } + anchor_end + = GNUNET_TIME_absolute_to_timestamp ( + GNUNET_TIME_absolute_add (anchor_start.abs_time, + denom->duration_withdraw)); + GNUNET_asprintf ( + &nf, + "%s/%s/%llu-%llu", + keydir, + denom->section, + anchor_start_ll, + (unsigned long long) (anchor_end.abs_time.abs_value_us + / GNUNET_TIME_UNIT_SECONDS.rel_value_us)); + /* Try to fix the legacy filename */ + if (0 != + rename (filename, + nf)) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, + "rename", + filename); + GNUNET_free (nf); + } } - anchor.abs_time.abs_value_us - = anchor_ll * GNUNET_TIME_UNIT_SECONDS.rel_value_us; - if (anchor_ll != anchor.abs_time.abs_value_us - / GNUNET_TIME_UNIT_SECONDS.rel_value_us) + else { - /* Integer overflow. Bad, invalid filename. */ - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Filename `%s' invalid for key file, skipping\n", - filename); - return; + anchor_start.abs_time.abs_value_us + = anchor_start_ll * GNUNET_TIME_UNIT_SECONDS.rel_value_us; + anchor_end.abs_time.abs_value_us + = anchor_end_ll * GNUNET_TIME_UNIT_SECONDS.rel_value_us; + if ( (anchor_start_ll != anchor_start.abs_time.abs_value_us + / GNUNET_TIME_UNIT_SECONDS.rel_value_us) || + (anchor_end_ll != anchor_end.abs_time.abs_value_us + / GNUNET_TIME_UNIT_SECONDS.rel_value_us) ) + { + /* Integer overflow. Bad, invalid filename. */ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Integer overflow. Filename `%s' invalid for key file, skipping\n", + anchor_s); + return; + } } + { struct DenominationKey *dk; struct DenominationKey *before; @@ -1763,8 +1897,9 @@ parse_key (struct Denomination *denom, dk = GNUNET_new (struct DenominationKey); dk->denom_priv = *priv; dk->denom = denom; - dk->anchor = anchor; - dk->filename = GNUNET_strdup (filename); + dk->anchor_start = anchor_start; + dk->anchor_end = anchor_end; + dk->filename = (NULL == nf) ? GNUNET_strdup (filename) : nf; GNUNET_CRYPTO_cs_private_key_get_public (priv, &dk->denom_pub); GNUNET_CRYPTO_hash (&dk->denom_pub, @@ -1791,9 +1926,9 @@ parse_key (struct Denomination *denom, NULL != pos; pos = pos->next) { - if (GNUNET_TIME_timestamp_cmp (pos->anchor, + if (GNUNET_TIME_timestamp_cmp (pos->anchor_start, >, - anchor)) + anchor_start)) break; before = pos; } @@ -1958,6 +2093,17 @@ parse_denomination_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg, GNUNET_free (secname); return GNUNET_SYSERR; } + if (GNUNET_TIME_relative_cmp (denom->duration_withdraw, + <, + GNUNET_TIME_UNIT_SECONDS)) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + ct, + "DURATION_WITHDRAW", + "less than one second is not supported"); + GNUNET_free (secname); + return GNUNET_SYSERR; + } if (GNUNET_TIME_relative_cmp (overlap_duration, >=, denom->duration_withdraw)) @@ -2011,7 +2157,6 @@ load_denominations (void *cls, { struct LoadContext *ctx = cls; struct Denomination *denom; - bool wake = true; char *cipher; if ( (0 != strncasecmp (denomination_alias, @@ -2069,9 +2214,6 @@ load_denominations (void *cls, GNUNET_CONTAINER_DLL_insert (denom_head, denom_tail, denom); - update_keys (denom, - ctx->t, - &wake); } @@ -2225,6 +2367,7 @@ TALER_SECMOD_cs_run (void *cls, .ret = GNUNET_OK, .t = opt->global_now }; + bool wake = true; GNUNET_assert (0 == pthread_mutex_lock (&keys_lock)); GNUNET_CONFIGURATION_iterate_sections (cfg, @@ -2237,6 +2380,8 @@ TALER_SECMOD_cs_run (void *cls, GNUNET_SCHEDULER_shutdown (); return; } + create_missing_keys (opt, + &wake); } if (NULL == denom_head) { @@ -2251,5 +2396,5 @@ TALER_SECMOD_cs_run (void *cls, keygen_task = GNUNET_SCHEDULER_add_with_priority ( GNUNET_SCHEDULER_PRIORITY_URGENT, &update_denominations, - NULL); + opt); } diff --git a/src/util/secmod_rsa.c b/src/util/secmod_rsa.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2024 Taler Systems SA + Copyright (C) 2014-2026 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 @@ -97,7 +97,12 @@ struct DenominationKey /** * Time at which this key is supposed to become valid. */ - struct GNUNET_TIME_Timestamp anchor; + struct GNUNET_TIME_Timestamp anchor_start; + + /** + * Time at which this key is supposed to expire (exclusive). + */ + struct GNUNET_TIME_Timestamp anchor_end; /** * Generation when this key was created or revoked. @@ -123,12 +128,12 @@ struct Denomination { /** - * Kept in a DLL. Sorted by #denomination_action_time(). + * Kept in a DLL. */ struct Denomination *next; /** - * Kept in a DLL. Sorted by #denomination_action_time(). + * Kept in a DLL. */ struct Denomination *prev; @@ -354,6 +359,7 @@ generate_response (struct DenominationKey *dk) void *buf; void *p; size_t tlen; + struct GNUNET_TIME_Relative effective_duration; buf_len = GNUNET_CRYPTO_rsa_public_key_encode (dk->denom_pub, &buf); @@ -366,12 +372,19 @@ generate_response (struct DenominationKey *dk) an->header.type = htons (TALER_HELPER_RSA_MT_AVAIL); an->pub_size = htons ((uint16_t) buf_len); an->section_name_len = htons ((uint16_t) nlen); - an->anchor_time = GNUNET_TIME_timestamp_hton (dk->anchor); - an->duration_withdraw = GNUNET_TIME_relative_hton (denom->duration_withdraw); + an->anchor_time = GNUNET_TIME_timestamp_hton (dk->anchor_start); + /* Effective duration is based on denum->duration_withdraw + overlap, + but we may have shifted the 'anchor_end' to align them, thus the + only correct way to determine it is: */ + effective_duration = GNUNET_TIME_absolute_get_difference ( + dk->anchor_start.abs_time, + dk->anchor_end.abs_time); + an->duration_withdraw = GNUNET_TIME_relative_hton (effective_duration); + TALER_exchange_secmod_rsa_sign (&dk->h_rsa, denom->section, - dk->anchor, - denom->duration_withdraw, + dk->anchor_start, + effective_duration, &TES_smpriv, &an->secm_sig); an->secm_pub = TES_smpub; @@ -415,16 +428,28 @@ do_sign (const struct TALER_RsaPubHashP *h_rsa, GNUNET_h2s (&h_rsa->hash)); return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN; } - if (GNUNET_TIME_absolute_is_future (dk->anchor.abs_time)) + if (GNUNET_TIME_absolute_is_future (dk->anchor_start.abs_time)) { /* it is too early */ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Signing request failed, denomination key %s is not yet valid (%llu)\n", GNUNET_h2s (&h_rsa->hash), - (unsigned long long) dk->anchor.abs_time.abs_value_us); + (unsigned long long) dk->anchor_start.abs_time.abs_value_us); return TALER_EC_EXCHANGE_DENOMINATION_HELPER_TOO_EARLY; } + if (GNUNET_TIME_absolute_is_past (dk->anchor_end.abs_time)) + { + /* it is too late; now, usually we should never get here + as we delete upon expiration, so this is just conservative */ + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Signing request failed, denomination key %s is expired (%llu)\n", + GNUNET_h2s (&h_rsa->hash), + (unsigned long long) dk->anchor_end.abs_time.abs_value_us); + /* usually we delete upon expiratoin, hence same EC */ + return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN; + } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Received request to sign over %u bytes with key %s\n", @@ -864,13 +889,15 @@ setup_key (struct DenominationKey *dk, &buf); GNUNET_CRYPTO_rsa_public_key_hash (pub, &dk->h_rsa.hash); - GNUNET_asprintf (&dk->filename, - "%s/%s/%llu", - keydir, - denom->section, - (unsigned long long) (dk->anchor.abs_time.abs_value_us - / GNUNET_TIME_UNIT_SECONDS.rel_value_us - )); + GNUNET_asprintf ( + &dk->filename, + "%s/%s/%llu-%llu", + keydir, + denom->section, + (unsigned long long) (dk->anchor_start.abs_time.abs_value_us + / GNUNET_TIME_UNIT_SECONDS.rel_value_us), + (unsigned long long) (dk->anchor_end.abs_time.abs_value_us + / GNUNET_TIME_UNIT_SECONDS.rel_value_us)); if (GNUNET_OK != GNUNET_DISK_fn_write (dk->filename, buf, @@ -890,7 +917,7 @@ setup_key (struct DenominationKey *dk, GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Setup fresh private key %s at %s in `%s' (generation #%llu)\n", GNUNET_h2s (&dk->h_rsa.hash), - GNUNET_TIME_timestamp2s (dk->anchor), + GNUNET_TIME_timestamp2s (dk->anchor_start), dk->filename, (unsigned long long) key_gen); dk->denom_priv = priv; @@ -989,7 +1016,8 @@ handle_revoke_request (struct TES_Client *client, denom = dk->denom; ndk = GNUNET_new (struct DenominationKey); ndk->denom = denom; - ndk->anchor = dk->anchor; + ndk->anchor_start = dk->anchor_start; + ndk->anchor_end = dk->anchor_end; if (GNUNET_OK != setup_key (ndk, dk)) @@ -1227,34 +1255,26 @@ rsa_update_client_keys (struct TES_Client *client) /** * Create a new denomination key (we do not have enough). * - * @param[in] denom denomination key to create - * @param now current time to use (to get many keys to use the exact same time) + * @param[in,out] denom denomination key to create + * @param anchor_start when to start key signing validity + * @param anchor_end when to end key signing validity * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue create_key (struct Denomination *denom, - struct GNUNET_TIME_Timestamp now) + struct GNUNET_TIME_Timestamp anchor_start, + struct GNUNET_TIME_Timestamp anchor_end) { struct DenominationKey *dk; - struct GNUNET_TIME_Timestamp anchor; - - anchor = now; - if (NULL != denom->keys_tail) - { - struct GNUNET_TIME_Absolute abs; - abs = GNUNET_TIME_absolute_add (denom->keys_tail->anchor.abs_time, - GNUNET_TIME_relative_subtract ( - denom->duration_withdraw, - overlap_duration)); - if (GNUNET_TIME_absolute_cmp (now.abs_time, - <, - abs)) - anchor = GNUNET_TIME_absolute_to_timestamp (abs); - } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Creating new key for `%s' with start date %s\n", + denom->section, + GNUNET_TIME_timestamp2s (anchor_start)); dk = GNUNET_new (struct DenominationKey); dk->denom = denom; - dk->anchor = anchor; + dk->anchor_start = anchor_start; + dk->anchor_end = anchor_end; if (GNUNET_OK != setup_key (dk, denom->keys_tail)) @@ -1270,95 +1290,61 @@ create_key (struct Denomination *denom, /** - * At what time does this denomination require its next action? - * Basically, the minimum of the withdraw expiration time of the - * oldest denomination key, and the withdraw expiration time of - * the newest denomination key minus the #lookahead_sign time. + * Obtain the maximum withdraw duration of all denominations. + * + * Must only be called while the #keys_lock is held. + * + * @return maximum withdraw duration, zero if there are no denominations + */ +static struct GNUNET_TIME_Relative +get_maximum_duration (void) +{ + struct GNUNET_TIME_Relative ret + = GNUNET_TIME_UNIT_ZERO; + + for (struct Denomination *denom = denom_head; + NULL != denom; + denom = denom->next) + { + ret = GNUNET_TIME_relative_max (ret, + denom->duration_withdraw); + } + return ret; +} + + +/** + * At what time do we need to next create keys if we just did? * - * @param denom denomination to compute action time for + * @return time when to next create keys if we just finished key generation */ static struct GNUNET_TIME_Absolute -denomination_action_time (const struct Denomination *denom) +action_time (void) { - struct DenominationKey *head = denom->keys_head; - struct DenominationKey *tail = denom->keys_tail; - struct GNUNET_TIME_Absolute tt; - - if (NULL == head) - return GNUNET_TIME_UNIT_ZERO_ABS; - tt = GNUNET_TIME_absolute_subtract ( - GNUNET_TIME_absolute_subtract ( - GNUNET_TIME_absolute_add (tail->anchor.abs_time, - denom->duration_withdraw), - lookahead_sign), - overlap_duration); - if (head->rc > 0) - return tt; /* head expiration does not count due to rc > 0 */ - return GNUNET_TIME_absolute_min ( - GNUNET_TIME_absolute_add (head->anchor.abs_time, - denom->duration_withdraw), - tt); + struct GNUNET_TIME_Relative md = get_maximum_duration (); + struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); + uint64_t mod; + + if (GNUNET_TIME_relative_is_zero (md)) + return GNUNET_TIME_UNIT_FOREVER_ABS; + mod = now.abs_value_us % md.rel_value_us; + now.abs_value_us -= mod; + return GNUNET_TIME_absolute_add (now, + md); } /** - * Create new keys and expire ancient keys of the given denomination @a denom. - * Removes the @a denom from the #denom_head DLL and re-insert its at the - * correct location sorted by next maintenance activity. + * Remove all denomination keys of @a denom that have expired. * - * @param[in,out] denom denomination to update material for - * @param now current time to use (to get many keys to use the exact same time) - * @param[in,out] wake set to true if we should wake the clients - * @return #GNUNET_OK on success + * @param[in,out] denom denomination family to remove keys for */ -static enum GNUNET_GenericReturnValue -update_keys (struct Denomination *denom, - struct GNUNET_TIME_Timestamp now, - bool *wake) +static void +remove_expired_denomination_keys (struct Denomination *denom) { - /* create new denomination keys */ - if (NULL != denom->keys_tail) - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Updating keys of denomination `%s', last key %s valid for another %s\n", - denom->section, - GNUNET_h2s (&denom->keys_tail->h_rsa.hash), - GNUNET_TIME_relative2s ( - GNUNET_TIME_absolute_get_remaining ( - GNUNET_TIME_absolute_subtract ( - GNUNET_TIME_absolute_add ( - denom->keys_tail->anchor.abs_time, - denom->duration_withdraw), - overlap_duration)), - GNUNET_YES)); - while ( (NULL == denom->keys_tail) || - GNUNET_TIME_absolute_is_past ( - GNUNET_TIME_absolute_subtract ( - GNUNET_TIME_absolute_subtract ( - GNUNET_TIME_absolute_add (denom->keys_tail->anchor.abs_time, - denom->duration_withdraw), - lookahead_sign), - overlap_duration)) ) - { - if (! *wake) - { - key_gen++; - *wake = true; - } - if (GNUNET_OK != - create_key (denom, - now)) - { - GNUNET_break (0); - globals->global_ret = EXIT_FAILURE; - GNUNET_SCHEDULER_shutdown (); - return GNUNET_SYSERR; - } - } - /* remove expired denomination keys */ while ( (NULL != denom->keys_head) && - GNUNET_TIME_absolute_is_past - (GNUNET_TIME_absolute_add (denom->keys_head->anchor.abs_time, - denom->duration_withdraw)) ) + GNUNET_TIME_absolute_is_past ( + denom->keys_head->anchor_end.abs_time)) { struct DenominationKey *key = denom->keys_head; struct DenominationKey *nxt = key->next; @@ -1385,72 +1371,157 @@ update_keys (struct Denomination *denom, GNUNET_free (key); key = nxt; } +} + + +/** + * Obtain the end anchor to use at this point. Uses the + * #lookahead_sign and then rounds it up by the maximum + * duration of any denomination to arrive at a globally + * valid end-date. + * + * Must only be called while the #keys_lock is held. + * + * @return end anchor + */ +static struct GNUNET_TIME_Timestamp +get_anchor_end (void) +{ + struct GNUNET_TIME_Relative md = get_maximum_duration (); + struct GNUNET_TIME_Absolute end + = GNUNET_TIME_relative_to_absolute (lookahead_sign); + uint64_t mod; + + if (GNUNET_TIME_relative_is_zero (md)) + return GNUNET_TIME_UNIT_ZERO_TS; + /* Round up 'end' to a multiple of 'md' */ + mod = end.abs_value_us % md.rel_value_us; + end.abs_value_us -= mod; + return GNUNET_TIME_absolute_to_timestamp ( + GNUNET_TIME_absolute_add (end, + md)); +} + - /* Update position of 'denom' in #denom_head DLL: sort by action time */ +/** + * Create all denomination keys that are required for our + * desired lookahead and that we do not yet have. + * + * @param[in,out] opt our options + * @param[in,out] wake set to true if we should wake the clients + */ +static void +create_missing_keys (struct TALER_SECMOD_Options *opt, + bool *wake) +{ + struct GNUNET_TIME_Timestamp start; + struct GNUNET_TIME_Timestamp end; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Updating denominations ...\n"); + start = opt->global_now; + GNUNET_assert (0 == pthread_mutex_lock (&keys_lock)); + end = get_anchor_end (); + for (struct Denomination *denom = denom_head; + NULL != denom; + denom = denom->next) { - struct Denomination *before; - struct GNUNET_TIME_Absolute at; + struct GNUNET_TIME_Timestamp anchor_start; + struct GNUNET_TIME_Timestamp anchor_end; + struct GNUNET_TIME_Timestamp next_end; + bool finished = false; - at = denomination_action_time (denom); - GNUNET_CONTAINER_DLL_remove (denom_head, - denom_tail, - denom); - before = NULL; - for (struct Denomination *pos = denom_head; - NULL != pos; - pos = pos->next) + if (NULL != denom->keys_tail) { - if (GNUNET_TIME_absolute_cmp (denomination_action_time (pos), - >=, - at)) - break; - before = pos; + anchor_start = denom->keys_tail->anchor_end; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Expanding keys of denomination `%s', last key %s valid for another %s\n", + denom->section, + GNUNET_h2s (&denom->keys_tail->h_rsa.hash), + GNUNET_TIME_relative2s ( + GNUNET_TIME_absolute_get_remaining ( + anchor_start.abs_time), + true)); } - GNUNET_CONTAINER_DLL_insert_after (denom_head, - denom_tail, - before, - denom); + else + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Starting keys of denomination `%s'\n", + denom->section); + anchor_start = start; + } + finished = GNUNET_TIME_timestamp_cmp (anchor_start, + >=, + end); + while (! finished) + { + anchor_end = GNUNET_TIME_absolute_to_timestamp ( + GNUNET_TIME_absolute_add (anchor_start.abs_time, + denom->duration_withdraw)); + next_end = GNUNET_TIME_absolute_to_timestamp ( + GNUNET_TIME_absolute_add (anchor_end.abs_time, + denom->duration_withdraw)); + if (GNUNET_TIME_timestamp_cmp (next_end, + >, + end)) + { + anchor_end = end; /* extend period to align end periods */ + finished = true; + } + /* adjust start time down to ensure overlap */ + anchor_start = GNUNET_TIME_absolute_to_timestamp ( + GNUNET_TIME_absolute_subtract (anchor_start.abs_time, + overlap_duration)); + if (! *wake) + { + key_gen++; + *wake = true; + } + if (GNUNET_OK != + create_key (denom, + anchor_start, + anchor_end)) + { + GNUNET_break (0); + return; + } + anchor_start = anchor_end; + } + remove_expired_denomination_keys (denom); } - return GNUNET_OK; + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Updating denominations finished ...\n"); } /** * Task run periodically to expire keys and/or generate fresh ones. * - * @param cls NULL + * @param cls the `struct TALER_SECMOD_Options *` */ static void update_denominations (void *cls) { - struct Denomination *denom; - struct GNUNET_TIME_Absolute now; - struct GNUNET_TIME_Timestamp t; + struct TALER_SECMOD_Options *opt = cls; + struct GNUNET_TIME_Absolute at; bool wake = false; (void) cls; keygen_task = NULL; - now = GNUNET_TIME_absolute_get (); - t = GNUNET_TIME_absolute_to_timestamp (now); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Updating denominations ...\n"); - GNUNET_assert (0 == pthread_mutex_lock (&keys_lock)); - do { - denom = denom_head; - if (GNUNET_OK != - update_keys (denom, - t, - &wake)) - return; - } while (denom != denom_head); - GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Updating denominations finished ...\n"); + /* update current time, global override no longer applies */ + opt->global_now = GNUNET_TIME_timestamp_get (); + create_missing_keys (opt, + &wake); if (wake) TES_wake_clients (); - keygen_task = GNUNET_SCHEDULER_add_at (denomination_action_time (denom), + at = action_time (); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Next key generation due at %s\n", + GNUNET_TIME_absolute2s (at)); + keygen_task = GNUNET_SCHEDULER_add_at (at, &update_denominations, - NULL); + opt); } @@ -1471,8 +1542,11 @@ parse_key (struct Denomination *denom, struct GNUNET_CRYPTO_RsaPrivateKey *priv; char *anchor_s; char dummy; - unsigned long long anchor_ll; - struct GNUNET_TIME_Timestamp anchor; + unsigned long long anchor_start_ll; + unsigned long long anchor_end_ll; + struct GNUNET_TIME_Timestamp anchor_start; + struct GNUNET_TIME_Timestamp anchor_end; + char *nf = NULL; anchor_s = strrchr (filename, '/'); @@ -1483,27 +1557,75 @@ parse_key (struct Denomination *denom, return; } anchor_s++; - if (1 != sscanf (anchor_s, - "%llu%c", - &anchor_ll, + if (2 != sscanf (anchor_s, + "%llu-%llu%c", + &anchor_start_ll, + &anchor_end_ll, &dummy)) { - /* Filenames in KEYDIR must ONLY be the anchor time in seconds! */ - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Filename `%s' invalid for key file, skipping\n", - filename); - return; + /* try legacy mode */ + if (1 != sscanf (anchor_s, + "%llu%c", + &anchor_start_ll, + &dummy)) + { + /* Filenames in KEYDIR must ONLY be the anchor time in seconds! */ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Filename `%s' invalid for key file, skipping\n", + anchor_s); + return; + } + anchor_start.abs_time.abs_value_us + = anchor_start_ll * GNUNET_TIME_UNIT_SECONDS.rel_value_us; + if (anchor_start_ll != anchor_start.abs_time.abs_value_us + / GNUNET_TIME_UNIT_SECONDS.rel_value_us) + { + /* Integer overflow. Bad, invalid filename. */ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Integer overflow. Filename `%s' invalid for key file, skipping\n", + anchor_s); + return; + } + anchor_end + = GNUNET_TIME_absolute_to_timestamp ( + GNUNET_TIME_absolute_add (anchor_start.abs_time, + denom->duration_withdraw)); + GNUNET_asprintf ( + &nf, + "%s/%s/%llu-%llu", + keydir, + denom->section, + anchor_start_ll, + (unsigned long long) (anchor_end.abs_time.abs_value_us + / GNUNET_TIME_UNIT_SECONDS.rel_value_us)); + /* Try to fix the legacy filename */ + if (0 != + rename (filename, + nf)) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, + "rename", + filename); + GNUNET_free (nf); + } } - anchor.abs_time.abs_value_us - = anchor_ll * GNUNET_TIME_UNIT_SECONDS.rel_value_us; - if (anchor_ll != anchor.abs_time.abs_value_us - / GNUNET_TIME_UNIT_SECONDS.rel_value_us) + else { - /* Integer overflow. Bad, invalid filename. */ - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Filename `%s' invalid for key file, skipping\n", - filename); - return; + anchor_start.abs_time.abs_value_us + = anchor_start_ll * GNUNET_TIME_UNIT_SECONDS.rel_value_us; + anchor_end.abs_time.abs_value_us + = anchor_end_ll * GNUNET_TIME_UNIT_SECONDS.rel_value_us; + if ( (anchor_start_ll != anchor_start.abs_time.abs_value_us + / GNUNET_TIME_UNIT_SECONDS.rel_value_us) || + (anchor_end_ll != anchor_end.abs_time.abs_value_us + / GNUNET_TIME_UNIT_SECONDS.rel_value_us) ) + { + /* Integer overflow. Bad, invalid filename. */ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Integer overflow. Filename `%s' invalid for key file, skipping\n", + anchor_s); + return; + } } priv = GNUNET_CRYPTO_rsa_private_key_decode (buf, buf_size); @@ -1512,7 +1634,8 @@ parse_key (struct Denomination *denom, /* Parser failure. */ GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "File `%s' is malformed, skipping\n", - filename); + (NULL == nf) ? filename : nf); + GNUNET_free (nf); return; } @@ -1526,13 +1649,15 @@ parse_key (struct Denomination *denom, { GNUNET_break (0); GNUNET_CRYPTO_rsa_private_key_free (priv); + GNUNET_free (nf); return; } dk = GNUNET_new (struct DenominationKey); dk->denom_priv = priv; dk->denom = denom; - dk->anchor = anchor; - dk->filename = GNUNET_strdup (filename); + dk->anchor_start = anchor_start; + dk->anchor_end = anchor_end; + dk->filename = (NULL == nf) ? GNUNET_strdup (filename) : nf; GNUNET_CRYPTO_rsa_public_key_hash (pub, &dk->h_rsa.hash); dk->denom_pub = pub; @@ -1559,9 +1684,9 @@ parse_key (struct Denomination *denom, NULL != pos; pos = pos->next) { - if (GNUNET_TIME_timestamp_cmp (pos->anchor, + if (GNUNET_TIME_timestamp_cmp (pos->anchor_start, >, - anchor)) + anchor_start)) break; before = pos; } @@ -1728,6 +1853,17 @@ parse_denomination_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg, GNUNET_free (secname); return GNUNET_SYSERR; } + if (GNUNET_TIME_relative_cmp (denom->duration_withdraw, + <, + GNUNET_TIME_UNIT_SECONDS)) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + ct, + "DURATION_WITHDRAW", + "less than one second is not supported"); + GNUNET_free (secname); + return GNUNET_SYSERR; + } if (GNUNET_TIME_relative_cmp (overlap_duration, >=, denom->duration_withdraw)) @@ -1786,11 +1922,6 @@ struct LoadContext const char *cprefix; /** - * Current time to use. - */ - struct GNUNET_TIME_Timestamp t; - - /** * Status, to be set to #GNUNET_SYSERR on failure */ enum GNUNET_GenericReturnValue ret; @@ -1810,7 +1941,6 @@ load_denominations (void *cls, { struct LoadContext *ctx = cls; struct Denomination *denom; - bool wake = true; char *cipher; if (0 != strncasecmp (denomination_alias, @@ -1828,7 +1958,8 @@ load_denominations (void *cls, "CIPHER"); return; } - if (0 != strcmp (cipher, "RSA")) + if (0 != strcmp (cipher, + "RSA")) { GNUNET_free (cipher); return; /* Ignore denominations of other types than CS */ @@ -1864,9 +1995,6 @@ load_denominations (void *cls, GNUNET_CONTAINER_DLL_insert (denom_head, denom_tail, denom); - update_keys (denom, - ctx->t, - &wake); } @@ -2019,9 +2147,9 @@ TALER_SECMOD_rsa_run (void *cls, struct LoadContext lc = { .cfg = cfg, .ret = GNUNET_OK, - .t = opt->global_now, .cprefix = opt->cprefix }; + bool wake = true; GNUNET_assert (0 == pthread_mutex_lock (&keys_lock)); GNUNET_CONFIGURATION_iterate_sections (cfg, @@ -2034,6 +2162,8 @@ TALER_SECMOD_rsa_run (void *cls, GNUNET_SCHEDULER_shutdown (); return; } + create_missing_keys (opt, + &wake); } if (NULL == denom_head) { @@ -2048,5 +2178,5 @@ TALER_SECMOD_rsa_run (void *cls, keygen_task = GNUNET_SCHEDULER_add_with_priority ( GNUNET_SCHEDULER_PRIORITY_URGENT, &update_denominations, - NULL); + opt); } diff --git a/src/util/test_helper_cs.conf b/src/util/test_helper_cs.conf @@ -1,6 +1,6 @@ [PATHS] # Persistent data storage for the testcase -TALER_TEST_HOME = test_helper_cs_home/ +TALER_TEST_HOME = test_helper_cs_home [coin_1] DURATION_WITHDRAW = 1 minute diff --git a/src/util/test_helper_rsa.c b/src/util/test_helper_rsa.c @@ -931,6 +931,7 @@ int main (int argc, const char *const argv[]) { + const char *loglevel = "WARNING"; struct GNUNET_OS_Process *helper; char *libexec_dir; char *binary_name; @@ -943,7 +944,7 @@ main (int argc, unsetenv ("XDG_DATA_HOME"); unsetenv ("XDG_CONFIG_HOME"); GNUNET_log_setup ("test-helper-rsa", - "WARNING", + loglevel, NULL); libexec_dir = GNUNET_OS_installation_get_path (TALER_EXCHANGE_project_data (), GNUNET_OS_IPK_BINDIR); @@ -959,7 +960,7 @@ main (int argc, "-c", "test_helper_rsa.conf", "-L", - "WARNING", + loglevel, NULL); if (NULL == helper) { diff --git a/src/util/test_helper_rsa.conf b/src/util/test_helper_rsa.conf @@ -1,6 +1,6 @@ [PATHS] # Persistent data storage for the testcase -TALER_TEST_HOME = test_helper_rsa_home/ +TALER_TEST_HOME = test_helper_rsa_home [coin_1] DURATION_WITHDRAW = 1 minute