diff options
Diffstat (limited to 'src/exchange/taler-exchange-httpd_keys.c')
-rw-r--r-- | src/exchange/taler-exchange-httpd_keys.c | 2789 |
1 files changed, 2285 insertions, 504 deletions
diff --git a/src/exchange/taler-exchange-httpd_keys.c b/src/exchange/taler-exchange-httpd_keys.c index 705900206..0ec28e950 100644 --- a/src/exchange/taler-exchange-httpd_keys.c +++ b/src/exchange/taler-exchange-httpd_keys.c @@ -1,31 +1,35 @@ /* - This file is part of TALER - Copyright (C) 2020, 2021 Taler Systems SA + This file is part of TALER + Copyright (C) 2020-2023 Taler Systems SA - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU Affero General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + 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 Affero General Public License for more details. + 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 Affero General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with - TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> -*/ + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ /** * @file taler-exchange-httpd_keys.c * @brief management of our various keys * @author Christian Grothoff + * @author Özgür Kesim */ #include "platform.h" #include "taler_json_lib.h" #include "taler_mhd_lib.h" +#include "taler_kyclogic_lib.h" #include "taler_dbevents.h" #include "taler-exchange-httpd.h" +#include "taler-exchange-httpd_config.h" #include "taler-exchange-httpd_keys.h" #include "taler-exchange-httpd_responses.h" #include "taler_exchangedb_plugin.h" +#include "taler_extensions.h" /** @@ -36,21 +40,9 @@ /** - * Taler protocol version in the format CURRENT:REVISION:AGE - * as used by GNU libtool. See - * https://www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html - * - * Please be very careful when updating and follow - * https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html#Updating-version-info - * precisely. Note that this version has NOTHING to do with the - * release version, and the format is NOT the same that semantic - * versioning uses either. - * - * When changing this version, you likely want to also update - * #TALER_PROTOCOL_CURRENT and #TALER_PROTOCOL_AGE in - * exchange_api_handle.c! + * When do we forcefully timeout a /keys request? */ -#define EXCHANGE_PROTOCOL_VERSION "9:0:0" +#define KEYS_TIMEOUT GNUNET_TIME_UNIT_MINUTES /** @@ -62,7 +54,7 @@ struct HelperDenomination /** * When will the helper start to use this key for signing? */ - struct GNUNET_TIME_Absolute start_time; + struct GNUNET_TIME_Timestamp start_time; /** * For how long will the helper allow signing? 0 if @@ -71,9 +63,9 @@ struct HelperDenomination struct GNUNET_TIME_Relative validity_duration; /** - * Hash of the denomination key. + * Hash of the full denomination key. */ - struct GNUNET_HashCode h_denom_pub; + struct TALER_DenominationHashP h_denom_pub; /** * Signature over this key from the security module's key. @@ -86,10 +78,29 @@ struct HelperDenomination struct TALER_DenominationPublicKey denom_pub; /** + * Details depend on the @e denom_pub.cipher type. + */ + union + { + + /** + * Hash of the RSA key. + */ + struct TALER_RsaPubHashP h_rsa; + + /** + * Hash of the CS key. + */ + struct TALER_CsPubHashP h_cs; + + } h_details; + + /** * Name in configuration section for this denomination type. */ char *section_name; + }; @@ -129,7 +140,7 @@ struct HelperSignkey /** * When will the helper start to use this key for signing? */ - struct GNUNET_TIME_Absolute start_time; + struct GNUNET_TIME_Timestamp start_time; /** * For how long will the helper allow signing? 0 if @@ -151,10 +162,9 @@ struct HelperSignkey /** - * State associated with the crypto helpers / security modules. - * Created per-thread, but NOT updated when the #key_generation - * is updated (instead constantly kept in sync whenever - * #TEH_keys_get_state() is called). + * State associated with the crypto helpers / security modules. NOT updated + * when the #key_generation is updated (instead constantly kept in sync + * whenever #TEH_keys_get_state() is called). */ struct HelperState { @@ -167,7 +177,12 @@ struct HelperState /** * Handle for the denom/RSA helper. */ - struct TALER_CRYPTO_DenominationHelper *dh; + struct TALER_CRYPTO_RsaDenominationHelper *rsadh; + + /** + * Handle for the denom/CS helper. + */ + struct TALER_CRYPTO_CsDenominationHelper *csdh; /** * Map from H(denom_pub) to `struct HelperDenomination` entries. @@ -175,6 +190,16 @@ struct HelperState struct GNUNET_CONTAINER_MultiHashMap *denom_keys; /** + * Map from H(rsa_pub) to `struct HelperDenomination` entries. + */ + struct GNUNET_CONTAINER_MultiHashMap *rsa_keys; + + /** + * Map from H(cs_pub) to `struct HelperDenomination` entries. + */ + struct GNUNET_CONTAINER_MultiHashMap *cs_keys; + + /** * Map from `struct TALER_ExchangePublicKey` to `struct HelperSignkey` * entries. Based on the fact that a `struct GNUNET_PeerIdentity` is also * an EdDSA public key. @@ -203,12 +228,17 @@ struct KeysResponseData struct MHD_Response *response_uncompressed; /** + * ETag for these responses. + */ + char *etag; + + /** * Cherry-picking timestamp the client must have set for this * response to be valid. 0 if this is the "full" response. * The client's request must include this date or a higher one * for this response to be applicable. */ - struct GNUNET_TIME_Absolute cherry_pick_date; + struct GNUNET_TIME_Timestamp cherry_pick_date; }; @@ -238,7 +268,6 @@ struct SigningKey }; - struct TEH_KeyStateHandle { @@ -256,12 +285,28 @@ struct TEH_KeyStateHandle struct GNUNET_CONTAINER_MultiPeerMap *signkey_map; /** + * Head of DLL of our global fees. + */ + struct TEH_GlobalFee *gf_head; + + /** + * Tail of DLL of our global fees. + */ + struct TEH_GlobalFee *gf_tail; + + /** * json array with the auditors of this exchange. Contains exactly * the information needed for the "auditors" field of the /keys response. */ json_t *auditors; /** + * json array with the global fees of this exchange. Contains exactly + * the information needed for the "global_fees" field of the /keys response. + */ + json_t *global_fees; + + /** * Sorted array of responses to /keys (MUST be sorted by cherry-picking date) of * length @e krd_array_length; */ @@ -293,18 +338,19 @@ struct TEH_KeyStateHandle /** * When did we initiate the key reloading? */ - struct GNUNET_TIME_Absolute reload_time; + struct GNUNET_TIME_Timestamp reload_time; /** - * When is the next key invalid and we expect to have a different reply? + * What is the period at which we rotate keys + * (signing or denomination keys)? */ - struct GNUNET_TIME_Absolute next_reload; + struct GNUNET_TIME_Relative rekey_frequency; /** * When does our online signing key expire and we * thus need to re-generate this response? */ - struct GNUNET_TIME_Absolute signature_expires; + struct GNUNET_TIME_Timestamp signature_expires; /** * True if #finish_keys_response() was not yet run and this key state @@ -335,17 +381,128 @@ struct SuspendedKeysRequests * The suspended connection. */ struct MHD_Connection *connection; + + /** + * When does this request timeout? + */ + struct GNUNET_TIME_Absolute timeout; +}; + + +/** + * Information we track about wire fees. + */ +struct WireFeeSet +{ + + /** + * Kept in a DLL. + */ + struct WireFeeSet *next; + + /** + * Kept in a DLL. + */ + struct WireFeeSet *prev; + + /** + * Actual fees. + */ + struct TALER_WireFeeSet fees; + + /** + * Start date of fee validity (inclusive). + */ + struct GNUNET_TIME_Timestamp start_date; + + /** + * End date of fee validity (exclusive). + */ + struct GNUNET_TIME_Timestamp end_date; + + /** + * Wire method the fees apply to. + */ + char *method; +}; + + +/** + * State we keep per thread to cache the /wire response. + */ +struct WireStateHandle +{ + + /** + * JSON reply for /wire response. + */ + json_t *json_reply; + + /** + * ETag for this response (if any). + */ + char *etag; + + /** + * head of DLL of wire fees. + */ + struct WireFeeSet *wfs_head; + + /** + * Tail of DLL of wire fees. + */ + struct WireFeeSet *wfs_tail; + + /** + * Earliest timestamp of all the wire methods when we have no more fees. + */ + struct GNUNET_TIME_Absolute cache_expiration; + + /** + * @e cache_expiration time, formatted. + */ + char dat[128]; + + /** + * For which (global) wire_generation was this data structure created? + * Used to check when we are outdated and need to be re-generated. + */ + uint64_t wire_generation; + + /** + * Is the wire data ready? + */ + bool ready; + }; /** + * Stores the latest generation of our wire response. + */ +static struct WireStateHandle *wire_state; + +/** + * Handler listening for wire updates by other exchange + * services. + */ +static struct GNUNET_DB_EventHandler *wire_eh; + +/** + * Counter incremented whenever we have a reason to re-build the #wire_state + * because something external changed. + */ +static uint64_t wire_generation; + + +/** * Stores the latest generation of our key state. */ static struct TEH_KeyStateHandle *key_state; /** * Counter incremented whenever we have a reason to re-build the keys because - * something external changed (in another thread). See #TEH_keys_get_state() and + * something external changed. See #TEH_keys_get_state() and * #TEH_keys_update_states() for uses of this variable. */ static uint64_t key_generation; @@ -379,15 +536,30 @@ static unsigned int skr_size; static struct MHD_Connection *skr_connection; /** + * Task to force timeouts on /keys requests. + */ +static struct GNUNET_SCHEDULER_Task *keys_tt; + +/** * For how long should a signing key be legally retained? * Configuration value. */ static struct GNUNET_TIME_Relative signkey_legal_duration; /** + * What type of asset are we dealing with here? + */ +static char *asset_type; + +/** * RSA security module public key, all zero if not known. */ -static struct TALER_SecurityModulePublicKeyP denom_sm_pub; +static struct TALER_SecurityModulePublicKeyP denom_rsa_sm_pub; + +/** + * CS security module public key, all zero if not known. + */ +static struct TALER_SecurityModulePublicKeyP denom_cs_sm_pub; /** * EdDSA security module public key, all zero if not known. @@ -399,6 +571,483 @@ static struct TALER_SecurityModulePublicKeyP esign_sm_pub; */ static bool terminating; + +/** + * Free memory associated with @a wsh + * + * @param[in] wsh wire state to destroy + */ +static void +destroy_wire_state (struct WireStateHandle *wsh) +{ + struct WireFeeSet *wfs; + + while (NULL != (wfs = wsh->wfs_head)) + { + GNUNET_CONTAINER_DLL_remove (wsh->wfs_head, + wsh->wfs_tail, + wfs); + GNUNET_free (wfs->method); + GNUNET_free (wfs); + } + json_decref (wsh->json_reply); + GNUNET_free (wsh->etag); + GNUNET_free (wsh); +} + + +/** + * Function called whenever another exchange process has updated + * the wire data in the database. + * + * @param cls NULL + * @param extra unused + * @param extra_size number of bytes in @a extra unused + */ +static void +wire_update_event_cb (void *cls, + const void *extra, + size_t extra_size) +{ + (void) cls; + (void) extra; + (void) extra_size; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Received /wire update event\n"); + TEH_check_invariants (); + wire_generation++; + key_generation++; + TEH_resume_keys_requests (false); +} + + +enum GNUNET_GenericReturnValue +TEH_wire_init () +{ + struct GNUNET_DB_EventHeaderP es = { + .size = htons (sizeof (es)), + .type = htons (TALER_DBEVENT_EXCHANGE_KEYS_UPDATED), + }; + + wire_eh = TEH_plugin->event_listen (TEH_plugin->cls, + GNUNET_TIME_UNIT_FOREVER_REL, + &es, + &wire_update_event_cb, + NULL); + if (NULL == wire_eh) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +void +TEH_wire_done () +{ + if (NULL != wire_state) + { + destroy_wire_state (wire_state); + wire_state = NULL; + } + if (NULL != wire_eh) + { + TEH_plugin->event_listen_cancel (TEH_plugin->cls, + wire_eh); + wire_eh = NULL; + } +} + + +/** + * Add information about a wire account to @a cls. + * + * @param cls a `json_t *` object to expand with wire account details + * @param payto_uri the exchange bank account URI to add + * @param conversion_url URL of a conversion service, NULL if there is no conversion + * @param debit_restrictions JSON array with debit restrictions on the account + * @param credit_restrictions JSON array with credit restrictions on the account + * @param master_sig master key signature affirming that this is a bank + * account of the exchange (of purpose #TALER_SIGNATURE_MASTER_WIRE_DETAILS) + * @param bank_label label the wallet should use to display the account, can be NULL + * @param priority priority for ordering bank account labels + */ +static void +add_wire_account (void *cls, + const char *payto_uri, + const char *conversion_url, + const json_t *debit_restrictions, + const json_t *credit_restrictions, + const struct TALER_MasterSignatureP *master_sig, + const char *bank_label, + int64_t priority) +{ + json_t *a = cls; + + if (GNUNET_OK != + TALER_exchange_wire_signature_check ( + payto_uri, + conversion_url, + debit_restrictions, + credit_restrictions, + &TEH_master_public_key, + master_sig)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Database has wire account with invalid signature. Skipping entry. Did the exchange offline public key change?\n"); + return; + } + if (0 != + json_array_append_new ( + a, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("payto_uri", + payto_uri), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("conversion_url", + conversion_url)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("bank_label", + bank_label)), + GNUNET_JSON_pack_int64 ("priority", + priority), + GNUNET_JSON_pack_array_incref ("debit_restrictions", + (json_t *) debit_restrictions), + GNUNET_JSON_pack_array_incref ("credit_restrictions", + (json_t *) credit_restrictions), + GNUNET_JSON_pack_data_auto ("master_sig", + master_sig)))) + { + GNUNET_break (0); /* out of memory!? */ + return; + } +} + + +/** + * Closure for #add_wire_fee(). + */ +struct AddContext +{ + /** + * Wire method the fees are for. + */ + char *wire_method; + + /** + * Wire state we are building. + */ + struct WireStateHandle *wsh; + + /** + * Array to append the fee to. + */ + json_t *a; + + /** + * Set to the maximum end-date seen. + */ + struct GNUNET_TIME_Absolute max_seen; +}; + + +/** + * Add information about a wire account to @a cls. + * + * @param cls a `struct AddContext` + * @param fees the wire fees we charge + * @param start_date from when are these fees valid (start date) + * @param end_date until when are these fees valid (end date, exclusive) + * @param master_sig master key signature affirming that this is the correct + * fee (of purpose #TALER_SIGNATURE_MASTER_WIRE_FEES) + */ +static void +add_wire_fee (void *cls, + const struct TALER_WireFeeSet *fees, + struct GNUNET_TIME_Timestamp start_date, + struct GNUNET_TIME_Timestamp end_date, + const struct TALER_MasterSignatureP *master_sig) +{ + struct AddContext *ac = cls; + struct WireFeeSet *wfs; + + if (GNUNET_OK != + TALER_exchange_offline_wire_fee_verify ( + ac->wire_method, + start_date, + end_date, + fees, + &TEH_master_public_key, + master_sig)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Database has wire fee with invalid signature. Skipping entry. Did the exchange offline public key change?\n"); + return; + } + ac->max_seen = GNUNET_TIME_absolute_max (ac->max_seen, + end_date.abs_time); + wfs = GNUNET_new (struct WireFeeSet); + wfs->start_date = start_date; + wfs->end_date = end_date; + wfs->fees = *fees; + wfs->method = GNUNET_strdup (ac->wire_method); + GNUNET_CONTAINER_DLL_insert (ac->wsh->wfs_head, + ac->wsh->wfs_tail, + wfs); + if (0 != + json_array_append_new ( + ac->a, + GNUNET_JSON_PACK ( + TALER_JSON_pack_amount ("wire_fee", + &fees->wire), + TALER_JSON_pack_amount ("closing_fee", + &fees->closing), + GNUNET_JSON_pack_timestamp ("start_date", + start_date), + GNUNET_JSON_pack_timestamp ("end_date", + end_date), + GNUNET_JSON_pack_data_auto ("sig", + master_sig)))) + { + GNUNET_break (0); /* out of memory!? */ + return; + } +} + + +/** + * Create the /wire response from our database state. + * + * @return NULL on error + */ +static struct WireStateHandle * +build_wire_state (void) +{ + json_t *wire_accounts_array; + json_t *wire_fee_object; + uint64_t wg = wire_generation; /* must be obtained FIRST */ + enum GNUNET_DB_QueryStatus qs; + struct WireStateHandle *wsh; + json_t *wads; + + wsh = GNUNET_new (struct WireStateHandle); + wsh->wire_generation = wg; + wire_accounts_array = json_array (); + GNUNET_assert (NULL != wire_accounts_array); + qs = TEH_plugin->get_wire_accounts (TEH_plugin->cls, + &add_wire_account, + wire_accounts_array); + if (0 > qs) + { + GNUNET_break (0); + json_decref (wire_accounts_array); + wsh->ready = false; + return wsh; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Build /wire data with %u accounts\n", + (unsigned int) json_array_size (wire_accounts_array)); + wire_fee_object = json_object (); + GNUNET_assert (NULL != wire_fee_object); + wsh->cache_expiration = GNUNET_TIME_UNIT_FOREVER_ABS; + { + json_t *account; + size_t index; + + json_array_foreach (wire_accounts_array, + index, + account) + { + char *wire_method; + const char *payto_uri = json_string_value (json_object_get (account, + "payto_uri")); + + GNUNET_assert (NULL != payto_uri); + wire_method = TALER_payto_get_method (payto_uri); + if (NULL == wire_method) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "No wire method in `%s'\n", + payto_uri); + wsh->ready = false; + json_decref (wire_accounts_array); + json_decref (wire_fee_object); + return wsh; + } + if (NULL == json_object_get (wire_fee_object, + wire_method)) + { + struct AddContext ac = { + .wire_method = wire_method, + .wsh = wsh, + .a = json_array () + }; + + GNUNET_assert (NULL != ac.a); + qs = TEH_plugin->get_wire_fees (TEH_plugin->cls, + wire_method, + &add_wire_fee, + &ac); + if (0 > qs) + { + GNUNET_break (0); + json_decref (ac.a); + json_decref (wire_fee_object); + json_decref (wire_accounts_array); + GNUNET_free (wire_method); + wsh->ready = false; + return wsh; + } + if (0 != json_array_size (ac.a)) + { + wsh->cache_expiration + = GNUNET_TIME_absolute_min (ac.max_seen, + wsh->cache_expiration); + GNUNET_assert (0 == + json_object_set_new (wire_fee_object, + wire_method, + ac.a)); + } + else + { + json_decref (ac.a); + } + } + GNUNET_free (wire_method); + } + } + + wads = json_array (); /* #7271 */ + GNUNET_assert (NULL != wads); + wsh->json_reply = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_array_steal ("accounts", + wire_accounts_array), + GNUNET_JSON_pack_array_steal ("wads", + wads), + GNUNET_JSON_pack_object_steal ("fees", + wire_fee_object)); + wsh->ready = true; + return wsh; +} + + +void +TEH_wire_update_state (void) +{ + struct GNUNET_DB_EventHeaderP es = { + .size = htons (sizeof (es)), + .type = htons (TALER_DBEVENT_EXCHANGE_WIRE_UPDATED), + }; + + TEH_plugin->event_notify (TEH_plugin->cls, + &es, + NULL, + 0); + wire_generation++; + key_generation++; +} + + +/** + * Return the current key state for this thread. Possibly + * re-builds the key state if we have reason to believe + * that something changed. + * + * @return NULL on error + */ +struct WireStateHandle * +get_wire_state (void) +{ + struct WireStateHandle *old_wsh; + + old_wsh = wire_state; + if ( (NULL == old_wsh) || + (old_wsh->wire_generation < wire_generation) ) + { + struct WireStateHandle *wsh; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Rebuilding /wire, generation upgrade from %llu to %llu\n", + (unsigned long long) (NULL == old_wsh) ? 0LL : + old_wsh->wire_generation, + (unsigned long long) wire_generation); + TEH_check_invariants (); + wsh = build_wire_state (); + wire_state = wsh; + if (NULL != old_wsh) + destroy_wire_state (old_wsh); + TEH_check_invariants (); + return wsh; + } + return old_wsh; +} + + +const struct TALER_WireFeeSet * +TEH_wire_fees_by_time ( + struct GNUNET_TIME_Timestamp ts, + const char *method) +{ + struct WireStateHandle *wsh = get_wire_state (); + + for (struct WireFeeSet *wfs = wsh->wfs_head; + NULL != wfs; + wfs = wfs->next) + { + if (0 != strcmp (method, + wfs->method)) + continue; + if ( (GNUNET_TIME_timestamp_cmp (wfs->start_date, + >, + ts)) || + (GNUNET_TIME_timestamp_cmp (ts, + >=, + wfs->end_date)) ) + continue; + return &wfs->fees; + } + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "No wire fees for method `%s' at %s configured\n", + method, + GNUNET_TIME_timestamp2s (ts)); + return NULL; +} + + +/** + * Function called to forcefully resume suspended keys requests. + * + * @param cls unused, NULL + */ +static void +keys_timeout_cb (void *cls) +{ + struct SuspendedKeysRequests *skr; + + (void) cls; + keys_tt = NULL; + while (NULL != (skr = skr_head)) + { + if (GNUNET_TIME_absolute_is_future (skr->timeout)) + break; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Resuming /keys request due to timeout\n"); + GNUNET_CONTAINER_DLL_remove (skr_head, + skr_tail, + skr); + MHD_resume_connection (skr->connection); + TALER_MHD_daemon_trigger (); + GNUNET_free (skr); + } + if (NULL == skr) + return; + keys_tt = GNUNET_SCHEDULER_add_at (skr->timeout, + &keys_timeout_cb, + NULL); +} + + /** * Suspend /keys request while we (hopefully) are waiting to be * provisioned with key material. @@ -425,6 +1074,13 @@ suspend_request (struct MHD_Connection *connection) GNUNET_CONTAINER_DLL_insert (skr_head, skr_tail, skr); + skr->timeout = GNUNET_TIME_relative_to_absolute (KEYS_TIMEOUT); + if (NULL == keys_tt) + { + keys_tt = GNUNET_SCHEDULER_add_at (skr->timeout, + &keys_timeout_cb, + NULL); + } skr_size++; if (skr_size > SKR_LIMIT) { @@ -442,6 +1098,56 @@ suspend_request (struct MHD_Connection *connection) } +/** + * Called on each denomination key. Checks that the key still works. + * + * @param cls NULL + * @param hc denomination hash (unused) + * @param value a `struct TEH_DenominationKey` + * @return #GNUNET_OK + */ +static enum GNUNET_GenericReturnValue +check_dk (void *cls, + const struct GNUNET_HashCode *hc, + void *value) +{ + struct TEH_DenominationKey *dk = value; + + (void) cls; + (void) hc; + switch (dk->denom_pub.bsign_pub_key->cipher) + { + case GNUNET_CRYPTO_BSA_INVALID: + break; + case GNUNET_CRYPTO_BSA_RSA: + GNUNET_assert (GNUNET_CRYPTO_rsa_public_key_check ( + dk->denom_pub.bsign_pub_key->details.rsa_public_key)); + return GNUNET_OK; + case GNUNET_CRYPTO_BSA_CS: + /* nothing to do for GNUNET_CRYPTO_BSA_CS */ + return GNUNET_OK; + } + GNUNET_assert (0); + return GNUNET_SYSERR; +} + + +void +TEH_check_invariants () +{ + struct TEH_KeyStateHandle *ksh; + + if (0 == TEH_check_invariants_flag) + return; + ksh = TEH_keys_get_state (); + if (NULL == ksh) + return; + GNUNET_CONTAINER_multihashmap_iterate (ksh->denomkey_map, + &check_dk, + NULL); +} + + void TEH_resume_keys_requests (bool do_shutdown) { @@ -476,6 +1182,7 @@ clear_response_cache (struct TEH_KeyStateHandle *ksh) MHD_destroy_response (krd->response_compressed); MHD_destroy_response (krd->response_uncompressed); + GNUNET_free (krd->etag); } GNUNET_array_grow (ksh->krd_array, ksh->krd_array_length, @@ -490,19 +1197,43 @@ clear_response_cache (struct TEH_KeyStateHandle *ksh) * @param sm_pub RSA security module public key to check */ static void -check_denom_sm_pub (const struct TALER_SecurityModulePublicKeyP *sm_pub) +check_denom_rsa_sm_pub (const struct TALER_SecurityModulePublicKeyP *sm_pub) { if (0 != GNUNET_memcmp (sm_pub, - &denom_sm_pub)) + &denom_rsa_sm_pub)) { - if (! GNUNET_is_zero (&denom_sm_pub)) + if (! GNUNET_is_zero (&denom_rsa_sm_pub)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Our RSA security module changed its key. This must not happen.\n"); GNUNET_assert (0); } - denom_sm_pub = *sm_pub; /* TOFU ;-) */ + denom_rsa_sm_pub = *sm_pub; /* TOFU ;-) */ + } +} + + +/** + * Check that the given CS security module's public key is the one + * we have pinned. If it does not match, we die hard. + * + * @param sm_pub RSA security module public key to check + */ +static void +check_denom_cs_sm_pub (const struct TALER_SecurityModulePublicKeyP *sm_pub) +{ + if (0 != + GNUNET_memcmp (sm_pub, + &denom_cs_sm_pub)) + { + if (! GNUNET_is_zero (&denom_cs_sm_pub)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Our CS security module changed its key. This must not happen.\n"); + GNUNET_assert (0); + } + denom_cs_sm_pub = *sm_pub; /* TOFU ;-) */ } } @@ -540,7 +1271,7 @@ check_esign_sm_pub (const struct TALER_SecurityModulePublicKeyP *sm_pub) * @param value the `struct HelperDenomination` to release * @return #GNUNET_OK (continue to iterate) */ -static int +static enum GNUNET_GenericReturnValue free_denom_cb (void *cls, const struct GNUNET_HashCode *h_denom_pub, void *value) @@ -549,7 +1280,7 @@ free_denom_cb (void *cls, (void) cls; (void) h_denom_pub; - GNUNET_CRYPTO_rsa_public_key_free (hd->denom_pub.rsa_public_key); + TALER_denom_pub_free (&hd->denom_pub); GNUNET_free (hd->section_name); GNUNET_free (hd); return GNUNET_OK; @@ -565,7 +1296,7 @@ free_denom_cb (void *cls, * @param value the `struct HelperSignkey` to release * @return #GNUNET_OK (continue to iterate) */ -static int +static enum GNUNET_GenericReturnValue free_esign_cb (void *cls, const struct GNUNET_PeerIdentity *pid, void *value) @@ -591,6 +1322,10 @@ destroy_key_helpers (struct HelperState *hs) GNUNET_CONTAINER_multihashmap_iterate (hs->denom_keys, &free_denom_cb, hs); + GNUNET_CONTAINER_multihashmap_destroy (hs->rsa_keys); + hs->rsa_keys = NULL; + GNUNET_CONTAINER_multihashmap_destroy (hs->cs_keys); + hs->cs_keys = NULL; GNUNET_CONTAINER_multihashmap_destroy (hs->denom_keys); hs->denom_keys = NULL; GNUNET_CONTAINER_multipeermap_iterate (hs->esign_keys, @@ -598,10 +1333,15 @@ destroy_key_helpers (struct HelperState *hs) hs); GNUNET_CONTAINER_multipeermap_destroy (hs->esign_keys); hs->esign_keys = NULL; - if (NULL != hs->dh) + if (NULL != hs->rsadh) + { + TALER_CRYPTO_helper_rsa_disconnect (hs->rsadh); + hs->rsadh = NULL; + } + if (NULL != hs->csdh) { - TALER_CRYPTO_helper_denom_disconnect (hs->dh); - hs->dh = NULL; + TALER_CRYPTO_helper_cs_disconnect (hs->csdh); + hs->csdh = NULL; } if (NULL != hs->esh) { @@ -612,6 +1352,51 @@ destroy_key_helpers (struct HelperState *hs) /** + * 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 (GNUNET_OK != (GNUNET_CONFIGURATION_have_value ( + TEH_cfg, + section_name, + "AGE_RESTRICTED"))) + return null_mask; + + 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; + } + + if (GNUNET_OK == ret) + { + if (! TEH_age_restriction_enabled) + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "age restriction set in section %s, yet, age restriction is not enabled\n", + section_name); + return TEH_age_restriction_config.mask; + } + + + return null_mask; +} + + +/** * Function called with information about available keys for signing. Usually * only called once per key upon connect. Also called again in case a key is * being revoked, in that case with an @a end_time of zero. @@ -623,20 +1408,20 @@ destroy_key_helpers (struct HelperState *hs) * zero if the key has been revoked or purged * @param validity_duration how long does the key remain available for signing; * zero if the key has been revoked or purged - * @param h_denom_pub hash of the @a denom_pub that is available (or was purged) - * @param denom_pub the public key itself, NULL if the key was revoked or purged + * @param h_rsa hash of the @a denom_pub that is available (or was purged) + * @param bs_pub the public key itself, NULL if the key was revoked or purged * @param sm_pub public key of the security module, NULL if the key was revoked or purged * @param sm_sig signature from the security module, NULL if the key was revoked or purged * The signature was already verified against @a sm_pub. */ static void -helper_denom_cb ( +helper_rsa_cb ( void *cls, const char *section_name, - struct GNUNET_TIME_Absolute start_time, + struct GNUNET_TIME_Timestamp start_time, struct GNUNET_TIME_Relative validity_duration, - const struct GNUNET_HashCode *h_denom_pub, - const struct TALER_DenominationPublicKey *denom_pub, + const struct TALER_RsaPubHashP *h_rsa, + struct GNUNET_CRYPTO_BlindSignPublicKey *bs_pub, const struct TALER_SecurityModulePublicKeyP *sm_pub, const struct TALER_SecurityModuleSignatureP *sm_sig) { @@ -645,14 +1430,94 @@ helper_denom_cb ( GNUNET_log (GNUNET_ERROR_TYPE_INFO, "RSA helper announces key %s for denomination type %s with validity %s\n", - GNUNET_h2s (h_denom_pub), + GNUNET_h2s (&h_rsa->hash), section_name, GNUNET_STRINGS_relative_time_to_string (validity_duration, GNUNET_NO)); key_generation++; TEH_resume_keys_requests (false); - hd = GNUNET_CONTAINER_multihashmap_get (hs->denom_keys, - h_denom_pub); + hd = GNUNET_CONTAINER_multihashmap_get (hs->rsa_keys, + &h_rsa->hash); + if (NULL != hd) + { + /* should be just an update (revocation!), so update existing entry */ + hd->validity_duration = validity_duration; + return; + } + GNUNET_assert (NULL != sm_pub); + check_denom_rsa_sm_pub (sm_pub); + hd = GNUNET_new (struct HelperDenomination); + hd->start_time = start_time; + hd->validity_duration = validity_duration; + hd->h_details.h_rsa = *h_rsa; + hd->sm_sig = *sm_sig; + GNUNET_assert (GNUNET_CRYPTO_BSA_RSA == bs_pub->cipher); + hd->denom_pub.bsign_pub_key = + GNUNET_CRYPTO_bsign_pub_incref (bs_pub); + /* load the age mask for the denomination, if applicable */ + hd->denom_pub.age_mask = load_age_mask (section_name); + TALER_denom_pub_hash (&hd->denom_pub, + &hd->h_denom_pub); + hd->section_name = GNUNET_strdup (section_name); + GNUNET_assert ( + GNUNET_OK == + GNUNET_CONTAINER_multihashmap_put ( + hs->denom_keys, + &hd->h_denom_pub.hash, + hd, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); + GNUNET_assert ( + GNUNET_OK == + GNUNET_CONTAINER_multihashmap_put ( + hs->rsa_keys, + &hd->h_details.h_rsa.hash, + hd, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); +} + + +/** + * Function called with information about available CS keys for signing. Usually + * only called once per key upon connect. Also called again in case a key is + * being revoked, in that case with an @a end_time of zero. + * + * @param cls closure with the `struct HelperState *` + * @param section_name name of the denomination type in the configuration; + * NULL if the key has been revoked or purged + * @param start_time when does the key become available for signing; + * zero if the key has been revoked or purged + * @param validity_duration how long does the key remain available for signing; + * zero if the key has been revoked or purged + * @param h_cs hash of the @a denom_pub that is available (or was purged) + * @param bs_pub the public key itself, NULL if the key was revoked or purged + * @param sm_pub public key of the security module, NULL if the key was revoked or purged + * @param sm_sig signature from the security module, NULL if the key was revoked or purged + * The signature was already verified against @a sm_pub. + */ +static void +helper_cs_cb ( + void *cls, + const char *section_name, + struct GNUNET_TIME_Timestamp start_time, + struct GNUNET_TIME_Relative validity_duration, + const struct TALER_CsPubHashP *h_cs, + struct GNUNET_CRYPTO_BlindSignPublicKey *bs_pub, + const struct TALER_SecurityModulePublicKeyP *sm_pub, + const struct TALER_SecurityModuleSignatureP *sm_sig) +{ + struct HelperState *hs = cls; + struct HelperDenomination *hd; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "CS helper announces key %s for denomination type %s with validity %s\n", + GNUNET_h2s (&h_cs->hash), + section_name, + GNUNET_STRINGS_relative_time_to_string (validity_duration, + GNUNET_NO)); + key_generation++; + TEH_resume_keys_requests (false); + hd = GNUNET_CONTAINER_multihashmap_get (hs->cs_keys, + &h_cs->hash); if (NULL != hd) { /* should be just an update (revocation!), so update existing entry */ @@ -660,20 +1525,32 @@ helper_denom_cb ( return; } GNUNET_assert (NULL != sm_pub); - check_denom_sm_pub (sm_pub); + check_denom_cs_sm_pub (sm_pub); hd = GNUNET_new (struct HelperDenomination); hd->start_time = start_time; hd->validity_duration = validity_duration; - hd->h_denom_pub = *h_denom_pub; + hd->h_details.h_cs = *h_cs; hd->sm_sig = *sm_sig; - hd->denom_pub.rsa_public_key - = GNUNET_CRYPTO_rsa_public_key_dup (denom_pub->rsa_public_key); + GNUNET_assert (GNUNET_CRYPTO_BSA_CS == bs_pub->cipher); + hd->denom_pub.bsign_pub_key + = GNUNET_CRYPTO_bsign_pub_incref (bs_pub); + /* load the age mask for the denomination, if applicable */ + hd->denom_pub.age_mask = load_age_mask (section_name); + TALER_denom_pub_hash (&hd->denom_pub, + &hd->h_denom_pub); hd->section_name = GNUNET_strdup (section_name); GNUNET_assert ( GNUNET_OK == GNUNET_CONTAINER_multihashmap_put ( hs->denom_keys, - &hd->h_denom_pub, + &hd->h_denom_pub.hash, + hd, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); + GNUNET_assert ( + GNUNET_OK == + GNUNET_CONTAINER_multihashmap_put ( + hs->cs_keys, + &hd->h_details.h_cs.hash, hd, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); } @@ -697,7 +1574,7 @@ helper_denom_cb ( static void helper_esign_cb ( void *cls, - struct GNUNET_TIME_Absolute start_time, + struct GNUNET_TIME_Timestamp start_time, struct GNUNET_TIME_Relative validity_duration, const struct TALER_ExchangePublicKeyP *exchange_pub, const struct TALER_SecurityModulePublicKeyP *sm_pub, @@ -721,7 +1598,6 @@ helper_esign_cb ( { /* should be just an update (revocation!), so update existing entry */ hsk->validity_duration = validity_duration; - GNUNET_break (0 == start_time.abs_value_us); return; } GNUNET_assert (NULL != sm_pub); @@ -753,18 +1629,35 @@ setup_key_helpers (struct HelperState *hs) hs->denom_keys = GNUNET_CONTAINER_multihashmap_create (1024, GNUNET_YES); + hs->rsa_keys + = GNUNET_CONTAINER_multihashmap_create (1024, + GNUNET_YES); + hs->cs_keys + = GNUNET_CONTAINER_multihashmap_create (1024, + GNUNET_YES); hs->esign_keys = GNUNET_CONTAINER_multipeermap_create (32, GNUNET_NO /* MUST BE NO! */); - hs->dh = TALER_CRYPTO_helper_denom_connect (TEH_cfg, - &helper_denom_cb, - hs); - if (NULL == hs->dh) + hs->rsadh = TALER_CRYPTO_helper_rsa_connect (TEH_cfg, + "taler-exchange", + &helper_rsa_cb, + hs); + if (NULL == hs->rsadh) + { + destroy_key_helpers (hs); + return GNUNET_SYSERR; + } + hs->csdh = TALER_CRYPTO_helper_cs_connect (TEH_cfg, + "taler-exchange", + &helper_cs_cb, + hs); + if (NULL == hs->csdh) { destroy_key_helpers (hs); return GNUNET_SYSERR; } hs->esh = TALER_CRYPTO_helper_esign_connect (TEH_cfg, + "taler-exchange", &helper_esign_cb, hs); if (NULL == hs->esh) @@ -784,7 +1677,8 @@ setup_key_helpers (struct HelperState *hs) static void sync_key_helpers (struct HelperState *hs) { - TALER_CRYPTO_helper_denom_poll (hs->dh); + TALER_CRYPTO_helper_rsa_poll (hs->rsadh); + TALER_CRYPTO_helper_cs_poll (hs->csdh); TALER_CRYPTO_helper_esign_poll (hs->esh); } @@ -797,7 +1691,7 @@ sync_key_helpers (struct HelperState *hs) * @param value a `struct TEH_DenominationKey` to free * @return #GNUNET_OK (continue to iterate) */ -static int +static enum GNUNET_GenericReturnValue clear_denomination_cb (void *cls, const struct GNUNET_HashCode *h_denom_pub, void *value) @@ -807,7 +1701,7 @@ clear_denomination_cb (void *cls, (void) cls; (void) h_denom_pub; - GNUNET_CRYPTO_rsa_public_key_free (dk->denom_pub.rsa_public_key); + TALER_denom_pub_free (&dk->denom_pub); while (NULL != (as = dk->as_head)) { GNUNET_CONTAINER_DLL_remove (dk->as_head, @@ -828,7 +1722,7 @@ clear_denomination_cb (void *cls, * @param value a `struct SigningKey` to free * @return #GNUNET_OK (continue to iterate) */ -static int +static enum GNUNET_GenericReturnValue clear_signkey_cb (void *cls, const struct GNUNET_PeerIdentity *pid, void *value) @@ -853,7 +1747,16 @@ static void destroy_key_state (struct TEH_KeyStateHandle *ksh, bool free_helper) { + struct TEH_GlobalFee *gf; + clear_response_cache (ksh); + while (NULL != (gf = ksh->gf_head)) + { + GNUNET_CONTAINER_DLL_remove (ksh->gf_head, + ksh->gf_tail, + gf); + GNUNET_free (gf); + } GNUNET_CONTAINER_multihashmap_iterate (ksh->denomkey_map, &clear_denomination_cb, ksh); @@ -864,6 +1767,8 @@ destroy_key_state (struct TEH_KeyStateHandle *ksh, GNUNET_CONTAINER_multipeermap_destroy (ksh->signkey_map); json_decref (ksh->auditors); ksh->auditors = NULL; + json_decref (ksh->global_fees); + ksh->global_fees = NULL; if (free_helper) { destroy_key_helpers (ksh->helpers); @@ -894,8 +1799,12 @@ keys_update_event_cb (void *cls, (void) cls; (void) extra; (void) extra_size; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Received /keys update event\n"); + TEH_check_invariants (); key_generation++; TEH_resume_keys_requests (false); + TEH_check_invariants (); } @@ -918,6 +1827,17 @@ TEH_keys_init () "SIGNKEY_LEGAL_DURATION"); return GNUNET_SYSERR; } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (TEH_cfg, + "exchange", + "ASSET_TYPE", + &asset_type)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING, + "exchange", + "ASSET_TYPE"); + asset_type = GNUNET_strdup ("fiat"); + } keys_eh = TEH_plugin->event_listen (TEH_plugin->cls, GNUNET_TIME_UNIT_FOREVER_REL, &es, @@ -938,6 +1858,11 @@ TEH_keys_init () void TEH_keys_finished () { + if (NULL != keys_tt) + { + GNUNET_SCHEDULER_cancel (keys_tt); + keys_tt = NULL; + } if (NULL != key_state) destroy_key_state (key_state, true); @@ -965,7 +1890,7 @@ static void denomination_info_cb ( void *cls, const struct TALER_DenominationPublicKey *denom_pub, - const struct GNUNET_HashCode *h_denom_pub, + const struct TALER_DenominationHashP *h_denom_pub, const struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta, const struct TALER_MasterSignatureP *master_sig, bool recoup_possible) @@ -973,27 +1898,48 @@ denomination_info_cb ( struct TEH_KeyStateHandle *ksh = cls; struct TEH_DenominationKey *dk; - if ( (0 == meta->start.abs_value_us) || - (0 == meta->expire_withdraw.abs_value_us) || - (0 == meta->expire_deposit.abs_value_us) || - (0 == meta->expire_legal.abs_value_us) ) + if (GNUNET_OK != + TALER_exchange_offline_denom_validity_verify ( + h_denom_pub, + meta->start, + meta->expire_withdraw, + meta->expire_deposit, + meta->expire_legal, + &meta->value, + &meta->fees, + &TEH_master_public_key, + master_sig)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Database has denomination with invalid signature. Skipping entry. Did the exchange offline public key change?\n"); + return; + } + + GNUNET_assert (GNUNET_CRYPTO_BSA_INVALID != + denom_pub->bsign_pub_key->cipher); + if (GNUNET_TIME_absolute_is_zero (meta->start.abs_time) || + GNUNET_TIME_absolute_is_zero (meta->expire_withdraw.abs_time) || + GNUNET_TIME_absolute_is_zero (meta->expire_deposit.abs_time) || + GNUNET_TIME_absolute_is_zero (meta->expire_legal.abs_time) ) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Database contains invalid denomination key %s\n", - GNUNET_h2s (h_denom_pub)); + GNUNET_h2s (&h_denom_pub->hash)); return; } dk = GNUNET_new (struct TEH_DenominationKey); - dk->denom_pub.rsa_public_key - = GNUNET_CRYPTO_rsa_public_key_dup (denom_pub->rsa_public_key); + TALER_denom_pub_copy (&dk->denom_pub, + denom_pub); dk->h_denom_pub = *h_denom_pub; 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, - &dk->h_denom_pub, + &dk->h_denom_pub.hash, dk, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); } @@ -1018,6 +1964,19 @@ signkey_info_cb ( struct SigningKey *sk; struct GNUNET_PeerIdentity pid; + if (GNUNET_OK != + TALER_exchange_offline_signkey_validity_verify ( + exchange_pub, + meta->start, + meta->expire_sign, + meta->expire_legal, + &TEH_master_public_key, + master_sig)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Database has signing key with invalid signature. Skipping entry. Did the exchange offline public key change?\n"); + return; + } sk = GNUNET_new (struct SigningKey); sk->exchange_pub = *exchange_pub; sk->meta = *meta; @@ -1058,7 +2017,7 @@ struct GetAuditorSigsContext * @param value a `struct TEH_DenominationKey` * @return #GNUNET_OK (continue to iterate) */ -static int +static enum GNUNET_GenericReturnValue get_auditor_sigs (void *cls, const struct GNUNET_HashCode *h_denom_pub, void *value) @@ -1139,7 +2098,7 @@ static void auditor_denom_cb ( void *cls, const struct TALER_AuditorPublicKeyP *auditor_pub, - const struct GNUNET_HashCode *h_denom_pub, + const struct TALER_DenominationHashP *h_denom_pub, const struct TALER_AuditorSignatureP *auditor_sig) { struct TEH_KeyStateHandle *ksh = cls; @@ -1147,7 +2106,7 @@ auditor_denom_cb ( struct TEH_AuditorSignature *as; dk = GNUNET_CONTAINER_multihashmap_get (ksh->denomkey_map, - h_denom_pub); + &h_denom_pub->hash); if (NULL == dk) { /* Odd, this should be impossible as per foreign key @@ -1171,9 +2130,9 @@ auditor_denom_cb ( struct SignKeyCtx { /** - * When does the next signing key expire. Updated. + * What is the current rotation frequency for signing keys. Updated. */ - struct GNUNET_TIME_Absolute next_sk_expire; + struct GNUNET_TIME_Relative min_sk_frequency; /** * JSON array of signing keys (being created). @@ -1191,7 +2150,7 @@ struct SignKeyCtx * @param value a `struct SigningKey` * @return #GNUNET_OK (continue to iterate) */ -static int +static enum GNUNET_GenericReturnValue add_sign_key_cb (void *cls, const struct GNUNET_PeerIdentity *pid, void *value) @@ -1199,21 +2158,26 @@ add_sign_key_cb (void *cls, struct SignKeyCtx *ctx = cls; struct SigningKey *sk = value; - ctx->next_sk_expire = - GNUNET_TIME_absolute_min (ctx->next_sk_expire, - sk->meta.expire_sign); - + (void) pid; + if (GNUNET_TIME_absolute_is_future (sk->meta.expire_sign.abs_time)) + { + ctx->min_sk_frequency = + GNUNET_TIME_relative_min (ctx->min_sk_frequency, + GNUNET_TIME_absolute_get_difference ( + sk->meta.start.abs_time, + sk->meta.expire_sign.abs_time)); + } GNUNET_assert ( 0 == json_array_append_new ( ctx->signkeys, GNUNET_JSON_PACK ( - GNUNET_JSON_pack_time_abs ("stamp_start", - sk->meta.start), - GNUNET_JSON_pack_time_abs ("stamp_expire", - sk->meta.expire_sign), - GNUNET_JSON_pack_time_abs ("stamp_end", - sk->meta.expire_legal), + GNUNET_JSON_pack_timestamp ("stamp_start", + sk->meta.start), + GNUNET_JSON_pack_timestamp ("stamp_expire", + sk->meta.expire_sign), + GNUNET_JSON_pack_timestamp ("stamp_end", + sk->meta.expire_legal), GNUNET_JSON_pack_data_auto ("master_sig", &sk->master_sig), GNUNET_JSON_pack_data_auto ("key", @@ -1238,10 +2202,10 @@ struct DenomKeyCtx json_t *recoup; /** - * When does the next denomination key expire. Updated. + * What is the minimum key rotation frequency of + * valid denomination keys? */ - struct GNUNET_TIME_Absolute next_dk_expire; - + struct GNUNET_TIME_Relative min_dk_frequency; }; @@ -1255,7 +2219,7 @@ struct DenomKeyCtx * @param value a `struct TEH_DenominationKey` * @return #GNUNET_OK (continue to iterate) */ -static int +static enum GNUNET_GenericReturnValue add_denom_key_cb (void *cls, const struct GNUNET_HashCode *h_denom_pub, void *value) @@ -1275,80 +2239,33 @@ add_denom_key_cb (void *cls, } else { - dkc->next_dk_expire = - GNUNET_TIME_absolute_min (dkc->next_dk_expire, - dk->meta.expire_withdraw); + if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time)) + { + dkc->min_dk_frequency = + GNUNET_TIME_relative_min (dkc->min_dk_frequency, + GNUNET_TIME_absolute_get_difference ( + dk->meta.start.abs_time, + dk->meta.expire_withdraw.abs_time)); + } (void) GNUNET_CONTAINER_heap_insert (dkc->heap, dk, - dk->meta.start.abs_value_us); + dk->meta.start.abs_time.abs_value_us); } return GNUNET_OK; } /** - * Produce HTTP "Date:" header. - * - * @param at time to write to @a date - * @param[out] date where to write the header, with - * at least 128 bytes available space. - */ -static void -get_date_string (struct GNUNET_TIME_Absolute at, - char date[128]) -{ - static const char *const days[] = - { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; - static const char *const mons[] = - { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", - "Nov", "Dec"}; - struct tm now; - time_t t; -#if ! defined(HAVE_C11_GMTIME_S) && ! defined(HAVE_W32_GMTIME_S) && \ - ! defined(HAVE_GMTIME_R) - struct tm*pNow; -#endif - - date[0] = 0; - t = (time_t) (at.abs_value_us / 1000LL / 1000LL); -#if defined(HAVE_C11_GMTIME_S) - if (NULL == gmtime_s (&t, &now)) - return; -#elif defined(HAVE_W32_GMTIME_S) - if (0 != gmtime_s (&now, &t)) - return; -#elif defined(HAVE_GMTIME_R) - if (NULL == gmtime_r (&t, &now)) - return; -#else - pNow = gmtime (&t); - if (NULL == pNow) - return; - now = *pNow; -#endif - sprintf (date, - "%3s, %02u %3s %04u %02u:%02u:%02u GMT", - days[now.tm_wday % 7], - (unsigned int) now.tm_mday, - mons[now.tm_mon % 12], - (unsigned int) (1900 + now.tm_year), - (unsigned int) now.tm_hour, - (unsigned int) now.tm_min, - (unsigned int) now.tm_sec); -} - - -/** * Add the headers we want to set for every /keys response. * - * @param ksh the key state to use + * @param cls the key state to use * @param[in,out] response the response to modify - * @return #GNUNET_OK on success */ -static enum GNUNET_GenericReturnValue -setup_general_response_headers (const struct TEH_KeyStateHandle *ksh, +static void +setup_general_response_headers (void *cls, struct MHD_Response *response) { + struct TEH_KeyStateHandle *ksh = cls; char dat[128]; TALER_MHD_add_global_headers (response); @@ -1356,30 +2273,72 @@ setup_general_response_headers (const struct TEH_KeyStateHandle *ksh, MHD_add_response_header (response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json")); - get_date_string (ksh->reload_time, - dat); GNUNET_break (MHD_YES == MHD_add_response_header (response, - MHD_HTTP_HEADER_LAST_MODIFIED, - dat)); - if (0 != ksh->next_reload.abs_value_us) + MHD_HTTP_HEADER_CACHE_CONTROL, + "public,must-revalidate,max-age=86400")); + if (! GNUNET_TIME_relative_is_zero (ksh->rekey_frequency)) { - struct GNUNET_TIME_Absolute m; - - m = GNUNET_TIME_relative_to_absolute (TEH_max_keys_caching); - m = GNUNET_TIME_absolute_min (m, - ksh->next_reload); - get_date_string (m, - dat); + struct GNUNET_TIME_Relative r; + struct GNUNET_TIME_Absolute a; + struct GNUNET_TIME_Timestamp km; + struct GNUNET_TIME_Timestamp m; + struct GNUNET_TIME_Timestamp we; + + r = GNUNET_TIME_relative_min (TEH_max_keys_caching, + ksh->rekey_frequency); + a = GNUNET_TIME_relative_to_absolute (r); + /* Round up to next full day to ensure the expiration + time does not become a fingerprint! */ + a = GNUNET_TIME_absolute_round_down (a, + GNUNET_TIME_UNIT_DAYS); + a = GNUNET_TIME_absolute_add (a, + GNUNET_TIME_UNIT_DAYS); + km = GNUNET_TIME_absolute_to_timestamp (a); + we = GNUNET_TIME_absolute_to_timestamp (wire_state->cache_expiration); + m = GNUNET_TIME_timestamp_min (we, + km); + TALER_MHD_get_date_string (m.abs_time, + dat); GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Setting /keys 'Expires' header to '%s'\n", - dat); + "Setting /keys 'Expires' header to '%s' (rekey frequency is %s)\n", + dat, + GNUNET_TIME_relative2s (ksh->rekey_frequency, + false)); GNUNET_break (MHD_YES == MHD_add_response_header (response, MHD_HTTP_HEADER_EXPIRES, dat)); + ksh->signature_expires + = GNUNET_TIME_timestamp_min (m, + ksh->signature_expires); } - return GNUNET_OK; + /* Set cache control headers: our response varies depending on these headers */ + GNUNET_break (MHD_YES == + MHD_add_response_header (response, + MHD_HTTP_HEADER_VARY, + MHD_HTTP_HEADER_ACCEPT_ENCODING)); +} + + +/** + * Function called with wallet balance thresholds. + * + * @param[in,out] cls a `json **` where to put the array of json amounts discovered + * @param threshold another threshold amount to add + */ +static void +wallet_threshold_cb (void *cls, + const struct TALER_Amount *threshold) +{ + json_t **ret = cls; + + if (NULL == *ret) + *ret = json_array (); + GNUNET_assert (0 == + json_array_append_new (*ret, + TALER_JSON_from_amount ( + threshold))); } @@ -1388,50 +2347,58 @@ setup_general_response_headers (const 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 last_cpd timestamp to use - * @param signkeys list of sign keys to return - * @param recoup list of revoked keys to return - * @param denoms list of denominations to return + * @param[in] denom_keys_hash hash over all the denomination keys in @a denoms + * @param last_cherry_pick_date timestamp to use + * @param[in,out] signkeys list of sign keys to return + * @param[in,out] recoup list of revoked keys to return + * @param[in,out] grouped_denominations list of grouped denominations to return * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue create_krd (struct TEH_KeyStateHandle *ksh, const struct GNUNET_HashCode *denom_keys_hash, - struct GNUNET_TIME_Absolute last_cpd, + struct GNUNET_TIME_Timestamp last_cherry_pick_date, json_t *signkeys, json_t *recoup, - json_t *denoms) + json_t *grouped_denominations) { struct KeysResponseData krd; struct TALER_ExchangePublicKeyP exchange_pub; struct TALER_ExchangeSignatureP exchange_sig; + struct WireStateHandle *wsh; json_t *keys; - GNUNET_assert (0 != last_cpd.abs_value_us); + wsh = get_wire_state (); + if (! wsh->ready) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + GNUNET_assert (! GNUNET_TIME_absolute_is_zero ( + last_cherry_pick_date.abs_time)); GNUNET_assert (NULL != signkeys); GNUNET_assert (NULL != recoup); - GNUNET_assert (NULL != denoms); + GNUNET_assert (NULL != grouped_denominations); GNUNET_assert (NULL != ksh->auditors); GNUNET_assert (NULL != TEH_currency); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Creating /keys at cherry pick date %s\n", - GNUNET_STRINGS_absolute_time_to_string (last_cpd)); - /* Sign hash over denomination keys */ - { - struct TALER_ExchangeKeySetPS ks = { - .purpose.size = htonl (sizeof (ks)), - .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_KEY_SET), - .list_issue_date = GNUNET_TIME_absolute_hton (last_cpd), - .hc = *denom_keys_hash - }; + GNUNET_TIME_timestamp2s (last_cherry_pick_date)); + + /* Sign hash over master signatures of all denomination keys until this time + (in reverse order). */ + { enum TALER_ErrorCode ec; if (TALER_EC_NONE != - (ec = TEH_keys_exchange_sign2 (ksh, - &ks, - &exchange_pub, - &exchange_sig))) + (ec = + TALER_exchange_online_key_set_sign ( + &TEH_keys_exchange_sign2_, + ksh, + last_cherry_pick_date, + denom_keys_hash, + &exchange_pub, + &exchange_sig))) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Could not create key response data: cannot sign (%s)\n", @@ -1439,21 +2406,44 @@ create_krd (struct TEH_KeyStateHandle *ksh, return GNUNET_SYSERR; } } + { const struct SigningKey *sk; sk = GNUNET_CONTAINER_multipeermap_get ( ksh->signkey_map, (const struct GNUNET_PeerIdentity *) &exchange_pub); - ksh->signature_expires = GNUNET_TIME_absolute_min (sk->meta.expire_sign, - ksh->signature_expires); + ksh->signature_expires = GNUNET_TIME_timestamp_min (sk->meta.expire_sign, + ksh->signature_expires); } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Build /keys data with %u wire accounts\n", + (unsigned int) json_array_size ( + json_object_get (wsh->json_reply, + "accounts"))); + keys = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("version", EXCHANGE_PROTOCOL_VERSION), + GNUNET_JSON_pack_string ("base_url", + TEH_base_url), GNUNET_JSON_pack_string ("currency", TEH_currency), + GNUNET_JSON_pack_object_steal ( + "currency_specification", + TALER_CONFIG_currency_specs_to_json (TEH_cspec)), + TALER_JSON_pack_amount ("stefan_abs", + &TEH_stefan_abs), + TALER_JSON_pack_amount ("stefan_log", + &TEH_stefan_log), + GNUNET_JSON_pack_double ("stefan_lin", + (double) TEH_stefan_lin), + GNUNET_JSON_pack_string ("asset_type", + asset_type), + GNUNET_JSON_pack_bool ("rewards_allowed", + GNUNET_YES == + TEH_enable_rewards), GNUNET_JSON_pack_data_auto ("master_public_key", &TEH_master_public_key), GNUNET_JSON_pack_time_rel ("reserve_closing_delay", @@ -1462,35 +2452,115 @@ create_krd (struct TEH_KeyStateHandle *ksh, signkeys), GNUNET_JSON_pack_array_incref ("recoup", recoup), - GNUNET_JSON_pack_array_incref ("denoms", - denoms), + GNUNET_JSON_pack_array_incref ("wads", + json_object_get (wsh->json_reply, + "wads")), + GNUNET_JSON_pack_array_incref ("accounts", + json_object_get (wsh->json_reply, + "accounts")), + GNUNET_JSON_pack_object_incref ("wire_fees", + json_object_get (wsh->json_reply, + "fees")), + GNUNET_JSON_pack_array_incref ("denominations", + grouped_denominations), GNUNET_JSON_pack_array_incref ("auditors", ksh->auditors), - GNUNET_JSON_pack_time_abs ("list_issue_date", - last_cpd), - GNUNET_JSON_pack_data_auto ("eddsa_pub", + GNUNET_JSON_pack_array_incref ("global_fees", + ksh->global_fees), + GNUNET_JSON_pack_timestamp ("list_issue_date", + last_cherry_pick_date), + GNUNET_JSON_pack_data_auto ("exchange_pub", &exchange_pub), - GNUNET_JSON_pack_data_auto ("eddsa_sig", + GNUNET_JSON_pack_data_auto ("exchange_sig", &exchange_sig)); GNUNET_assert (NULL != keys); - if ( (TEH_KYC_NONE != TEH_kyc_config.mode) && - (GNUNET_OK == - TALER_amount_is_valid (&TEH_kyc_config.wallet_balance_limit)) ) + + /* Set wallet limit if KYC is configured */ { - GNUNET_assert ( - 0 == - json_object_set_new ( + json_t *wblwk = NULL; + + TALER_KYCLOGIC_kyc_iterate_thresholds ( + TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE, + &wallet_threshold_cb, + &wblwk); + if (NULL != wblwk) + GNUNET_assert ( + 0 == + json_object_set_new ( + keys, + "wallet_balance_limit_without_kyc", + wblwk)); + } + + /* Signal support for the configured, enabled extensions. */ + { + json_t *extensions = json_object (); + bool has_extensions = false; + + GNUNET_assert (NULL != extensions); + /* Fill in the configurations of the enabled extensions */ + for (const struct TALER_Extensions *iter = TALER_extensions_get_head (); + NULL != iter && NULL != iter->extension; + iter = iter->next) + { + const struct TALER_Extension *extension = iter->extension; + json_t *manifest; + int r; + + /* skip if not enabled */ + if (! extension->enabled) + continue; + + /* flag our findings so far */ + has_extensions = true; + + + manifest = extension->manifest (extension); + GNUNET_assert (manifest); + + r = json_object_set_new ( + extensions, + extension->name, + manifest); + GNUNET_assert (0 == r); + } + + /* Update the keys object with the extensions and its signature */ + if (has_extensions) + { + json_t *sig; + int r; + + r = json_object_set_new ( keys, - "wallet_balance_limit_without_kyc", - TALER_JSON_from_amount ( - &TEH_kyc_config.wallet_balance_limit))); + "extensions", + extensions); + GNUNET_assert (0 == r); + + /* Add the signature of the extensions, if it is not zero */ + if (TEH_extensions_signed) + { + sig = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("extensions_sig", + &TEH_extensions_sig)); + + r = json_object_update (keys, sig); + GNUNET_assert (0 == r); + } + } + else + { + json_decref (extensions); + } } + { char *keys_json; void *keys_jsonz; size_t keys_jsonz_size; int comp; + char etag[sizeof (struct GNUNET_HashCode) * 2]; /* Convert /keys response to UTF8-String */ keys_json = json_dumps (keys, @@ -1502,15 +2572,34 @@ create_krd (struct TEH_KeyStateHandle *ksh, keys_jsonz = GNUNET_strdup (keys_json); keys_jsonz_size = strlen (keys_json); + /* hash to compute etag */ + { + struct GNUNET_HashCode ehash; + char *end; + + GNUNET_CRYPTO_hash (keys_jsonz, + keys_jsonz_size, + &ehash); + end = GNUNET_STRINGS_data_to_string (&ehash, + sizeof (ehash), + etag, + sizeof (etag)); + *end = '\0'; + } + /* Create uncompressed response */ krd.response_uncompressed = MHD_create_response_from_buffer (keys_jsonz_size, keys_json, MHD_RESPMEM_MUST_FREE); GNUNET_assert (NULL != krd.response_uncompressed); - GNUNET_assert (GNUNET_OK == - setup_general_response_headers (ksh, - krd.response_uncompressed)); + setup_general_response_headers (ksh, + krd.response_uncompressed); + /* Information is always public, revalidate after 1 day */ + GNUNET_break (MHD_YES == + MHD_add_response_header (krd.response_uncompressed, + MHD_HTTP_HEADER_ETAG, + etag)); /* Also compute compressed version of /keys response */ comp = TALER_MHD_body_compress (&keys_jsonz, &keys_jsonz_size); @@ -1526,11 +2615,16 @@ create_krd (struct TEH_KeyStateHandle *ksh, MHD_add_response_header (krd.response_compressed, MHD_HTTP_HEADER_CONTENT_ENCODING, "deflate")) ); - GNUNET_assert (GNUNET_OK == - setup_general_response_headers (ksh, - krd.response_compressed)); + setup_general_response_headers (ksh, + krd.response_compressed); + /* Information is always public, revalidate after 1 day */ + GNUNET_break (MHD_YES == + MHD_add_response_header (krd.response_compressed, + MHD_HTTP_HEADER_ETAG, + etag)); + krd.etag = GNUNET_strdup (etag); } - krd.cherry_pick_date = last_cpd; + krd.cherry_pick_date = last_cherry_pick_date; GNUNET_array_append (ksh->krd_array, ksh->krd_array_length, krd); @@ -1539,6 +2633,194 @@ create_krd (struct TEH_KeyStateHandle *ksh, /** + * Element in the `struct SignatureContext` array. + */ +struct SignatureElement +{ + + /** + * Offset of the denomination in the group array, + * for sorting (2nd rank, ascending). + */ + unsigned int offset; + + /** + * Offset of the group in the denominations array, + * for sorting (2nd rank, ascending). + */ + unsigned int group_offset; + + /** + * Pointer to actual master signature to hash over. + */ + struct TALER_MasterSignatureP master_sig; +}; + +/** + * Context for collecting the array of master signatures + * needed to verify the exchange_sig online signature. + */ +struct SignatureContext +{ + /** + * Array of signatures to hash over. + */ + struct SignatureElement *elements; + + /** + * Write offset in the @e elements array. + */ + unsigned int elements_pos; + + /** + * Allocated space for @e elements. + */ + unsigned int elements_size; +}; + + +/** + * Determine order to sort two elements by before + * we hash the master signatures. Used for + * sorting with qsort(). + * + * @param a pointer to a `struct SignatureElement` + * @param b pointer to a `struct SignatureElement` + * @return 0 if equal, -1 if a < b, 1 if a > b. + */ +static int +signature_context_sort_cb (const void *a, + const void *b) +{ + const struct SignatureElement *sa = a; + const struct SignatureElement *sb = b; + + if (sa->group_offset < sb->group_offset) + return -1; + if (sa->group_offset > sb->group_offset) + return 1; + if (sa->offset < sb->offset) + return -1; + if (sa->offset > sb->offset) + return 1; + /* We should never have two disjoint elements + with same time and offset */ + GNUNET_assert (sa == sb); + return 0; +} + + +/** + * Append a @a master_sig to the @a sig_ctx using the + * given attributes for (later) sorting. + * + * @param[in,out] sig_ctx signature context to update + * @param group_offset offset for the group + * @param offset offset for the entry + * @param master_sig master signature for the entry + */ +static void +append_signature (struct SignatureContext *sig_ctx, + unsigned int group_offset, + unsigned int offset, + const struct TALER_MasterSignatureP *master_sig) +{ + struct SignatureElement *element; + unsigned int new_size; + + if (sig_ctx->elements_pos == sig_ctx->elements_size) + { + if (0 == sig_ctx->elements_size) + new_size = 1024; + else + new_size = sig_ctx->elements_size * 2; + GNUNET_array_grow (sig_ctx->elements, + sig_ctx->elements_size, + new_size); + } + element = &sig_ctx->elements[sig_ctx->elements_pos++]; + element->offset = offset; + element->group_offset = group_offset; + element->master_sig = *master_sig; +} + + +/** + *GroupData is the value we store for each group meta-data */ +struct GroupData +{ + /** + * The json blob with the group meta-data and list of denominations + */ + json_t *json; + + /** + * List of denominations for the group, + * included in @e json, do not free separately! + */ + json_t *list; + + /** + * Offset of the group in the final array. + */ + unsigned int group_off; + +}; + + +/** + * Helper function called to clean up the group data + * in the denominations_by_group below. + * + * @param cls unused + * @param key unused + * @param value a `struct GroupData` to free + * @return #GNUNET_OK + */ +static int +free_group (void *cls, + const struct GNUNET_HashCode *key, + void *value) +{ + struct GroupData *gd = value; + + (void) cls; + (void) key; + GNUNET_free (gd); + return GNUNET_OK; +} + + +static void +compute_msig_hash (struct SignatureContext *sig_ctx, + struct GNUNET_HashCode *hc) +{ + struct GNUNET_HashContext *hash_context; + + hash_context = GNUNET_CRYPTO_hash_context_start (); + qsort (sig_ctx->elements, + sig_ctx->elements_pos, + sizeof (struct SignatureElement), + &signature_context_sort_cb); + for (unsigned int i = 0; i<sig_ctx->elements_pos; i++) + { + struct SignatureElement *element = &sig_ctx->elements[i]; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Adding %u,%u,%s\n", + element->group_offset, + element->offset, + TALER_B2S (&element->master_sig)); + GNUNET_CRYPTO_hash_context_read (hash_context, + &element->master_sig, + sizeof (element->master_sig)); + } + GNUNET_CRYPTO_hash_context_finish (hash_context, + hc); +} + + +/** * Update the "/keys" responses in @a ksh, computing the detailed replies. * * This function is to recompute all (including cherry-picked) responses we @@ -1550,144 +2832,399 @@ create_krd (struct TEH_KeyStateHandle *ksh, static enum GNUNET_GenericReturnValue finish_keys_response (struct TEH_KeyStateHandle *ksh) { + enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR; json_t *recoup; - struct SignKeyCtx sctx; - json_t *denoms; - struct GNUNET_TIME_Absolute last_cpd; + struct SignKeyCtx sctx = { + .min_sk_frequency = GNUNET_TIME_UNIT_FOREVER_REL + }; + json_t *grouped_denominations = NULL; + struct GNUNET_TIME_Timestamp last_cherry_pick_date; struct GNUNET_CONTAINER_Heap *heap; - struct GNUNET_HashContext *hash_context; + struct SignatureContext sig_ctx = { 0 }; + /* Remember if we have any denomination with age restriction */ + bool has_age_restricted_denomination = false; + struct WireStateHandle *wsh; + wsh = get_wire_state (); + if (! wsh->ready) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (0 == + json_array_size (json_object_get (wsh->json_reply, + "accounts")) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "No wire accounts available. Refusing to generate /keys response.\n"); + return GNUNET_NO; + } sctx.signkeys = json_array (); GNUNET_assert (NULL != sctx.signkeys); - sctx.next_sk_expire = GNUNET_TIME_UNIT_FOREVER_ABS; + recoup = json_array (); + GNUNET_assert (NULL != recoup); + grouped_denominations = json_array (); + GNUNET_assert (NULL != grouped_denominations); + GNUNET_CONTAINER_multipeermap_iterate (ksh->signkey_map, &add_sign_key_cb, &sctx); - recoup = json_array (); - GNUNET_assert (NULL != recoup); + if (0 == json_array_size (sctx.signkeys)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "No online signing keys available. Refusing to generate /keys response.\n"); + ret = GNUNET_NO; + goto CLEANUP; + } heap = GNUNET_CONTAINER_heap_create (GNUNET_CONTAINER_HEAP_ORDER_MAX); { struct DenomKeyCtx dkc = { .recoup = recoup, .heap = heap, - .next_dk_expire = GNUNET_TIME_UNIT_FOREVER_ABS, + .min_dk_frequency = GNUNET_TIME_UNIT_FOREVER_REL, }; GNUNET_CONTAINER_multihashmap_iterate (ksh->denomkey_map, &add_denom_key_cb, &dkc); - ksh->next_reload - = GNUNET_TIME_absolute_min (dkc.next_dk_expire, - sctx.next_sk_expire); + ksh->rekey_frequency + = GNUNET_TIME_relative_min (dkc.min_dk_frequency, + sctx.min_sk_frequency); } - denoms = json_array (); - GNUNET_assert (NULL != denoms); - last_cpd = GNUNET_TIME_UNIT_ZERO_ABS; - hash_context = GNUNET_CRYPTO_hash_context_start (); + + last_cherry_pick_date = GNUNET_TIME_UNIT_ZERO_TS; + { struct TEH_DenominationKey *dk; + struct GNUNET_CONTAINER_MultiHashMap *denominations_by_group; - /* heap = min heap, sorted by start time */ + denominations_by_group = + GNUNET_CONTAINER_multihashmap_create (1024, + GNUNET_NO /* NO, because keys are only on the stack */); + /* heap = max heap, sorted by start time */ while (NULL != (dk = GNUNET_CONTAINER_heap_remove_root (heap))) { - if ( (last_cpd.abs_value_us != dk->meta.start.abs_value_us) && - (0 != last_cpd.abs_value_us) ) + if (GNUNET_TIME_timestamp_cmp (last_cherry_pick_date, + !=, + dk->meta.start) && + (! GNUNET_TIME_absolute_is_zero (last_cherry_pick_date.abs_time)) ) { + /* + * This is not the first entry in the heap (because last_cherry_pick_date != + * GNUNET_TIME_UNIT_ZERO_TS) and the previous entry had a different + * start time. Therefore, we create a new entry in ksh. + */ struct GNUNET_HashCode hc; - GNUNET_CRYPTO_hash_context_finish ( - GNUNET_CRYPTO_hash_context_copy (hash_context), - &hc); + compute_msig_hash (&sig_ctx, + &hc); if (GNUNET_OK != create_krd (ksh, &hc, - last_cpd, + last_cherry_pick_date, sctx.signkeys, recoup, - denoms)) + grouped_denominations)) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failed to generate key response data for %s\n", - GNUNET_STRINGS_absolute_time_to_string (last_cpd)); - GNUNET_CRYPTO_hash_context_abort (hash_context); + GNUNET_TIME_timestamp2s (last_cherry_pick_date)); /* drain heap before destroying it */ while (NULL != (dk = GNUNET_CONTAINER_heap_remove_root (heap))) /* intentionally empty */; GNUNET_CONTAINER_heap_destroy (heap); - json_decref (denoms); - json_decref (sctx.signkeys); - json_decref (recoup); - return GNUNET_SYSERR; + goto CLEANUP; } } - last_cpd = dk->meta.start; - GNUNET_CRYPTO_hash_context_read (hash_context, - &dk->h_denom_pub, - sizeof (struct GNUNET_HashCode)); - GNUNET_assert ( - 0 == - json_array_append_new ( - denoms, - GNUNET_JSON_PACK ( + + last_cherry_pick_date = dk->meta.start; + /* + * Group the denominations by {cipher, value, fees, age_mask}. + * + * For each group we save the group meta-data and the list of + * denominations in this group as a json-blob in the multihashmap + * denominations_by_group. + */ + { + struct GroupData *group; + json_t *entry; + struct GNUNET_HashCode key; + struct TALER_DenominationGroup meta = { + .cipher = dk->denom_pub.bsign_pub_key->cipher, + .value = dk->meta.value, + .fees = dk->meta.fees, + .age_mask = dk->meta.age_mask, + }; + + /* Search the group/JSON-blob for the key */ + TALER_denomination_group_get_key (&meta, + &key); + group = GNUNET_CONTAINER_multihashmap_get ( + denominations_by_group, + &key); + if (NULL == group) + { + /* There is no group for this meta-data yet, so we create a new group */ + bool age_restricted = meta.age_mask.bits != 0; + const char *cipher; + + group = GNUNET_new (struct GroupData); + switch (meta.cipher) + { + case GNUNET_CRYPTO_BSA_RSA: + cipher = age_restricted ? "RSA+age_restricted" : "RSA"; + break; + case GNUNET_CRYPTO_BSA_CS: + cipher = age_restricted ? "CS+age_restricted" : "CS"; + break; + default: + GNUNET_assert (false); + } + /* Create a new array for the denominations in this group */ + group->list = json_array (); + GNUNET_assert (NULL != group->list); + group->json = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("cipher", + cipher), + GNUNET_JSON_pack_array_steal ("denoms", + group->list), + TALER_JSON_PACK_DENOM_FEES ("fee", + &meta.fees), + TALER_JSON_pack_amount ("value", + &meta.value)); + GNUNET_assert (NULL != group->json); + if (age_restricted) + { + GNUNET_assert ( + 0 == + json_object_set_new (group->json, + "age_mask", + json_integer ( + meta.age_mask.bits))); + /* Remember that we have found at least _one_ age restricted denomination */ + has_age_restricted_denomination = true; + } + group->group_off + = json_array_size (grouped_denominations); + GNUNET_assert (0 == + json_array_append_new ( + grouped_denominations, + group->json)); + GNUNET_assert ( + GNUNET_OK == + GNUNET_CONTAINER_multihashmap_put (denominations_by_group, + &key, + group, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); + } + + /* Now that we have found/created the right group, add the + denomination to the list */ + { + struct HelperDenomination *hd; + struct GNUNET_JSON_PackSpec key_spec; + bool private_key_lost; + + hd = GNUNET_CONTAINER_multihashmap_get (ksh->helpers->denom_keys, + &dk->h_denom_pub.hash); + private_key_lost + = (NULL == hd) || + GNUNET_TIME_absolute_is_past ( + GNUNET_TIME_absolute_add ( + hd->start_time.abs_time, + hd->validity_duration)); + switch (meta.cipher) + { + case GNUNET_CRYPTO_BSA_RSA: + key_spec = + GNUNET_JSON_pack_rsa_public_key ( + "rsa_pub", + dk->denom_pub.bsign_pub_key->details.rsa_public_key); + break; + case GNUNET_CRYPTO_BSA_CS: + key_spec = + GNUNET_JSON_pack_data_varsize ( + "cs_pub", + &dk->denom_pub.bsign_pub_key->details.cs_public_key, + sizeof (dk->denom_pub.bsign_pub_key->details.cs_public_key)); + break; + default: + GNUNET_assert (false); + } + + entry = GNUNET_JSON_PACK ( GNUNET_JSON_pack_data_auto ("master_sig", &dk->master_sig), - GNUNET_JSON_pack_time_abs ("stamp_start", - dk->meta.start), - GNUNET_JSON_pack_time_abs ("stamp_expire_withdraw", - dk->meta.expire_withdraw), - GNUNET_JSON_pack_time_abs ("stamp_expire_deposit", - dk->meta.expire_deposit), - GNUNET_JSON_pack_time_abs ("stamp_expire_legal", - dk->meta.expire_legal), - GNUNET_JSON_pack_rsa_public_key ("denom_pub", - dk->denom_pub.rsa_public_key), - TALER_JSON_pack_amount ("value", - &dk->meta.value), - TALER_JSON_pack_amount ("fee_withdraw", - &dk->meta.fee_withdraw), - TALER_JSON_pack_amount ("fee_deposit", - &dk->meta.fee_deposit), - TALER_JSON_pack_amount ("fee_refresh", - &dk->meta.fee_refresh), - TALER_JSON_pack_amount ("fee_refund", - &dk->meta.fee_refund)))); - } + GNUNET_JSON_pack_allow_null ( + private_key_lost + ? GNUNET_JSON_pack_bool ("lost", + true) + : GNUNET_JSON_pack_string ("dummy", + NULL)), + GNUNET_JSON_pack_timestamp ("stamp_start", + dk->meta.start), + GNUNET_JSON_pack_timestamp ("stamp_expire_withdraw", + dk->meta.expire_withdraw), + GNUNET_JSON_pack_timestamp ("stamp_expire_deposit", + dk->meta.expire_deposit), + GNUNET_JSON_pack_timestamp ("stamp_expire_legal", + dk->meta.expire_legal), + key_spec + ); + GNUNET_assert (NULL != entry); + } + + /* Build up the running hash of all master signatures of the + denominations */ + append_signature (&sig_ctx, + group->group_off, + (unsigned int) json_array_size (group->list), + &dk->master_sig); + /* Finally, add the denomination to the list of denominations in this + group */ + GNUNET_assert (json_is_array (group->list)); + GNUNET_assert (0 == + json_array_append_new (group->list, + entry)); + } + } /* loop over heap ends */ + + GNUNET_CONTAINER_multihashmap_iterate (denominations_by_group, + &free_group, + NULL); + GNUNET_CONTAINER_multihashmap_destroy (denominations_by_group); } GNUNET_CONTAINER_heap_destroy (heap); - if (0 != last_cpd.abs_value_us) + + if (! GNUNET_TIME_absolute_is_zero (last_cherry_pick_date.abs_time)) { struct GNUNET_HashCode hc; - GNUNET_CRYPTO_hash_context_finish (hash_context, - &hc); + compute_msig_hash (&sig_ctx, + &hc); if (GNUNET_OK != create_krd (ksh, &hc, - last_cpd, + last_cherry_pick_date, sctx.signkeys, recoup, - denoms)) + grouped_denominations)) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failed to generate key response data for %s\n", - GNUNET_STRINGS_absolute_time_to_string (last_cpd)); - json_decref (denoms); - json_decref (sctx.signkeys); - json_decref (recoup); - return GNUNET_SYSERR; + GNUNET_TIME_timestamp2s (last_cherry_pick_date)); + goto CLEANUP; } ksh->management_only = false; + + /* Sanity check: Make sure that age restriction is enabled IFF at least + * one age restricted denomination exist */ + if (! has_age_restricted_denomination && TEH_age_restriction_enabled) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Age restriction is enabled, but NO denominations with age restriction found!\n"); + goto CLEANUP; + } + else if (has_age_restricted_denomination && ! TEH_age_restriction_enabled) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Age restriction is NOT enabled, but denominations with age restriction found!\n"); + goto CLEANUP; + } } else { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "No denomination keys available. Refusing to generate /keys response.\n"); - GNUNET_CRYPTO_hash_context_abort (hash_context); } - json_decref (sctx.signkeys); + ret = GNUNET_OK; + +CLEANUP: + GNUNET_array_grow (sig_ctx.elements, + sig_ctx.elements_size, + 0); + json_decref (grouped_denominations); + if (NULL != sctx.signkeys) + json_decref (sctx.signkeys); json_decref (recoup); - json_decref (denoms); - return GNUNET_OK; + return ret; +} + + +/** + * Called with information about global fees. + * + * @param cls `struct TEH_KeyStateHandle *` we are building + * @param fees the global fees we charge + * @param purse_timeout when do purses time out + * @param history_expiration how long are account histories preserved + * @param purse_account_limit how many purses are free per account + * @param start_date from when are these fees valid (start date) + * @param end_date until when are these fees valid (end date, exclusive) + * @param master_sig master key signature affirming that this is the correct + * fee (of purpose #TALER_SIGNATURE_MASTER_GLOBAL_FEES) + */ +static void +global_fee_info_cb ( + void *cls, + const struct TALER_GlobalFeeSet *fees, + struct GNUNET_TIME_Relative purse_timeout, + struct GNUNET_TIME_Relative history_expiration, + uint32_t purse_account_limit, + struct GNUNET_TIME_Timestamp start_date, + struct GNUNET_TIME_Timestamp end_date, + const struct TALER_MasterSignatureP *master_sig) +{ + struct TEH_KeyStateHandle *ksh = cls; + struct TEH_GlobalFee *gf; + + if (GNUNET_OK != + TALER_exchange_offline_global_fee_verify ( + start_date, + end_date, + fees, + purse_timeout, + history_expiration, + purse_account_limit, + &TEH_master_public_key, + master_sig)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Database has global fee with invalid signature. Skipping entry. Did the exchange offline public key change?\n"); + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Found global fees with %u purses\n", + purse_account_limit); + gf = GNUNET_new (struct TEH_GlobalFee); + gf->start_date = start_date; + gf->end_date = end_date; + gf->fees = *fees; + gf->purse_timeout = purse_timeout; + gf->history_expiration = history_expiration; + gf->purse_account_limit = purse_account_limit; + gf->master_sig = *master_sig; + GNUNET_CONTAINER_DLL_insert (ksh->gf_head, + ksh->gf_tail, + gf); + GNUNET_assert ( + 0 == + json_array_append_new ( + ksh->global_fees, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_timestamp ("start_date", + start_date), + GNUNET_JSON_pack_timestamp ("end_date", + end_date), + TALER_JSON_PACK_GLOBAL_FEES (fees), + GNUNET_JSON_pack_time_rel ("history_expiration", + history_expiration), + GNUNET_JSON_pack_time_rel ("purse_timeout", + purse_timeout), + GNUNET_JSON_pack_uint64 ("purse_account_limit", + purse_account_limit), + GNUNET_JSON_pack_data_auto ("master_sig", + master_sig)))); } @@ -1707,9 +3244,8 @@ build_key_state (struct HelperState *hs, enum GNUNET_DB_QueryStatus qs; ksh = GNUNET_new (struct TEH_KeyStateHandle); - ksh->signature_expires = GNUNET_TIME_UNIT_FOREVER_ABS; - ksh->reload_time = GNUNET_TIME_absolute_get (); - GNUNET_TIME_round_abs (&ksh->reload_time); + ksh->signature_expires = GNUNET_TIME_UNIT_FOREVER_TS; + ksh->reload_time = GNUNET_TIME_timestamp_get (); /* We must use the key_generation from when we STARTED the process! */ ksh->key_generation = key_generation; if (NULL == hs) @@ -1729,12 +3265,31 @@ build_key_state (struct HelperState *hs, ksh->helpers = hs; } ksh->denomkey_map = GNUNET_CONTAINER_multihashmap_create (1024, - GNUNET_YES); + true); ksh->signkey_map = GNUNET_CONTAINER_multipeermap_create (32, - GNUNET_NO /* MUST be NO! */); + false /* MUST be false! */); ksh->auditors = json_array (); GNUNET_assert (NULL != ksh->auditors); /* NOTE: fetches master-signed signkeys, but ALSO those that were revoked! */ + GNUNET_break (GNUNET_OK == + TEH_plugin->preflight (TEH_plugin->cls)); + if (NULL != ksh->global_fees) + json_decref (ksh->global_fees); + ksh->global_fees = json_array (); + qs = TEH_plugin->get_global_fees (TEH_plugin->cls, + &global_fee_info_cb, + ksh); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Loading global fees from DB: %d\n", + qs); + if (qs < 0) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); + GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs); + destroy_key_state (ksh, + true); + return NULL; + } qs = TEH_plugin->iterate_denominations (TEH_plugin->cls, &denomination_info_cb, ksh); @@ -1777,20 +3332,23 @@ build_key_state (struct HelperState *hs, true); return NULL; } + if (management_only) { ksh->management_only = true; return ksh; } + if (GNUNET_OK != finish_keys_response (ksh)) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Could not finish /keys response (likely no signing keys available yet)\n"); + "Could not finish /keys response (required data not configured yet)\n"); destroy_key_state (ksh, true); return NULL; } + return ksh; } @@ -1812,16 +3370,8 @@ TEH_keys_update_states () } -/** - * Obtain the key state for the current thread. Should ONLY be used - * directly if @a management_only is true. Otherwise use #TEH_keys_get_state(). - * - * @param management_only if we should NOT run 'finish_keys_response()' - * because we only need the state for the /management/keys API - * @return NULL on error - */ static struct TEH_KeyStateHandle * -get_key_state (bool management_only) +keys_get_state (bool management_only) { struct TEH_KeyStateHandle *old_ksh; struct TEH_KeyStateHandle *ksh; @@ -1837,9 +3387,9 @@ get_key_state (bool management_only) return ksh; } if ( (old_ksh->key_generation < key_generation) || - (GNUNET_TIME_absolute_is_past (old_ksh->signature_expires)) ) + (GNUNET_TIME_absolute_is_past (old_ksh->signature_expires.abs_time)) ) { - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Rebuilding /keys, generation upgrade from %llu to %llu\n", (unsigned long long) old_ksh->key_generation, (unsigned long long) key_generation); @@ -1857,27 +3407,58 @@ get_key_state (bool management_only) struct TEH_KeyStateHandle * +TEH_keys_get_state_for_management_only (void) +{ + return keys_get_state (true); +} + + +struct TEH_KeyStateHandle * TEH_keys_get_state (void) { struct TEH_KeyStateHandle *ksh; - ksh = get_key_state (false); + ksh = keys_get_state (false); if (NULL == ksh) return NULL; + if (ksh->management_only) { if (GNUNET_OK != finish_keys_response (ksh)) return NULL; } + return ksh; } +const struct TEH_GlobalFee * +TEH_keys_global_fee_by_time ( + struct TEH_KeyStateHandle *ksh, + struct GNUNET_TIME_Timestamp ts) +{ + for (const struct TEH_GlobalFee *gf = ksh->gf_head; + NULL != gf; + gf = gf->next) + { + if (GNUNET_TIME_timestamp_cmp (ts, + >=, + gf->start_date) && + GNUNET_TIME_timestamp_cmp (ts, + <, + gf->end_date)) + return gf; + } + return NULL; +} + + struct TEH_DenominationKey * -TEH_keys_denomination_by_hash (const struct GNUNET_HashCode *h_denom_pub, - struct MHD_Connection *conn, - MHD_RESULT *mret) +TEH_keys_denomination_by_hash ( + const struct TALER_DenominationHashP *h_denom_pub, + struct MHD_Connection *conn, + MHD_RESULT *mret) { struct TEH_KeyStateHandle *ksh; @@ -1890,25 +3471,29 @@ TEH_keys_denomination_by_hash (const struct GNUNET_HashCode *h_denom_pub, NULL); return NULL; } - return TEH_keys_denomination_by_hash2 (ksh, - h_denom_pub, - conn, - mret); + + return TEH_keys_denomination_by_hash_from_state (ksh, + h_denom_pub, + conn, + mret); } struct TEH_DenominationKey * -TEH_keys_denomination_by_hash2 (struct TEH_KeyStateHandle *ksh, - const struct GNUNET_HashCode *h_denom_pub, - struct MHD_Connection *conn, - MHD_RESULT *mret) +TEH_keys_denomination_by_hash_from_state ( + const struct TEH_KeyStateHandle *ksh, + const struct TALER_DenominationHashP *h_denom_pub, + struct MHD_Connection *conn, + MHD_RESULT *mret) { struct TEH_DenominationKey *dk; dk = GNUNET_CONTAINER_multihashmap_get (ksh->denomkey_map, - h_denom_pub); + &h_denom_pub->hash); if (NULL == dk) { + if (NULL == conn) + return NULL; *mret = TEH_RESPONSE_reply_unknown_denom_pub_hash (conn, h_denom_pub); return NULL; @@ -1917,33 +3502,220 @@ TEH_keys_denomination_by_hash2 (struct TEH_KeyStateHandle *ksh, } -struct TALER_DenominationSignature -TEH_keys_denomination_sign (const struct GNUNET_HashCode *h_denom_pub, - const void *msg, - size_t msg_size, - enum TALER_ErrorCode *ec) +enum TALER_ErrorCode +TEH_keys_denomination_batch_sign ( + unsigned int csds_length, + const struct TEH_CoinSignData csds[static csds_length], + bool for_melt, + struct TALER_BlindedDenominationSignature bss[static csds_length]) +{ + struct TEH_KeyStateHandle *ksh; + struct HelperDenomination *hd; + struct TALER_CRYPTO_RsaSignRequest rsrs[csds_length]; + struct TALER_CRYPTO_CsSignRequest csrs[csds_length]; + struct TALER_BlindedDenominationSignature rs[csds_length]; + struct TALER_BlindedDenominationSignature cs[csds_length]; + unsigned int rsrs_pos = 0; + unsigned int csrs_pos = 0; + enum TALER_ErrorCode ec; + + ksh = TEH_keys_get_state (); + if (NULL == ksh) + return TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING; + for (unsigned int i = 0; i<csds_length; i++) + { + const struct TALER_DenominationHashP *h_denom_pub = csds[i].h_denom_pub; + const struct TALER_BlindedPlanchet *bp = csds[i].bp; + + hd = GNUNET_CONTAINER_multihashmap_get (ksh->helpers->denom_keys, + &h_denom_pub->hash); + if (NULL == hd) + return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN; + if (bp->blinded_message->cipher != + hd->denom_pub.bsign_pub_key->cipher) + return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; + switch (hd->denom_pub.bsign_pub_key->cipher) + { + case GNUNET_CRYPTO_BSA_RSA: + rsrs[rsrs_pos].h_rsa = &hd->h_details.h_rsa; + rsrs[rsrs_pos].msg + = bp->blinded_message->details.rsa_blinded_message.blinded_msg; + rsrs[rsrs_pos].msg_size + = bp->blinded_message->details.rsa_blinded_message.blinded_msg_size; + rsrs_pos++; + break; + case GNUNET_CRYPTO_BSA_CS: + csrs[csrs_pos].h_cs = &hd->h_details.h_cs; + csrs[csrs_pos].blinded_planchet + = &bp->blinded_message->details.cs_blinded_message; + csrs_pos++; + break; + default: + return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; + } + } + + if ( (0 != csrs_pos) && + (0 != rsrs_pos) ) + { + memset (rs, + 0, + sizeof (rs)); + memset (cs, + 0, + sizeof (cs)); + } + ec = TALER_EC_NONE; + if (0 != csrs_pos) + { + ec = TALER_CRYPTO_helper_cs_batch_sign ( + ksh->helpers->csdh, + csrs_pos, + csrs, + for_melt, + (0 == rsrs_pos) ? bss : cs); + if (TALER_EC_NONE != ec) + { + for (unsigned int i = 0; i<csrs_pos; i++) + TALER_blinded_denom_sig_free (&cs[i]); + return ec; + } + TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_CS] += csrs_pos; + } + if (0 != rsrs_pos) + { + ec = TALER_CRYPTO_helper_rsa_batch_sign ( + ksh->helpers->rsadh, + rsrs_pos, + rsrs, + (0 == csrs_pos) ? bss : rs); + if (TALER_EC_NONE != ec) + { + for (unsigned int i = 0; i<csrs_pos; i++) + TALER_blinded_denom_sig_free (&cs[i]); + for (unsigned int i = 0; i<rsrs_pos; i++) + TALER_blinded_denom_sig_free (&rs[i]); + return ec; + } + TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_RSA] += rsrs_pos; + } + + if ( (0 != csrs_pos) && + (0 != rsrs_pos) ) + { + rsrs_pos = 0; + csrs_pos = 0; + for (unsigned int i = 0; i<csds_length; i++) + { + const struct TALER_BlindedPlanchet *bp = csds[i].bp; + + switch (bp->blinded_message->cipher) + { + case GNUNET_CRYPTO_BSA_RSA: + bss[i] = rs[rsrs_pos++]; + break; + case GNUNET_CRYPTO_BSA_CS: + bss[i] = cs[csrs_pos++]; + break; + default: + GNUNET_assert (0); + } + } + } + return TALER_EC_NONE; +} + + +enum TALER_ErrorCode +TEH_keys_denomination_cs_r_pub ( + const struct TEH_CsDeriveData *cdd, + bool for_melt, + struct GNUNET_CRYPTO_CSPublicRPairP *r_pub) { + const struct TALER_DenominationHashP *h_denom_pub = cdd->h_denom_pub; + const struct GNUNET_CRYPTO_CsSessionNonce *nonce = cdd->nonce; struct TEH_KeyStateHandle *ksh; - struct TALER_DenominationSignature none = { NULL }; + struct HelperDenomination *hd; ksh = TEH_keys_get_state (); if (NULL == ksh) { - *ec = TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING; - return none; + return TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING; + } + hd = GNUNET_CONTAINER_multihashmap_get (ksh->helpers->denom_keys, + &h_denom_pub->hash); + if (NULL == hd) + { + return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN; + } + if (GNUNET_CRYPTO_BSA_CS != + hd->denom_pub.bsign_pub_key->cipher) + { + return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; + } + + { + struct TALER_CRYPTO_CsDeriveRequest cdr = { + .h_cs = &hd->h_details.h_cs, + .nonce = nonce + }; + return TALER_CRYPTO_helper_cs_r_derive (ksh->helpers->csdh, + &cdr, + for_melt, + r_pub); + } +} + + +enum TALER_ErrorCode +TEH_keys_denomination_cs_batch_r_pub ( + unsigned int cdds_length, + const struct TEH_CsDeriveData cdds[static cdds_length], + bool for_melt, + struct GNUNET_CRYPTO_CSPublicRPairP r_pubs[static cdds_length]) +{ + struct TEH_KeyStateHandle *ksh; + struct HelperDenomination *hd; + struct TALER_CRYPTO_CsDeriveRequest cdrs[cdds_length]; + + ksh = TEH_keys_get_state (); + if (NULL == ksh) + { + return TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING; + } + for (unsigned int i = 0; i<cdds_length; i++) + { + const struct TALER_DenominationHashP *h_denom_pub = cdds[i].h_denom_pub; + const struct GNUNET_CRYPTO_CsSessionNonce *nonce = cdds[i].nonce; + + hd = GNUNET_CONTAINER_multihashmap_get (ksh->helpers->denom_keys, + &h_denom_pub->hash); + if (NULL == hd) + { + return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN; + } + if (GNUNET_CRYPTO_BSA_CS != + hd->denom_pub.bsign_pub_key->cipher) + { + return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; + } + cdrs[i].h_cs = &hd->h_details.h_cs; + cdrs[i].nonce = nonce; } - return TALER_CRYPTO_helper_denom_sign (ksh->helpers->dh, - h_denom_pub, - msg, - msg_size, - ec); + + return TALER_CRYPTO_helper_cs_r_batch_derive (ksh->helpers->csdh, + cdds_length, + cdrs, + for_melt, + r_pubs); } void -TEH_keys_denomination_revoke (const struct GNUNET_HashCode *h_denom_pub) +TEH_keys_denomination_revoke (const struct TALER_DenominationHashP *h_denom_pub) { struct TEH_KeyStateHandle *ksh; + struct HelperDenomination *hd; ksh = TEH_keys_get_state (); if (NULL == ksh) @@ -1951,9 +3723,30 @@ TEH_keys_denomination_revoke (const struct GNUNET_HashCode *h_denom_pub) GNUNET_break (0); return; } - TALER_CRYPTO_helper_denom_revoke (ksh->helpers->dh, - h_denom_pub); - TEH_keys_update_states (); + hd = GNUNET_CONTAINER_multihashmap_get (ksh->helpers->denom_keys, + &h_denom_pub->hash); + if (NULL == hd) + { + GNUNET_break (0); + return; + } + switch (hd->denom_pub.bsign_pub_key->cipher) + { + case GNUNET_CRYPTO_BSA_INVALID: + break; + case GNUNET_CRYPTO_BSA_RSA: + TALER_CRYPTO_helper_rsa_revoke (ksh->helpers->rsadh, + &hd->h_details.h_rsa); + TEH_keys_update_states (); + return; + case GNUNET_CRYPTO_BSA_CS: + TALER_CRYPTO_helper_cs_revoke (ksh->helpers->csdh, + &hd->h_details.h_cs); + TEH_keys_update_states (); + return; + } + GNUNET_break (0); + return; } @@ -1983,13 +3776,15 @@ TEH_keys_exchange_sign_ ( enum TALER_ErrorCode TEH_keys_exchange_sign2_ ( - struct TEH_KeyStateHandle *ksh, + void *cls, const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose, struct TALER_ExchangePublicKeyP *pub, struct TALER_ExchangeSignatureP *sig) { + struct TEH_KeyStateHandle *ksh = cls; enum TALER_ErrorCode ec; + TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_EDDSA]++; ec = TALER_CRYPTO_helper_esign_sign_ (ksh->helpers->esh, purpose, pub, @@ -2044,7 +3839,7 @@ TEH_keys_exchange_revoke (const struct TALER_ExchangePublicKeyP *exchange_pub) * Comparator used for a binary search by cherry_pick_date for @a key in the * `struct KeysResponseData` array. See libc's qsort() and bsearch() functions. * - * @param key pointer to a `struct GNUNET_TIME_Absolute` + * @param key pointer to a `struct GNUNET_TIME_Timestamp` * @param value pointer to a `struct KeysResponseData` array entry * @return 0 if time matches, -1 if key is smaller, 1 if key is larger */ @@ -2052,12 +3847,16 @@ static int krd_search_comparator (const void *key, const void *value) { - const struct GNUNET_TIME_Absolute *kd = key; + const struct GNUNET_TIME_Timestamp *kd = key; const struct KeysResponseData *krd = value; - if (kd->abs_value_us > krd->cherry_pick_date.abs_value_us) + if (GNUNET_TIME_timestamp_cmp (*kd, + >, + krd->cherry_pick_date)) return -1; - if (kd->abs_value_us < krd->cherry_pick_date.abs_value_us) + if (GNUNET_TIME_timestamp_cmp (*kd, + <, + krd->cherry_pick_date)) return 1; return 0; } @@ -2067,8 +3866,12 @@ MHD_RESULT TEH_keys_get_handler (struct TEH_RequestContext *rc, const char *const args[]) { - struct GNUNET_TIME_Absolute last_issue_date; + struct GNUNET_TIME_Timestamp last_issue_date; + const char *etag; + etag = MHD_lookup_connection_value (rc->connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_IF_NONE_MATCH); (void) args; { const char *have_cherrypick; @@ -2095,11 +3898,11 @@ TEH_keys_get_handler (struct TEH_RequestContext *rc, be a problem, as giving back 'older' data than what the client asks for (given that the client asks for data in the distant future) is not problematic */ - last_issue_date = GNUNET_TIME_absolute_from_s (cherrypickn); + last_issue_date = GNUNET_TIME_timestamp_from_s (cherrypickn); } else { - last_issue_date.abs_value_us = 0LLU; + last_issue_date = GNUNET_TIME_UNIT_ZERO_TS; } } @@ -2108,15 +3911,20 @@ TEH_keys_get_handler (struct TEH_RequestContext *rc, const struct KeysResponseData *krd; ksh = TEH_keys_get_state (); - if (NULL == ksh) + if ( (NULL == ksh) || + (0 == ksh->krd_array_length) ) { - if ( (SKR_LIMIT == skr_size) && - (rc->connection == skr_connection) ) + if ( ( (SKR_LIMIT == skr_size) && + (rc->connection == skr_connection) ) || + TEH_suicide) { - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, - "too many connections suspended on /keys"); + return TALER_MHD_reply_with_error ( + rc->connection, + MHD_HTTP_SERVICE_UNAVAILABLE, + TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, + TEH_suicide + ? "server terminating" + : "too many connections suspended waiting on /keys"); } return suspend_request (rc->connection); } @@ -2127,16 +3935,16 @@ TEH_keys_get_handler (struct TEH_RequestContext *rc, &krd_search_comparator); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Filtering /keys by cherry pick date %s found entry %u/%u\n", - GNUNET_STRINGS_absolute_time_to_string (last_issue_date), + GNUNET_TIME_timestamp2s (last_issue_date), (unsigned int) (krd - ksh->krd_array), ksh->krd_array_length); if ( (NULL == krd) && (ksh->krd_array_length > 0) ) { - if (0 != last_issue_date.abs_value_us) + if (! GNUNET_TIME_absolute_is_zero (last_issue_date.abs_time)) GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Client provided invalid cherry picking timestamp %s, returning full response\n", - GNUNET_STRINGS_absolute_time_to_string (last_issue_date)); + GNUNET_TIME_timestamp2s (last_issue_date)); krd = &ksh->krd_array[ksh->krd_array_length - 1]; } if (NULL == krd) @@ -2145,6 +3953,14 @@ TEH_keys_get_handler (struct TEH_RequestContext *rc, Wait until they are. */ return suspend_request (rc->connection); } + if ( (NULL != etag) && + (0 == strcmp (etag, + krd->etag)) ) + return TEH_RESPONSE_reply_not_modified (rc->connection, + krd->etag, + &setup_general_response_headers, + ksh); + return MHD_queue_response (rc->connection, MHD_HTTP_OK, (MHD_YES == @@ -2156,22 +3972,23 @@ TEH_keys_get_handler (struct TEH_RequestContext *rc, /** - * Load fees and expiration times (!) for the denomination type configured in - * section @a section_name. Before calling this function, the `start` and - * `validity_duration` times must already be initialized in @a meta. + * Load extension data, like fees, expiration times (!) and age restriction + * flags for the denomination type configured in section @a section_name. + * Before calling this function, the `start` and `validity_duration` times must + * already be initialized in @a meta. * * @param section_name section in the configuration to use * @param[in,out] meta denomination type data to complete * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue -load_fees (const char *section_name, - struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta) +load_extension_data (const char *section_name, + struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta) { struct GNUNET_TIME_Relative deposit_duration; struct GNUNET_TIME_Relative legal_duration; - GNUNET_assert (0 != meta->start.abs_value_us); /* caller bug */ + GNUNET_assert (! GNUNET_TIME_absolute_is_zero (meta->start.abs_time)); /* caller bug */ if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (TEH_cfg, section_name, @@ -2194,14 +4011,13 @@ load_fees (const char *section_name, "DURATION_LEGAL"); return GNUNET_SYSERR; } - /* NOTE: this is a change from the 0.8 semantics of the configuration: - before duration_spend was relative to 'start', not to 'expire_withdraw'. - But doing it this way avoids the error case where previously - duration_spend < duration_withdraw was not allowed. */ - meta->expire_deposit = GNUNET_TIME_absolute_add (meta->expire_withdraw, - deposit_duration); - meta->expire_legal = GNUNET_TIME_absolute_add (meta->expire_deposit, - legal_duration); + meta->expire_deposit + = GNUNET_TIME_absolute_to_timestamp ( + GNUNET_TIME_absolute_add (meta->expire_withdraw.abs_time, + deposit_duration)); + meta->expire_legal = GNUNET_TIME_absolute_to_timestamp ( + GNUNET_TIME_absolute_add (meta->expire_deposit.abs_time, + legal_duration)); if (GNUNET_OK != TALER_config_get_amount (TEH_cfg, section_name, @@ -2214,115 +4030,71 @@ load_fees (const char *section_name, section_name); return GNUNET_SYSERR; } - if (GNUNET_OK != - TALER_config_get_amount (TEH_cfg, - section_name, - "FEE_WITHDRAW", - &meta->fee_withdraw)) - { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - "Need amount for option `%s' in section `%s'\n", - "FEE_WITHDRAW", - section_name); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_config_get_amount (TEH_cfg, - section_name, - "FEE_DEPOSIT", - &meta->fee_deposit)) + if (0 != strcasecmp (TEH_currency, + meta->value.currency)) { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - "Need amount for option `%s' in section `%s'\n", - "FEE_DEPOSIT", - section_name); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_config_get_amount (TEH_cfg, - section_name, - "FEE_REFRESH", - &meta->fee_refresh)) - { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - "Need amount for option `%s' in section `%s'\n", - "FEE_REFRESH", - section_name); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Need denomination value in section `%s' to use currency `%s'\n", + section_name, + TEH_currency); return GNUNET_SYSERR; } if (GNUNET_OK != - TALER_config_get_amount (TEH_cfg, - section_name, - "FEE_REFUND", - &meta->fee_refund)) - { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - "Need amount for option `%s' in section `%s'\n", - "FEE_REFUND", - section_name); - return GNUNET_SYSERR; - } - if ( (0 != strcasecmp (TEH_currency, - meta->value.currency)) || - (0 != strcasecmp (TEH_currency, - meta->fee_withdraw.currency)) || - (0 != strcasecmp (TEH_currency, - meta->fee_deposit.currency)) || - (0 != strcasecmp (TEH_currency, - meta->fee_refresh.currency)) || - (0 != strcasecmp (TEH_currency, - meta->fee_refund.currency)) ) - { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - "Need amounts in section `%s' to use currency `%s'\n", - section_name, - TEH_currency); + TALER_config_get_denom_fees (TEH_cfg, + TEH_currency, + section_name, + &meta->fees)) return GNUNET_SYSERR; - } + meta->age_mask = load_age_mask (section_name); return GNUNET_OK; } enum GNUNET_GenericReturnValue -TEH_keys_load_fees (const struct GNUNET_HashCode *h_denom_pub, +TEH_keys_load_fees (struct TEH_KeyStateHandle *ksh, + const struct TALER_DenominationHashP *h_denom_pub, struct TALER_DenominationPublicKey *denom_pub, struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta) { - struct TEH_KeyStateHandle *ksh; struct HelperDenomination *hd; enum GNUNET_GenericReturnValue ok; - ksh = get_key_state (true); - if (NULL == ksh) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - hd = GNUNET_CONTAINER_multihashmap_get (ksh->helpers->denom_keys, - h_denom_pub); + &h_denom_pub->hash); if (NULL == hd) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Denomination %s not known\n", - GNUNET_h2s (h_denom_pub)); + GNUNET_h2s (&h_denom_pub->hash)); return GNUNET_NO; } meta->start = hd->start_time; - meta->expire_withdraw = GNUNET_TIME_absolute_add (meta->start, - hd->validity_duration); - ok = load_fees (hd->section_name, - meta); + meta->expire_withdraw = GNUNET_TIME_absolute_to_timestamp ( + GNUNET_TIME_absolute_add (meta->start.abs_time, + hd->validity_duration)); + ok = load_extension_data (hd->section_name, + meta); if (GNUNET_OK == ok) - denom_pub->rsa_public_key - = GNUNET_CRYPTO_rsa_public_key_dup (hd->denom_pub.rsa_public_key); + { + GNUNET_assert (GNUNET_CRYPTO_BSA_INVALID != + hd->denom_pub.bsign_pub_key->cipher); + TALER_denom_pub_copy (denom_pub, + &hd->denom_pub); + } else - denom_pub->rsa_public_key = NULL; + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "No fees for `%s', voiding key\n", + hd->section_name); + memset (denom_pub, + 0, + sizeof (*denom_pub)); + } return ok; } -int +enum GNUNET_GenericReturnValue TEH_keys_get_timing (const struct TALER_ExchangePublicKeyP *exchange_pub, struct TALER_EXCHANGEDB_SignkeyMetaData *meta) { @@ -2330,7 +4102,7 @@ TEH_keys_get_timing (const struct TALER_ExchangePublicKeyP *exchange_pub, struct HelperSignkey *hsk; struct GNUNET_PeerIdentity pid; - ksh = get_key_state (true); + ksh = TEH_keys_get_state_for_management_only (); if (NULL == ksh) { GNUNET_break (0); @@ -2340,11 +4112,19 @@ TEH_keys_get_timing (const struct TALER_ExchangePublicKeyP *exchange_pub, pid.public_key = exchange_pub->eddsa_pub; hsk = GNUNET_CONTAINER_multipeermap_get (ksh->helpers->esign_keys, &pid); + if (NULL == hsk) + { + GNUNET_break (0); + return GNUNET_NO; + } meta->start = hsk->start_time; - meta->expire_sign = GNUNET_TIME_absolute_add (meta->start, - hsk->validity_duration); - meta->expire_legal = GNUNET_TIME_absolute_add (meta->expire_sign, - signkey_legal_duration); + + meta->expire_sign = GNUNET_TIME_absolute_to_timestamp ( + GNUNET_TIME_absolute_add (meta->start.abs_time, + hsk->validity_duration)); + meta->expire_legal = GNUNET_TIME_absolute_to_timestamp ( + GNUNET_TIME_absolute_add (meta->expire_sign.abs_time, + signkey_legal_duration)); return GNUNET_OK; } @@ -2383,7 +4163,7 @@ struct FutureBuilderContext * @param value a `struct HelperDenomination` * @return #GNUNET_OK (continue to iterate) */ -static int +static enum GNUNET_GenericReturnValue add_future_denomkey_cb (void *cls, const struct GNUNET_HashCode *h_denom_pub, void *value) @@ -2391,7 +4171,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); @@ -2400,11 +4180,12 @@ add_future_denomkey_cb (void *cls, if (GNUNET_TIME_relative_is_zero (hd->validity_duration)) return GNUNET_OK; /* this key already expired! */ meta.start = hd->start_time; - meta.expire_withdraw = GNUNET_TIME_absolute_add (meta.start, - hd->validity_duration); + meta.expire_withdraw = GNUNET_TIME_absolute_to_timestamp ( + GNUNET_TIME_absolute_add (meta.start.abs_time, + hd->validity_duration)); if (GNUNET_OK != - load_fees (hd->section_name, - &meta)) + load_extension_data (hd->section_name, + &meta)) { /* Woops, couldn't determine fee structure!? */ return GNUNET_OK; @@ -2416,24 +4197,18 @@ add_future_denomkey_cb (void *cls, GNUNET_JSON_PACK ( TALER_JSON_pack_amount ("value", &meta.value), - GNUNET_JSON_pack_time_abs ("stamp_start", - meta.start), - GNUNET_JSON_pack_time_abs ("stamp_expire_withdraw", - meta.expire_withdraw), - GNUNET_JSON_pack_time_abs ("stamp_expire_deposit", - meta.expire_deposit), - GNUNET_JSON_pack_time_abs ("stamp_expire_legal", - meta.expire_legal), - GNUNET_JSON_pack_rsa_public_key ("denom_pub", - hd->denom_pub.rsa_public_key), - TALER_JSON_pack_amount ("fee_withdraw", - &meta.fee_withdraw), - TALER_JSON_pack_amount ("fee_deposit", - &meta.fee_deposit), - TALER_JSON_pack_amount ("fee_refresh", - &meta.fee_refresh), - TALER_JSON_pack_amount ("fee_refund", - &meta.fee_refund), + GNUNET_JSON_pack_timestamp ("stamp_start", + meta.start), + GNUNET_JSON_pack_timestamp ("stamp_expire_withdraw", + meta.expire_withdraw), + GNUNET_JSON_pack_timestamp ("stamp_expire_deposit", + meta.expire_deposit), + GNUNET_JSON_pack_timestamp ("stamp_expire_legal", + meta.expire_legal), + TALER_JSON_pack_denom_pub ("denom_pub", + &hd->denom_pub), + TALER_JSON_PACK_DENOM_FEES ("fee", + &meta.fees), GNUNET_JSON_pack_data_auto ("denom_secmod_sig", &hd->sm_sig), GNUNET_JSON_pack_string ("section_name", @@ -2453,7 +4228,7 @@ add_future_denomkey_cb (void *cls, * @param value a `struct HelperDenomination` * @return #GNUNET_OK (continue to iterate) */ -static int +static enum GNUNET_GenericReturnValue add_future_signkey_cb (void *cls, const struct GNUNET_PeerIdentity *pid, void *value) @@ -2461,8 +4236,8 @@ add_future_signkey_cb (void *cls, struct FutureBuilderContext *fbc = cls; struct HelperSignkey *hsk = value; struct SigningKey *sk; - struct GNUNET_TIME_Absolute stamp_expire; - struct GNUNET_TIME_Absolute legal_end; + struct GNUNET_TIME_Timestamp stamp_expire; + struct GNUNET_TIME_Timestamp legal_end; sk = GNUNET_CONTAINER_multipeermap_get (fbc->ksh->signkey_map, pid); @@ -2470,22 +4245,24 @@ add_future_signkey_cb (void *cls, return GNUNET_OK; /* skip: this key is already active */ if (GNUNET_TIME_relative_is_zero (hsk->validity_duration)) return GNUNET_OK; /* this key already expired! */ - stamp_expire = GNUNET_TIME_absolute_add (hsk->start_time, - hsk->validity_duration); - legal_end = GNUNET_TIME_absolute_add (stamp_expire, - signkey_legal_duration); + stamp_expire = GNUNET_TIME_absolute_to_timestamp ( + GNUNET_TIME_absolute_add (hsk->start_time.abs_time, + hsk->validity_duration)); + legal_end = GNUNET_TIME_absolute_to_timestamp ( + GNUNET_TIME_absolute_add (stamp_expire.abs_time, + signkey_legal_duration)); GNUNET_assert (0 == json_array_append_new ( fbc->signkeys, GNUNET_JSON_PACK ( GNUNET_JSON_pack_data_auto ("key", &hsk->exchange_pub), - GNUNET_JSON_pack_time_abs ("stamp_start", - hsk->start_time), - GNUNET_JSON_pack_time_abs ("stamp_expire", - stamp_expire), - GNUNET_JSON_pack_time_abs ("stamp_end", - legal_end), + GNUNET_JSON_pack_timestamp ("stamp_start", + hsk->start_time), + GNUNET_JSON_pack_timestamp ("stamp_expire", + stamp_expire), + GNUNET_JSON_pack_timestamp ("stamp_end", + legal_end), GNUNET_JSON_pack_data_auto ("signkey_secmod_sig", &hsk->sm_sig)))); return GNUNET_OK; @@ -2499,11 +4276,12 @@ TEH_keys_management_get_keys_handler (const struct TEH_RequestHandler *rh, struct TEH_KeyStateHandle *ksh; json_t *reply; - ksh = get_key_state (true); + (void) rh; + ksh = TEH_keys_get_state_for_management_only (); if (NULL == ksh) { return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, + MHD_HTTP_SERVICE_UNAVAILABLE, TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, "no key state"); } @@ -2516,8 +4294,10 @@ TEH_keys_management_get_keys_handler (const struct TEH_RequestHandler *rh, .signkeys = json_array () }; - if (GNUNET_is_zero (&denom_sm_pub)) + if ( (GNUNET_is_zero (&denom_rsa_sm_pub)) && + (GNUNET_is_zero (&denom_cs_sm_pub)) ) { + /* Either IPC failed, or neither helper had any denominations configured. */ return TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_GATEWAY, TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE, @@ -2530,7 +4310,6 @@ TEH_keys_management_get_keys_handler (const struct TEH_RequestHandler *rh, TALER_EC_EXCHANGE_SIGNKEY_HELPER_UNAVAILABLE, NULL); } - // then a secmod helper is not yet running and we should return an MHD_HTTP_BAD_GATEWAY! GNUNET_assert (NULL != fbc.denoms); GNUNET_assert (NULL != fbc.signkeys); GNUNET_CONTAINER_multihashmap_iterate (ksh->helpers->denom_keys, @@ -2547,7 +4326,9 @@ TEH_keys_management_get_keys_handler (const struct TEH_RequestHandler *rh, GNUNET_JSON_pack_data_auto ("master_pub", &TEH_master_public_key), GNUNET_JSON_pack_data_auto ("denom_secmod_public_key", - &denom_sm_pub), + &denom_rsa_sm_pub), + GNUNET_JSON_pack_data_auto ("denom_secmod_cs_public_key", + &denom_cs_sm_pub), GNUNET_JSON_pack_data_auto ("signkey_secmod_public_key", &esign_sm_pub)); GNUNET_log (GNUNET_ERROR_TYPE_INFO, |