/* This file is part of TALER Copyright (C) 2014--2020 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 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 */ /** * @file taler-exchange-httpd_keystate.c * @brief management of our coin signing keys * @author Florian Dold * @author Benedikt Mueller * @author Christian Grothoff */ #include "platform.h" #include #include "taler_json_lib.h" #include "taler_mhd_lib.h" #include "taler-exchange-httpd_keystate.h" #include "taler-exchange-httpd_responses.h" #include "taler_exchangedb_plugin.h" /** * 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! */ #define EXCHANGE_PROTOCOL_VERSION "8:0:0" /** * Signatures of an auditor over a denomination key of this exchange. */ struct AuditorSignature { /** * We store the signatures in a DLL. */ struct AuditorSignature *prev; /** * We store the signatures in a DLL. */ struct AuditorSignature *next; /** * A signature from the auditor. */ struct TALER_AuditorSignatureP asig; /** * Public key of the auditor. */ struct TALER_AuditorPublicKeyP apub; /** * URL of the auditor. Allocated at the end of this struct. */ const char *auditor_url; }; /** * Entry in sorted array of denomination keys. Sorted by starting * "start" time (validity period) of the `struct * TALER_DenominationKeyValidityPS`. */ struct DenominationKeyEntry { /** * Reference to the public key. * (Must also be in the `denomkey_map`). */ const struct TALER_EXCHANGEDB_DenominationKey *dki; /** * Head of DLL of signatures for this @e dki. */ struct AuditorSignature *as_head; /** * Tail of DLL of signatures for this @e dki. */ struct AuditorSignature *as_tail; /** * Hash of the public denomination key. */ struct GNUNET_HashCode denom_key_hash; }; /** * Entry in (sorted) array with possible pre-build responses for /keys. * We keep pre-build responses for the various (valid) cherry-picking * values around. */ struct KeysResponseData { /** * Response to return if the client supports (deflate) compression. */ struct MHD_Response *response_compressed; /** * Response to return if the client does not support compression. */ struct MHD_Response *response_uncompressed; /** * 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; }; /** * State we keep around while building an individual entry in the * `struct KeysResponseData` array, i.e. the global state for ONE of * the responses. */ struct ResponseBuilderContext { /** * Hash context we used to combine the hashes of all denomination * keys into one big hash for signing. */ struct GNUNET_HashContext *hash_context; /** * JSON array with denomination key information. */ json_t *denom_keys_array; /** * JSON array with auditor information. */ json_t *auditors_array; /** * Keys after what issue date do we care about? */ struct GNUNET_TIME_Absolute last_issue_date; /** * Flag set to #GNUNET_SYSERR on internal errors */ int error; }; /** * State we keep around while building the `struct KeysResponseData` * array, i.e. the global state for all of the responses. */ struct ResponseFactoryContext { /** * JSON array with revoked denomination keys. Every response * always returns the full list (cherry picking does not apply * for key revocations, as we cannot sort those by issue date). */ json_t *recoup_array; /** * JSON array with signing keys. Every response includes the full * list, as it should be quite short anyway, and for simplicity the * client only communicates the one time stamp of the last * denomination key it knows when cherry picking. */ json_t *sign_keys_array; /** * Sorted array of denomination keys. Length is @e denomkey_array_length. * Entries are sorted by the validity period's starting time. */ struct DenominationKeyEntry *denomkey_array; /** * The main key state we are building everything for. */ struct TEH_KS_StateHandle *key_state; /** * Time stamp used as "now". */ struct GNUNET_TIME_Absolute now; /** * Length of the @e denomkey_array. */ unsigned int denomkey_array_length; }; /** * Snapshot of the (coin and signing) keys (including private keys) of * the exchange. There can be multiple instances of this struct, as it is * reference counted and only destroyed once the last user is done * with it. The current instance is acquired using * #TEH_KS_acquire(). Using this function increases the * reference count. The contents of this structure (except for the * reference counter) should be considered READ-ONLY until it is * ultimately destroyed (as there can be many concurrent users). */ struct TEH_KS_StateHandle { /** * Mapping from denomination keys to denomination key issue struct. * Used to lookup the key by hash. */ struct GNUNET_CONTAINER_MultiHashMap *denomkey_map; /** * Mapping from revoked denomination keys to denomination key issue struct. * Used to lookup the key by hash. */ struct GNUNET_CONTAINER_MultiHashMap *revoked_map; /** * Sorted array of responses to /keys (MUST be sorted by cherry-picking date) of * length @e krd_array_length; */ struct KeysResponseData *krd_array; /** * When did we initiate the key reloading? */ struct GNUNET_TIME_Absolute reload_time; /** * When is the next key invalid and we have to reload? (We also * reload on SIGUSR1.) */ struct GNUNET_TIME_Absolute next_reload; /** * When does the first active denomination key expire (for deposit)? */ struct GNUNET_TIME_Absolute min_dk_expire; /** * Exchange signing key that should be used currently. */ struct TALER_EXCHANGEDB_PrivateSigningKeyInformationP current_sign_key_issue; /** * Reference count. The struct is released when the RC hits zero. Once * this object is aliased, the reference counter must only be changed while * holding the #internal_key_state_mutex. */ unsigned int refcnt; /** * Length of the @e krd_array. */ unsigned int krd_array_length; }; /** * Exchange key state. This is the long-term, read-only internal global state, * which the various threads "lock" to use in read-only ways. We eventually * create a completely new object "on the side" and then start to return * the new read-only object to threads that ask. Once none of the threads * use the previous object (RC drops to zero), we discard it. * * Thus, this instance should never be used directly, instead reserve * access via #TEH_KS_acquire() and release it via #TEH_KS_release(). * * As long as MHD threads are running, access to this field requires * locking the #internal_key_state_mutex. */ static struct TEH_KS_StateHandle *internal_key_state; /** * Mutex protecting access to #internal_key_state. */ static pthread_mutex_t internal_key_state_mutex = PTHREAD_MUTEX_INITIALIZER; /** * Configuration value LOOKAHEAD_PROVIDE that says for how far in the * future we want to provide exchange key information to clients. */ static struct GNUNET_TIME_Relative conf_duration_provide; /* ************************** Clean up logic *********************** */ /** * Release memory used by @a rfc. * * @param rfc factory to release (but do not #GNUNET_free() rfc itself!) */ static void destroy_response_factory (struct ResponseFactoryContext *rfc) { if (NULL != rfc->recoup_array) { json_decref (rfc->recoup_array); rfc->recoup_array = NULL; } if (NULL != rfc->sign_keys_array) { json_decref (rfc->sign_keys_array); rfc->sign_keys_array = NULL; } for (unsigned int i = 0; idenomkey_array_length; i++) { struct DenominationKeyEntry *dke = &rfc->denomkey_array[i]; struct AuditorSignature *as; while (NULL != (as = dke->as_head)) { GNUNET_CONTAINER_DLL_remove (dke->as_head, dke->as_tail, as); GNUNET_free (as); } } GNUNET_array_grow (rfc->denomkey_array, rfc->denomkey_array_length, 0); } /** * Release memory used by @a rbc. * * @param rbc memory to release, excluding @a rbc itself */ static void destroy_response_builder (struct ResponseBuilderContext *rbc) { if (NULL != rbc->denom_keys_array) { json_decref (rbc->denom_keys_array); rbc->denom_keys_array = NULL; } if (NULL != rbc->auditors_array) { json_decref (rbc->auditors_array); rbc->auditors_array = NULL; } if (NULL != rbc->hash_context) { GNUNET_CRYPTO_hash_context_abort (rbc->hash_context); rbc->hash_context = NULL; } } /** * Iterator for freeing denomination keys. * * @param cls closure with the `struct TEH_KS_StateHandle` (unused) * @param key hash of the denomination key (unused) * @param value coin details, a `struct TALER_EXCHANGEDB_DenominationKey` * @return #GNUNET_OK to continue to iterate, * #GNUNET_NO to stop iteration with no error, * #GNUNET_SYSERR to abort iteration with error! */ static int free_denom_key (void *cls, const struct GNUNET_HashCode *key, void *value) { struct TALER_EXCHANGEDB_DenominationKey *dki = value; (void) cls; (void) key; if (NULL != dki->denom_priv.rsa_private_key) GNUNET_CRYPTO_rsa_private_key_free (dki->denom_priv.rsa_private_key); GNUNET_CRYPTO_rsa_public_key_free (dki->denom_pub.rsa_public_key); GNUNET_free (dki); return GNUNET_OK; } /** * Internal function to free key state. Reference count must be at zero. * * @param key_state the key state to free */ static void ks_free (struct TEH_KS_StateHandle *key_state) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "KS release called (%p)\n", key_state); GNUNET_assert (0 == key_state->refcnt); if (NULL != key_state->denomkey_map) { GNUNET_CONTAINER_multihashmap_iterate (key_state->denomkey_map, &free_denom_key, key_state); GNUNET_CONTAINER_multihashmap_destroy (key_state->denomkey_map); key_state->denomkey_map = NULL; } if (NULL != key_state->revoked_map) { GNUNET_CONTAINER_multihashmap_iterate (key_state->revoked_map, &free_denom_key, key_state); GNUNET_CONTAINER_multihashmap_destroy (key_state->revoked_map); key_state->revoked_map = NULL; } for (unsigned int i = 0; ikrd_array_length; i++) { struct KeysResponseData *krd = &key_state->krd_array[i]; if (NULL != krd->response_compressed) MHD_destroy_response (krd->response_compressed); if (NULL != krd->response_uncompressed) MHD_destroy_response (krd->response_uncompressed); } GNUNET_array_grow (key_state->krd_array, key_state->krd_array_length, 0); GNUNET_free (key_state); } /* ************************* Signal logic ************************** */ /** * Pipe used for signaling reloading of our key state. */ static int reload_pipe[2] = { -1, -1 }; /** * Handle a signal, writing relevant signal numbers to the pipe. * * @param signal_number the signal number */ static void handle_signal (int signal_number) { char c = (char) signal_number; /* never seen a signal_number > 127 on any platform */ (void) ! write (reload_pipe[1], &c, 1); /* While one might like to "handle errors" here, even logging via fprintf() isn't safe inside of a signal handler. So there is nothing we safely CAN do. OTOH, also very little that can go wrong in practice. Calling _exit() on errors might be a possibility, but that might do more harm than good. */// } /* ************************** State builder ************************ */ /** * Convert the public part of denomination key data to a JSON object. * * @param pk public key of the denomination * @param dki the denomination key issue information * @return a JSON object describing the denomination key issue (public part) */ static json_t * denom_key_issue_to_json ( const struct TALER_DenominationPublicKey *pk, const struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki) { struct TALER_Amount value; struct TALER_Amount fee_withdraw; struct TALER_Amount fee_deposit; struct TALER_Amount fee_refresh; struct TALER_Amount fee_refund; TALER_amount_ntoh (&value, &dki->properties.value); TALER_amount_ntoh (&fee_withdraw, &dki->properties.fee_withdraw); TALER_amount_ntoh (&fee_deposit, &dki->properties.fee_deposit); TALER_amount_ntoh (&fee_refresh, &dki->properties.fee_refresh); TALER_amount_ntoh (&fee_refund, &dki->properties.fee_refund); return json_pack ("{s:o, s:o, s:o, s:o, s:o," " s:o, s:o, s:o, s:o, s:o," " s:o}", "master_sig", GNUNET_JSON_from_data_auto (&dki->signature), "stamp_start", GNUNET_JSON_from_time_abs (GNUNET_TIME_absolute_ntoh ( dki->properties.start)), "stamp_expire_withdraw", GNUNET_JSON_from_time_abs (GNUNET_TIME_absolute_ntoh ( dki->properties.expire_withdraw)), "stamp_expire_deposit", GNUNET_JSON_from_time_abs (GNUNET_TIME_absolute_ntoh ( dki->properties.expire_deposit)), "stamp_expire_legal", GNUNET_JSON_from_time_abs (GNUNET_TIME_absolute_ntoh ( dki->properties.expire_legal)), /* 5 entries until here */ "denom_pub", GNUNET_JSON_from_rsa_public_key (pk->rsa_public_key), "value", TALER_JSON_from_amount (&value), "fee_withdraw", TALER_JSON_from_amount (&fee_withdraw), "fee_deposit", TALER_JSON_from_amount (&fee_deposit), "fee_refresh", TALER_JSON_from_amount (&fee_refresh), /* 10 entries until here */ "fee_refund", TALER_JSON_from_amount (&fee_refund)); } /** * Store a copy of @a dki in @a map. * * @param map hash map to store @a dki in * @param dki information to store in @a map * @return #GNUNET_OK on success, * #GNUNET_NO if such an entry already exists */ static int store_in_map (struct GNUNET_CONTAINER_MultiHashMap *map, const struct TALER_EXCHANGEDB_DenominationKey *dki) { /* First, we verify that the @a dki is actually well-formed. While it comes from our own hard disk, there is the possibility of misconfiguration (i.e. bogus file in the directory), or that the administrator used the wrong master public key, and we should not accept entries that are not well-formed. */// { const struct TALER_EXCHANGEDB_DenominationKeyInformationP *dkip; struct TALER_DenominationKeyValidityPS denom_key_issue; dkip = &dki->issue; denom_key_issue = dkip->properties; /* The above is straight from our disk. We should not trust that it is well-formed, so we setup crucial fields ourselves. */ denom_key_issue.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY); denom_key_issue.purpose.size = htonl (sizeof (struct TALER_DenominationKeyValidityPS)); denom_key_issue.master = TEH_master_public_key; /* Check that the data we read matches our expectations */ if (0 != GNUNET_memcmp (&denom_key_issue, &dkip->properties)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Invalid fields in denomination key `%s'\n", GNUNET_h2s (&dkip->properties.denom_hash)); return GNUNET_SYSERR; } /* Also check the signature by the master public key */ if (GNUNET_SYSERR == GNUNET_CRYPTO_eddsa_verify ( TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY, &denom_key_issue, &dkip->signature.eddsa_signature, &TEH_master_public_key.eddsa_pub)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Invalid signature on denomination key `%s'\n", GNUNET_h2s (&dkip->properties.denom_hash)); return GNUNET_SYSERR; } } /* We need to make a deep copy of the @a dki, as the original was allocated elsewhere and will be freed by the caller. */ { struct TALER_EXCHANGEDB_DenominationKey *d2; d2 = GNUNET_new (struct TALER_EXCHANGEDB_DenominationKey); d2->issue = dki->issue; if (GNUNET_OK != GNUNET_CONTAINER_multihashmap_put (map, &d2->issue.properties.denom_hash, d2, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Duplicate denomination key `%s'\n", GNUNET_h2s (&d2->issue.properties.denom_hash)); GNUNET_free (d2); return GNUNET_NO; } /* finish *deep* part of deep copy */ if (NULL != dki->denom_priv.rsa_private_key) { /* we might be past the withdraw period, so private key could have been deleted, so only try to (deep) copy if non-NULL. */ d2->denom_priv.rsa_private_key = GNUNET_CRYPTO_rsa_private_key_dup (dki->denom_priv.rsa_private_key); } d2->denom_pub.rsa_public_key = GNUNET_CRYPTO_rsa_public_key_dup (dki->denom_pub.rsa_public_key); } return GNUNET_OK; } /** * Closure for #add_revocations_transaction(). */ struct AddRevocationContext { /** * Denomination key that is revoked. */ const struct TALER_EXCHANGEDB_DenominationKey *dki; /** * Signature affirming the revocation. */ const struct TALER_MasterSignatureP *revocation_master_sig; }; /** * Execute transaction to add revocations. * * @param cls closure with the `struct AddRevocationContext *` * @param connection NULL * @param session database session to use * @param[out] mhd_ret not used * @return transaction status */ static enum GNUNET_DB_QueryStatus add_revocations_transaction (void *cls, struct MHD_Connection *connection, struct TALER_EXCHANGEDB_Session *session, MHD_RESULT *mhd_ret) { struct AddRevocationContext *arc = cls; enum GNUNET_DB_QueryStatus qs; struct TALER_MasterSignatureP master_sig; uint64_t rowid; (void) connection; (void) mhd_ret; qs = TEH_plugin->get_denomination_revocation (TEH_plugin->cls, session, &arc->dki->issue.properties. denom_hash, &master_sig, &rowid); if (0 > qs) return qs; /* failure */ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) return qs; /* already exists == success */ return TEH_plugin->insert_denomination_revocation (TEH_plugin->cls, session, &arc->dki->issue.properties .denom_hash, arc->revocation_master_sig); } /** * Execute transaction to add a denomination to the DB. * * @param cls closure with the `const struct TALER_EXCHANGEDB_DenominationKey *` * @param connection NULL * @param session database session to use * @param[out] mhd_ret not used * @return transaction status */ static enum GNUNET_DB_QueryStatus add_denomination_transaction (void *cls, struct MHD_Connection *connection, struct TALER_EXCHANGEDB_Session *session, MHD_RESULT *mhd_ret) { const struct TALER_EXCHANGEDB_DenominationKey *dki = cls; enum GNUNET_DB_QueryStatus qs; struct TALER_EXCHANGEDB_DenominationKeyInformationP issue_exists; (void) connection; (void) mhd_ret; qs = TEH_plugin->get_denomination_info (TEH_plugin->cls, session, &dki->issue.properties.denom_hash, &issue_exists); if (0 > qs) return qs; if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) return qs; return TEH_plugin->insert_denomination_info (TEH_plugin->cls, session, &dki->denom_pub, &dki->issue); } /** * Iterator for (re)loading/initializing denomination keys. * * @param cls closure with a `struct ResponseFactoryContext *` * @param dki the denomination key issue * @param alias coin alias * @return #GNUNET_OK to continue to iterate, * #GNUNET_NO to stop iteration with no error, * #GNUNET_SYSERR to abort iteration with error! */ static int reload_keys_denom_iter (void *cls, const char *alias, const struct TALER_EXCHANGEDB_DenominationKey *dki) { struct ResponseFactoryContext *rfc = cls; struct TEH_KS_StateHandle *key_state = rfc->key_state; struct GNUNET_TIME_Absolute start; struct GNUNET_TIME_Absolute horizon; struct GNUNET_TIME_Absolute expire_deposit; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Loading denomination key `%s' (%s)\n", alias, GNUNET_h2s (&dki->issue.properties.denom_hash)); expire_deposit = GNUNET_TIME_absolute_ntoh ( dki->issue.properties.expire_deposit); if (expire_deposit.abs_value_us < rfc->now.abs_value_us) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Skipping expired denomination key `%s'\n", alias); return GNUNET_OK; } if (0 != GNUNET_memcmp (&dki->issue.properties.master, &TEH_master_public_key)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Master key in denomination key file `%s' does not match! Skipping it.\n", alias); return GNUNET_OK; } horizon = GNUNET_TIME_absolute_add (rfc->now, conf_duration_provide); start = GNUNET_TIME_absolute_ntoh (dki->issue.properties.start); if (start.abs_value_us > horizon.abs_value_us) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Skipping future denomination key `%s' (%s), validity starts at %s\n", alias, GNUNET_h2s (&dki->issue.properties.denom_hash), GNUNET_STRINGS_absolute_time_to_string (start)); return GNUNET_OK; } if (GNUNET_OK != TEH_DB_run_transaction (NULL, "add denomination key", NULL, &add_denomination_transaction, (void *) dki)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Could not persist denomination key %s in DB. Committing suicide via SIGTERM.\n", GNUNET_h2s (&dki->issue.properties.denom_hash)); handle_signal (SIGTERM); return GNUNET_SYSERR; } GNUNET_assert (NULL != dki->denom_priv.rsa_private_key); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Adding denomination key `%s' (%s) to active set\n", alias, GNUNET_h2s (&dki->issue.properties.denom_hash)); if (GNUNET_NO /* entry already exists */ == store_in_map (key_state->denomkey_map, dki)) return GNUNET_OK; /* do not update expiration if entry exists */ key_state->min_dk_expire = GNUNET_TIME_absolute_min (key_state->min_dk_expire, expire_deposit); return GNUNET_OK; } /** * Iterator for revocation of denomination keys. * * @param cls closure with a `struct ResponseFactoryContext *` * @param denom_hash hash of revoked denomination public key * @param revocation_master_sig signature showing @a denom_hash was revoked * @return #GNUNET_OK to continue to iterate, * #GNUNET_NO to stop iteration with no error, * #GNUNET_SYSERR to abort iteration with error! */ static int revocations_iter (void *cls, const struct GNUNET_HashCode *denom_hash, const struct TALER_MasterSignatureP *revocation_master_sig) { struct ResponseFactoryContext *rfc = cls; struct TEH_KS_StateHandle *key_state = rfc->key_state; struct AddRevocationContext arc; struct TALER_EXCHANGEDB_DenominationKey *dki; dki = GNUNET_CONTAINER_multihashmap_get (key_state->denomkey_map, denom_hash); if (NULL == dki) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Revoked denomination `%s' unknown (or duplicate file), ignoring revocation\n", GNUNET_h2s (denom_hash)); return GNUNET_OK; } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Adding denomination key `%s' to revocation set\n", GNUNET_h2s (denom_hash)); GNUNET_assert (GNUNET_YES == GNUNET_CONTAINER_multihashmap_remove (key_state->denomkey_map, denom_hash, dki)); GNUNET_assert (GNUNET_YES == GNUNET_CONTAINER_multihashmap_put (key_state->revoked_map, &dki->issue.properties. denom_hash, dki, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); /* Try to insert revocation into DB */ arc.dki = dki; arc.revocation_master_sig = revocation_master_sig; if (GNUNET_OK != TEH_DB_run_transaction (NULL, "add denomination key revocation", NULL, &add_revocations_transaction, &arc)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to add revocation to database. This is fatal. Committing suicide via SIGTERM.\n"); handle_signal (SIGTERM); return GNUNET_SYSERR; } { json_t *obj; obj = json_pack ("{s:o}", "h_denom_pub", GNUNET_JSON_from_data_auto (denom_hash)); if (NULL == obj) { GNUNET_break (0); return GNUNET_SYSERR; } if (0 != json_array_append_new (rfc->recoup_array, obj)) { GNUNET_break (0); return GNUNET_SYSERR; } } return GNUNET_OK; } /** * Convert the public part of a sign key issue to a JSON object. * * @param ski the sign key issue * @param ski_sig signature over @a ski * @return a JSON object describing the sign key issue (public part) */ static json_t * sign_key_issue_to_json (const struct TALER_ExchangeSigningKeyValidityPS *ski, const struct TALER_MasterSignatureP *ski_sig) { return json_pack ("{s:o, s:o, s:o, s:o, s:o}", "stamp_start", GNUNET_JSON_from_time_abs (GNUNET_TIME_absolute_ntoh ( ski->start)), "stamp_expire", GNUNET_JSON_from_time_abs (GNUNET_TIME_absolute_ntoh ( ski->expire)), "stamp_end", GNUNET_JSON_from_time_abs (GNUNET_TIME_absolute_ntoh (ski->end)), "master_sig", GNUNET_JSON_from_data_auto (ski_sig), "key", GNUNET_JSON_from_data_auto (&ski->signkey_pub)); } /** * Iterator for sign keys. Adds current and near-future signing keys * to the `sign_keys_array` and stores the current one in the * `key_state`. * * @param cls closure with the `struct ResponseFactoryContext *` * @param filename name of the file the key came from * @param ski the sign key issue * @return #GNUNET_OK to continue to iterate, * #GNUNET_NO to stop iteration with no error, * #GNUNET_SYSERR to abort iteration with error! */ static int reload_keys_sign_iter ( void *cls, const char *filename, const struct TALER_EXCHANGEDB_PrivateSigningKeyInformationP *ski) { struct ResponseFactoryContext *rfc = cls; struct TEH_KS_StateHandle *key_state = rfc->key_state; struct GNUNET_TIME_Absolute now; struct GNUNET_TIME_Absolute horizon; horizon = GNUNET_TIME_relative_to_absolute (conf_duration_provide); if (GNUNET_TIME_absolute_ntoh (ski->issue.start).abs_value_us > horizon.abs_value_us) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Skipping future signing key `%s'\n", filename); return GNUNET_OK; } now = GNUNET_TIME_absolute_get (); if (GNUNET_TIME_absolute_ntoh (ski->issue.expire).abs_value_us < now.abs_value_us) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Skipping expired signing key `%s'\n", filename); return GNUNET_OK; } if (0 != GNUNET_memcmp (&ski->issue.master_public_key, &TEH_master_public_key)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Master key in signing key file `%s' does not match! Skipping it.\n", filename); return GNUNET_OK; } /* The signkey is valid at this time, check if it's more recent than what we have so far! */ if ( (GNUNET_TIME_absolute_ntoh ( key_state->current_sign_key_issue.issue.start).abs_value_us < GNUNET_TIME_absolute_ntoh (ski->issue.start).abs_value_us) && (GNUNET_TIME_absolute_ntoh (ski->issue.start).abs_value_us < now.abs_value_us) ) { /* We use the most recent one, if it is valid now (not just in the near future) */ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Found signing key valid until `%s'\n", GNUNET_STRINGS_absolute_time_to_string ( GNUNET_TIME_absolute_ntoh ( key_state->current_sign_key_issue.issue.end))); key_state->current_sign_key_issue = *ski; } if (0 != json_array_append_new (rfc->sign_keys_array, sign_key_issue_to_json (&ski->issue, &ski->master_sig))) { GNUNET_break (0); return GNUNET_SYSERR; } return GNUNET_OK; } /** * @brief Iterator called with auditor information. * Check that the @a mpub actually matches this exchange, and then * add the auditor information to our /keys response (if it is * (still) applicable). * * @param cls closure with the `struct ResponseFactoryContext *` * @param apub the auditor's public key * @param auditor_url URL of the auditor * @param mpub the exchange's public key (as expected by the auditor) * @param dki_len length of @a dki and @a asigs * @param asigs array with the auditor's signatures, of length @a dki_len * @param dki array of denomination coin data signed by the auditor * @return #GNUNET_OK to continue to iterate, * #GNUNET_NO to stop iteration with no error, * #GNUNET_SYSERR to abort iteration with error! */ static int reload_auditor_iter (void *cls, const struct TALER_AuditorPublicKeyP *apub, const char *auditor_url, const struct TALER_MasterPublicKeyP *mpub, unsigned int dki_len, const struct TALER_AuditorSignatureP *asigs, const struct TALER_DenominationKeyValidityPS *dki) { struct ResponseFactoryContext *rfc = cls; struct TEH_KS_StateHandle *key_state = rfc->key_state; /* Check if the signature is at least for this exchange. */ if (0 != memcmp (&mpub->eddsa_pub, &TEH_master_public_key, sizeof (struct GNUNET_CRYPTO_EddsaPublicKey))) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Auditing information provided for a different exchange, ignored\n"); return GNUNET_OK; } /* Filter the auditor information for those for which the keys actually match the denomination keys that are active right now */ for (unsigned int i = 0; idenomkey_map, &dki[i].denom_hash)) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Found auditor signature for DK `%s', but key is not in active map\n", GNUNET_h2s (&dki[i].denom_hash)); continue; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Found auditor signature for DK `%s'\n", GNUNET_h2s (&dki[i].denom_hash)); /* Note: the array is sorted, we could theoretically speed this up using a binary search. */ matched = GNUNET_NO; for (unsigned int j = 0; jdenomkey_array_length; j++) { struct DenominationKeyEntry *dke = &rfc->denomkey_array[j]; struct AuditorSignature *as; if (0 != memcmp (&dki[i].denom_hash, &dke->dki->issue.properties.denom_hash, sizeof (struct GNUNET_HashCode))) continue; if (0 != memcmp (&dki[i], &dke->dki->issue.properties, sizeof (struct TALER_DenominationKeyValidityPS))) { /* if the hash is the same, the properties should also match! */ GNUNET_break (0); continue; } as = GNUNET_malloc (sizeof (struct AuditorSignature) + strlen (auditor_url) + 1); as->asig = asigs[i]; as->apub = *apub; as->auditor_url = (const char *) &as[1]; memcpy (&as[1], auditor_url, strlen (auditor_url) + 1); GNUNET_CONTAINER_DLL_insert (dke->as_head, dke->as_tail, as); matched = GNUNET_YES; break; } if (GNUNET_NO == matched) { GNUNET_break (0); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "DK `%s' is in active map, but not in array!?\n", GNUNET_h2s (&dki[i].denom_hash)); } } return GNUNET_OK; } /** * Initialize the `denomkey_array`. We are called once per * array index, which is tracked in `denomkey_array_length` (the * array will be of sufficient size). Set the pointer to the * denomination key and increment the `denomkey_array_length`. * * @param cls a `struct ResponseFactoryContext` * @param denom_hash hash of a denomination key * @param value a `struct TALER_EXCHANGEDB_DenominationKey *` * @return #GNUNET_OK */ static int initialize_denomkey_array (void *cls, const struct GNUNET_HashCode *denom_hash, void *value) { struct ResponseFactoryContext *rfc = cls; struct TALER_EXCHANGEDB_DenominationKey *dki = value; rfc->denomkey_array[rfc->denomkey_array_length].denom_key_hash = *denom_hash; rfc->denomkey_array[rfc->denomkey_array_length++].dki = dki; return GNUNET_OK; } /** * Comparator used to sort the `struct DenominationKeyEntry` array * by the validity period's starting time of the keys. Must match * the expected sorting by cherry_pick_date (which is based on the * issue.properties.start) used in #krd_search_comparator. * * @param k1 a `struct DenominationKeyEntry *` * @param k2 a `struct DenominationKeyEntry *` * @return -1 if k1 starts before k2, * 1 if k2 starts before k1, * 0 if they start at the same time */ static int denomkey_array_sort_comparator (const void *k1, const void *k2) { const struct DenominationKeyEntry *dke1 = k1; const struct DenominationKeyEntry *dke2 = k2; struct GNUNET_TIME_Absolute d1 = GNUNET_TIME_absolute_ntoh (dke1->dki->issue.properties.start); struct GNUNET_TIME_Absolute d2 = GNUNET_TIME_absolute_ntoh (dke2->dki->issue.properties.start); if (d1.abs_value_us < d2.abs_value_us) return -1; if (d1.abs_value_us > d2.abs_value_us) return 1; return 0; } /** * 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 key_state the key state to use * @param[in,out] response the response to modify * @return #GNUNET_OK on success */ static int setup_general_response_headers (const struct TEH_KS_StateHandle *key_state, struct MHD_Response *response) { char dat[128]; TALER_MHD_add_global_headers (response); GNUNET_break (MHD_YES == MHD_add_response_header (response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json")); get_date_string (key_state->reload_time, dat); GNUNET_break (MHD_YES == MHD_add_response_header (response, MHD_HTTP_HEADER_LAST_MODIFIED, dat)); if (0 != key_state->next_reload.abs_value_us) { struct GNUNET_TIME_Absolute m; m = GNUNET_TIME_relative_to_absolute (TEH_max_keys_caching); m = GNUNET_TIME_absolute_min (m, key_state->next_reload); get_date_string (m, dat); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Setting /keys 'Expires' header to '%s'\n", dat); GNUNET_break (MHD_YES == MHD_add_response_header (response, MHD_HTTP_HEADER_EXPIRES, dat)); } return GNUNET_OK; } /** * Information about an auditor to be added. */ struct AuditorEntry { /** * URL of the auditor (allocated still as part of a * `struct AuditorSignature`, do not free!). */ const char *auditor_url; /** * Public key of the auditor (allocated still as part of a * `struct AuditorSignature`, do not free!). */ const struct TALER_AuditorPublicKeyP *apub; /** * Array of denomination keys and auditor signatures. */ json_t *ar; }; /** * Free auditor entry from the hash map. * * @param cls NULL * @param key unused * @param value a `struct AuditorEntry` to free * @return #GNUNET_OK (to continue to iterate) */ static int free_auditor_entry (void *cls, const struct GNUNET_HashCode *key, void *value) { struct AuditorEntry *ae = value; (void) cls; (void) key; json_decref (ae->ar); GNUNET_free (ae); return GNUNET_OK; } /** * Convert auditor entries from the hash map to entries * in the auditor array, free the auditor entry as well. * * @param cls a `struct ResponseBuilderContext *` * @param key unused * @param value a `struct AuditorEntry` to add to the `auditors_array` * @return #GNUNET_OK (to continue to iterate), #GNUNET_SYSERR to terminate with error */ static int add_auditor_entry (void *cls, const struct GNUNET_HashCode *key, void *value) { struct ResponseBuilderContext *rbc = cls; struct AuditorEntry *ae = value; json_t *ao; (void) key; ao = json_pack ("{s:o, s:s, s:o}", "denomination_keys", ae->ar, "auditor_url", ae->auditor_url, "auditor_pub", GNUNET_JSON_from_data_auto (ae->apub)); if (NULL == ao) { GNUNET_break (0); return GNUNET_SYSERR; } if (0 != json_array_append_new (rbc->auditors_array, ao)) { GNUNET_break (0); return GNUNET_SYSERR; } GNUNET_free (ae); return GNUNET_OK; } /** * Initialize @a krd for the given @a cherry_pick_date using * the key data in @a rfc. This function actually builds the * respective JSON replies (compressed and uncompressed). * * @param rfc factory with key material * @param[out] krd response object to initialize * @param denom_off offset in the @a rfc's `denomkey_array` at which * keys beyond the @a cherry_pick_date are stored * @param cherry_pick_date cut-off date to use * @return #GNUNET_OK on success, #GNUNET_SYSERR on error */ static int build_keys_response (const struct ResponseFactoryContext *rfc, struct KeysResponseData *krd, unsigned int denom_off, struct GNUNET_TIME_Absolute cherry_pick_date) { struct ResponseBuilderContext rbc; json_t *keys; struct TALER_ExchangeKeySetPS ks; struct TALER_ExchangeSignatureP sig; char *keys_json; struct GNUNET_TIME_Relative reserve_closing_delay; void *keys_jsonz; size_t keys_jsonz_size; int comp; krd->cherry_pick_date = cherry_pick_date; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Creating /keys for cherry pick date %s\n", GNUNET_STRINGS_absolute_time_to_string (cherry_pick_date)); /* Initialize `rbc` */ memset (&rbc, 0, sizeof (rbc)); rbc.denom_keys_array = json_array (); if (NULL == rbc.denom_keys_array) { GNUNET_break (0); return GNUNET_SYSERR; } rbc.auditors_array = json_array (); if (NULL == rbc.auditors_array) { destroy_response_builder (&rbc); GNUNET_break (0); return GNUNET_SYSERR; } rbc.hash_context = GNUNET_CRYPTO_hash_context_start (); /* Go over relevant denomination keys. */ { struct GNUNET_CONTAINER_MultiHashMap *auditors; auditors = GNUNET_CONTAINER_multihashmap_create (4, GNUNET_NO); for (unsigned int i = denom_off; idenomkey_array_length; i++) { /* Add denomination key to the response */ const struct DenominationKeyEntry *dke = &rfc->denomkey_array[i]; const struct GNUNET_HashCode *denom_key_hash = &dke->denom_key_hash; GNUNET_CRYPTO_hash_context_read (rbc.hash_context, denom_key_hash, sizeof (struct GNUNET_HashCode)); if (0 != json_array_append_new (rbc.denom_keys_array, denom_key_issue_to_json (&dke->dki->denom_pub, &dke->dki->issue))) { GNUNET_break (0); destroy_response_builder (&rbc); return GNUNET_SYSERR; } /* Add auditor data */ for (const struct AuditorSignature *as = dke->as_head; NULL != as; as = as->next) { struct GNUNET_HashCode ahash; struct AuditorEntry *ae; GNUNET_CRYPTO_hash (&as->apub, sizeof (as->apub), &ahash); ae = GNUNET_CONTAINER_multihashmap_get (auditors, &ahash); if (NULL == ae) { ae = GNUNET_new (struct AuditorEntry); ae->auditor_url = as->auditor_url; ae->ar = json_array (); ae->apub = &as->apub; GNUNET_assert (GNUNET_YES == GNUNET_CONTAINER_multihashmap_put (auditors, &ahash, ae, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); } if (0 != json_array_append_new (ae->ar, json_pack ("{s:o, s:o}", "denom_pub_h", GNUNET_JSON_from_data_auto ( denom_key_hash), "auditor_sig", GNUNET_JSON_from_data_auto ( &as->asig)))) { destroy_response_builder (&rbc); GNUNET_break (0); GNUNET_CONTAINER_multihashmap_iterate (auditors, &free_auditor_entry, NULL); GNUNET_CONTAINER_multihashmap_destroy (auditors); return GNUNET_SYSERR; } } } GNUNET_CONTAINER_multihashmap_iterate (auditors, &add_auditor_entry, &rbc); GNUNET_CONTAINER_multihashmap_destroy (auditors); } /* Sign hash over denomination keys */ ks.purpose.size = htonl (sizeof (ks)); ks.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_KEY_SET); ks.list_issue_date = GNUNET_TIME_absolute_hton (rfc->key_state->reload_time); GNUNET_CRYPTO_hash_context_finish (rbc.hash_context, &ks.hc); rbc.hash_context = NULL; GNUNET_CRYPTO_eddsa_sign ( &rfc->key_state->current_sign_key_issue.signkey_priv.eddsa_priv, &ks, &sig.eddsa_signature); if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (TEH_cfg, "exchangedb", "IDLE_RESERVE_EXPIRATION_TIME", &reserve_closing_delay)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "exchangedb", "IDLE_RESERVE_EXPIRATION_TIME"); /* use default */ reserve_closing_delay = GNUNET_TIME_relative_multiply ( GNUNET_TIME_UNIT_WEEKS, 4); } /* Build /keys response */ keys = json_pack ("{s:s, s:o, s:o, s:O, s:O," " s:o, s:o, s:o, s:o, s:o}", /* 1-5 */ "version", EXCHANGE_PROTOCOL_VERSION, "master_public_key", GNUNET_JSON_from_data_auto ( &TEH_master_public_key), "reserve_closing_delay", GNUNET_JSON_from_time_rel ( reserve_closing_delay), "signkeys", rfc->sign_keys_array, "recoup", rfc->recoup_array, /* 6-10 */ "denoms", rbc.denom_keys_array, "auditors", rbc.auditors_array, "list_issue_date", GNUNET_JSON_from_time_abs ( rfc->key_state->reload_time), "eddsa_pub", GNUNET_JSON_from_data_auto ( &rfc->key_state->current_sign_key_issue.issue.signkey_pub), "eddsa_sig", GNUNET_JSON_from_data_auto (&sig)); if (NULL == keys) { destroy_response_builder (&rbc); GNUNET_break (0); return GNUNET_SYSERR; } rbc.denom_keys_array = NULL; rbc.auditors_array = NULL; destroy_response_builder (&rbc); /* Convert /keys response to UTF8-String */ keys_json = json_dumps (keys, JSON_INDENT (2)); json_decref (keys); if (NULL == keys_json) { GNUNET_break (0); return GNUNET_SYSERR; } /* Keep copy for later compression... */ keys_jsonz = GNUNET_strdup (keys_json); keys_jsonz_size = strlen (keys_json); /* Create uncompressed response */ krd->response_uncompressed = MHD_create_response_from_buffer (keys_jsonz_size, keys_json, MHD_RESPMEM_MUST_FREE); if (NULL == krd->response_uncompressed) { GNUNET_break (0); GNUNET_free (keys_json); GNUNET_free (keys_jsonz); return GNUNET_SYSERR; } if (GNUNET_OK != setup_general_response_headers (rfc->key_state, krd->response_uncompressed)) { GNUNET_break (0); GNUNET_free (keys_jsonz); return GNUNET_SYSERR; } /* Also compute compressed version of /keys response */ comp = TALER_MHD_body_compress (&keys_jsonz, &keys_jsonz_size); krd->response_compressed = MHD_create_response_from_buffer (keys_jsonz_size, keys_jsonz, MHD_RESPMEM_MUST_FREE); if (NULL == krd->response_compressed) { GNUNET_break (0); GNUNET_free (keys_jsonz); return GNUNET_SYSERR; } /* If the response is actually compressed, set the respective header. */ if ( (MHD_YES == comp) && (MHD_YES != MHD_add_response_header (krd->response_compressed, MHD_HTTP_HEADER_CONTENT_ENCODING, "deflate")) ) { GNUNET_break (0); return GNUNET_SYSERR; } if (GNUNET_OK != setup_general_response_headers (rfc->key_state, krd->response_compressed)) { GNUNET_break (0); return GNUNET_SYSERR; } return GNUNET_OK; } /** * Function called with information about the exchange's denomination * keys based on what is known in the database. Used to learn our * public keys (after the private keys are deleted, we still need to * have the public keys around for a while to verify signatures). * * This function checks if the @a denom_pub is already known to us, * and if not adds it to our set. * * @param cls closure, a `struct ResponseFactoryContext *` * @param denom_pub public key of the denomination * @param issue detailed information about the denomination (value, expiration times, fees) */ static void reload_public_denoms_cb ( void *cls, const struct TALER_DenominationPublicKey *denom_pub, const struct TALER_EXCHANGEDB_DenominationKeyInformationP *issue) { struct ResponseFactoryContext *rfc = cls; struct TALER_EXCHANGEDB_DenominationKey dki; int ret; if (rfc->now.abs_value_us > GNUNET_TIME_absolute_ntoh (issue->properties.expire_legal).abs_value_us) { /* Expired key, discard. */ return; } if (NULL != GNUNET_CONTAINER_multihashmap_get (rfc->key_state->denomkey_map, &issue->properties.denom_hash)) return; /* exists / known */ if (NULL != GNUNET_CONTAINER_multihashmap_get (rfc->key_state->revoked_map, &issue->properties.denom_hash)) return; /* exists / known */ /* zero-out, just for future-proofing */ memset (&dki, 0, sizeof (dki)); dki.denom_priv.rsa_private_key = NULL; /* not available! */ dki.denom_pub.rsa_public_key = denom_pub->rsa_public_key; dki.issue = *issue; ret = store_in_map (rfc->key_state->denomkey_map, &dki /* makes a deep copy of dki */); if (GNUNET_SYSERR == ret) { GNUNET_break (0); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Signature wrong on denomination key `%s' (skipping)!\n", GNUNET_h2s (&issue->properties.denom_hash)); return; } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Loaded denomination key %s from DB, no private key (hopefully revoked!)\n", GNUNET_h2s (&issue->properties.denom_hash)); /* we can assert here as we checked for duplicates just above */ GNUNET_assert (GNUNET_OK == ret); } /** * Actual "main" logic that builds the state which this module * evolves around. This function will import the key data from * the exchangedb module and convert it into (1) internally used * lookup tables, and (2) HTTP responses to be returned from * /keys. * * State returned is to be freed with #ks_free() -- but only * once the reference counter has again hit zero. * * @return NULL on error (usually pretty fatal...) */ static struct TEH_KS_StateHandle * make_fresh_key_state (struct GNUNET_TIME_Absolute now) { struct TEH_KS_StateHandle *key_state; struct ResponseFactoryContext rfc; struct GNUNET_TIME_Absolute last; unsigned int off; enum GNUNET_DB_QueryStatus qs; memset (&rfc, 0, sizeof (rfc)); rfc.recoup_array = json_array (); if (NULL == rfc.recoup_array) { GNUNET_break (0); return NULL; } rfc.sign_keys_array = json_array (); if (NULL == rfc.sign_keys_array) { GNUNET_break (0); json_decref (rfc.recoup_array); return NULL; } key_state = GNUNET_new (struct TEH_KS_StateHandle); rfc.key_state = key_state; rfc.now = now; key_state->min_dk_expire = GNUNET_TIME_UNIT_FOREVER_ABS; key_state->denomkey_map = GNUNET_CONTAINER_multihashmap_create (32, GNUNET_NO); key_state->revoked_map = GNUNET_CONTAINER_multihashmap_create (4, GNUNET_NO); key_state->reload_time = GNUNET_TIME_absolute_get (); GNUNET_TIME_round_abs (&key_state->reload_time); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Loading keys from `%s'\n", TEH_exchange_directory); /* Initialize the 'denomkey_map' and the 'revoked_map' and 'rfc.recoup_array' */ if (-1 == TALER_EXCHANGEDB_denomination_keys_iterate (TEH_exchange_directory, &reload_keys_denom_iter, &rfc)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to load denomination keys from `%s'.\n", TEH_exchange_directory); ks_free (key_state); json_decref (rfc.recoup_array); json_decref (rfc.sign_keys_array); return NULL; } /* We do not get expired DKIs from TALER_EXCHANGEDB_denomination_keys_iterate(), so we must fetch the old keys (where we only have the public keys) from the database! */ qs = TEH_plugin->iterate_denomination_info (TEH_plugin->cls, &reload_public_denoms_cb, &rfc); GNUNET_break (0 <= qs); /* warn, but continue, fingers crossed */ /* process revocations */ if (-1 == TALER_EXCHANGEDB_revocations_iterate (TEH_revocation_directory, &TEH_master_public_key, &revocations_iter, &rfc)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to load denomination keys from `%s'.\n", TEH_exchange_directory); ks_free (key_state); json_decref (rfc.recoup_array); json_decref (rfc.sign_keys_array); return NULL; } /* Initialize `current_sign_key_issue` and `rfc.sign_keys_array` */ TALER_EXCHANGEDB_signing_keys_iterate (TEH_exchange_directory, &reload_keys_sign_iter, &rfc); if (0 != memcmp (&key_state->current_sign_key_issue.issue.master_public_key, &TEH_master_public_key, sizeof (struct TALER_MasterPublicKeyP))) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Have no signing key. Bad configuration.\n"); ks_free (key_state); destroy_response_factory (&rfc); return NULL; } /* sanity check */ if (0 == GNUNET_CONTAINER_multihashmap_size (key_state->denomkey_map)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Have no denomination keys. Bad configuration.\n"); ks_free (key_state); destroy_response_factory (&rfc); return NULL; } /* Initialize and sort the `denomkey_array` */ rfc.denomkey_array = GNUNET_new_array (GNUNET_CONTAINER_multihashmap_size ( key_state->denomkey_map), struct DenominationKeyEntry); GNUNET_CONTAINER_multihashmap_iterate (key_state->denomkey_map, &initialize_denomkey_array, &rfc); GNUNET_assert (rfc.denomkey_array_length == GNUNET_CONTAINER_multihashmap_size (key_state->denomkey_map)); qsort (rfc.denomkey_array, rfc.denomkey_array_length, sizeof (struct DenominationKeyEntry), &denomkey_array_sort_comparator); /* Complete `denomkey_array` by adding auditor signature data */ TALER_EXCHANGEDB_auditor_iterate (TEH_cfg, &reload_auditor_iter, &rfc); /* Sanity check: do we have auditors for all denomination keys? */ for (unsigned int i = 0; ias_head) GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Denomination key `%s' at %p not signed by any auditor!\n", GNUNET_h2s (&dke->denom_key_hash), dke); } /* Determine size of `krd_array` by counting number of discrete denomination key starting times. */ last = GNUNET_TIME_UNIT_ZERO_ABS; key_state->krd_array_length = 0; off = 1; /* reserve one slot for the "no keys" response */ for (unsigned int i = 0; idki->issue.properties.start); if (last.abs_value_us == d.abs_value_us) continue; /* must be monotonically increasing as per qsort() call above: */ GNUNET_assert (last.abs_value_us < d.abs_value_us); last = d; off++; } /* Compute next automatic reload time */ key_state->next_reload = GNUNET_TIME_absolute_min (GNUNET_TIME_absolute_ntoh ( key_state->current_sign_key_issue.issue.expire), key_state->min_dk_expire); GNUNET_assert (0 != key_state->next_reload.abs_value_us); /* Initialize `krd_array` */ key_state->krd_array_length = off; key_state->krd_array = GNUNET_new_array (key_state->krd_array_length, struct KeysResponseData); off = 0; last = GNUNET_TIME_UNIT_ZERO_ABS; for (unsigned int i = 0; idki->issue.properties.start); if (last.abs_value_us == d.abs_value_us) continue; if (GNUNET_OK != build_keys_response (&rfc, &key_state->krd_array[off++], i, last)) { /* Fail hard, will be caught via test on `off` below */ GNUNET_break (0); off = key_state->krd_array_length; /* flag as 'invalid' */ break; } last = d; } /* Finally, build an `empty` response without denomination keys for requests past the last known denomination key start date */ if ( (off + 1 < key_state->krd_array_length) || (GNUNET_OK != build_keys_response (&rfc, &key_state->krd_array[off++], rfc.denomkey_array_length, last)) ) { GNUNET_break (0); ks_free (key_state); destroy_response_factory (&rfc); return NULL; } /* Clean up intermediary state we don't need anymore and return new key_state! */ destroy_response_factory (&rfc); return key_state; } /* ************************** Persistent part ********************** */ /** * Release key state, free if necessary (if reference count gets to zero). * * @param location name of the function in which the lock is acquired * @param key_state the key state to release */ void TEH_KS_release_ (const char *location, struct TEH_KS_StateHandle *key_state) { int do_free; GNUNET_assert (0 == pthread_mutex_lock (&internal_key_state_mutex)); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "KS released at %s (%p/%d)\n", location, key_state, key_state->refcnt); GNUNET_assert (0 < key_state->refcnt); key_state->refcnt--; do_free = (0 == key_state->refcnt); GNUNET_assert ( (! do_free) || (key_state != internal_key_state) ); GNUNET_assert (0 == pthread_mutex_unlock (&internal_key_state_mutex)); if (do_free) ks_free (key_state); } /** * Acquire the key state of the exchange. Updates keys if necessary. * For every call to #TEH_KS_acquire(), a matching call * to #TEH_KS_release() must be made. * * @param now for what timestamp should we acquire the key state * @param location name of the function in which the lock is acquired * @return the key state, NULL on error (usually pretty fatal) */ struct TEH_KS_StateHandle * TEH_KS_acquire_ (struct GNUNET_TIME_Absolute now, const char *location) { struct TEH_KS_StateHandle *key_state; struct TEH_KS_StateHandle *os; os = NULL; GNUNET_assert (0 == pthread_mutex_lock (&internal_key_state_mutex)); /* If the current internal key state is missing (failed to load one on startup?) or expired, we try to setup a fresh one even without having gotten SIGUSR1 */ if ( ( (NULL != internal_key_state) && (internal_key_state->next_reload.abs_value_us <= now.abs_value_us) ) || (NULL == internal_key_state) ) { os = internal_key_state; internal_key_state = make_fresh_key_state (now); if (NULL != internal_key_state) internal_key_state->refcnt = 1; /* alias from #internal_key_state */ if (NULL != os) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "KS released in acquire due to expiration\n"); GNUNET_assert (0 < os->refcnt); os->refcnt--; /* #internal_key_state alias dropped */ if (0 != os->refcnt) os = NULL; /* do NOT release yet, otherwise release after unlocking */ } } if (NULL == internal_key_state) { /* We tried and failed to setup #internal_key_state */ GNUNET_assert (0 == pthread_mutex_unlock (&internal_key_state_mutex)); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to initialize key state\n"); if (NULL != os) ks_free (os); return NULL; } key_state = internal_key_state; key_state->refcnt++; /* returning an alias, increment RC */ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "KS acquired at %s (%p/%d)\n", location, key_state, key_state->refcnt); GNUNET_assert (0 == pthread_mutex_unlock (&internal_key_state_mutex)); if (NULL != os) ks_free (os); return key_state; } /** * Look up the issue for a denom public key. Note that the result * is only valid while the @a key_state is not released! * * @param key_state state to look in * @param denom_pub_hash hash of denomination public key * @param use purpose for which the key is being located * @param[out] ec set to the error code, in case the operation failed * @param[out] hc set to the HTTP status code to use * @return the denomination key issue, * or NULL if denom_pub could not be found (or is not valid at this time for the given @a use) */ struct TALER_EXCHANGEDB_DenominationKey * TEH_KS_denomination_key_lookup_by_hash ( const struct TEH_KS_StateHandle *key_state, const struct GNUNET_HashCode *denom_pub_hash, enum TEH_KS_DenominationKeyUse use, enum TALER_ErrorCode *ec, unsigned int *hc) { struct TALER_EXCHANGEDB_DenominationKey *dki; struct GNUNET_TIME_Absolute now; const struct GNUNET_CONTAINER_MultiHashMap *map; map = (TEH_KS_DKU_RECOUP == use) ? key_state->revoked_map : key_state->denomkey_map; dki = GNUNET_CONTAINER_multihashmap_get (map, denom_pub_hash); if ( (NULL == dki) && (TEH_KS_DKU_ZOMBIE == use) ) dki = GNUNET_CONTAINER_multihashmap_get (key_state->revoked_map, denom_pub_hash); if (NULL == dki) { *hc = MHD_HTTP_NOT_FOUND; *ec = TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN; return NULL; } now = GNUNET_TIME_absolute_get (); if (now.abs_value_us < GNUNET_TIME_absolute_ntoh (dki->issue.properties.start).abs_value_us) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Not returning DKI for %s, as start time is in the future\n", GNUNET_h2s (denom_pub_hash)); *hc = MHD_HTTP_PRECONDITION_FAILED; *ec = TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE; return NULL; } now = GNUNET_TIME_absolute_get (); switch (use) { case TEH_KS_DKU_WITHDRAW: if (now.abs_value_us > GNUNET_TIME_absolute_ntoh ( dki->issue.properties.expire_withdraw).abs_value_us) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Not returning DKI for %s, as time to create coins has passed\n", GNUNET_h2s (denom_pub_hash)); *ec = TALER_EC_EXCHANGE_WITHDRAW_VALIDITY_IN_PAST; *hc = MHD_HTTP_GONE; return NULL; } if (NULL == dki->denom_priv.rsa_private_key) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Not returning DKI of %s for WITHDRAW operation as we lack the private key, even though the withdraw period did not yet expire!\n", GNUNET_h2s (denom_pub_hash)); *ec = TALER_EC_EXCHANGE_WITHDRAW_DENOMINATION_KEY_LOST; *hc = MHD_HTTP_SERVICE_UNAVAILABLE; return NULL; } break; case TEH_KS_DKU_DEPOSIT: if (now.abs_value_us > GNUNET_TIME_absolute_ntoh ( dki->issue.properties.expire_deposit).abs_value_us) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Not returning DKI for %s, as time to spend coin has passed\n", GNUNET_h2s (denom_pub_hash)); *ec = TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED; *hc = MHD_HTTP_GONE; return NULL; } break; case TEH_KS_DKU_RECOUP: if (now.abs_value_us > GNUNET_TIME_absolute_ntoh ( dki->issue.properties.expire_deposit).abs_value_us) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Not returning DKI for %s, as time to recoup coin has passed\n", GNUNET_h2s (denom_pub_hash)); *ec = TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED; *hc = MHD_HTTP_GONE; return NULL; } break; case TEH_KS_DKU_ZOMBIE: if (now.abs_value_us > GNUNET_TIME_absolute_ntoh ( dki->issue.properties.expire_legal).abs_value_us) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Not returning DKI for %s, as legal expiration of coin has passed\n", GNUNET_h2s (denom_pub_hash)); *ec = TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED; *hc = MHD_HTTP_GONE; return NULL; } break; } return dki; } /** * Call #handle_signal() to pass the received signal via * the control pipe. */ static void handle_sigusr1 (void) { handle_signal (SIGUSR1); } /** * Call #handle_signal() to pass the received signal via * the control pipe. */ static void handle_sigint (void) { handle_signal (SIGINT); } /** * Call #handle_signal() to pass the received signal via * the control pipe. */ static void handle_sigterm (void) { handle_signal (SIGTERM); } /** * Call #handle_signal() to pass the received signal via * the control pipe. */ static void handle_sighup (void) { handle_signal (SIGHUP); } /** * Call #handle_signal() to pass the received signal via * the control pipe. */ static void handle_sigchld (void) { handle_signal (SIGCHLD); } /** * Read signals from a pipe in a loop, and reload keys from disk if * SIGUSR1 is received, terminate if SIGTERM/SIGINT is received, and * restart if SIGHUP is received. * * @return #GNUNET_SYSERR on errors, * #GNUNET_OK to terminate normally * #GNUNET_NO to restart an update version of the binary */ int TEH_KS_loop (void) { int ret; ret = 2; while (2 == ret) { char c; ssize_t res; errno = 0; res = read (reload_pipe[0], &c, 1); if ((res < 0) && (EINTR != errno)) { GNUNET_break (0); ret = GNUNET_SYSERR; break; } if (EINTR == errno) continue; switch (c) { case SIGUSR1: { struct TEH_KS_StateHandle *fs; struct TEH_KS_StateHandle *os; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "(re-)loading keys\n"); /* Create fresh key state before critical region */ fs = make_fresh_key_state (GNUNET_TIME_absolute_get ()); if (NULL == fs) { /* Ok, that went badly, terminate process */ ret = GNUNET_SYSERR; break; } fs->refcnt = 1; /* we'll alias from #internal_key_state soon */ /* swap active key state in critical region */ GNUNET_assert (0 == pthread_mutex_lock (&internal_key_state_mutex)); os = internal_key_state; internal_key_state = fs; if (NULL != os) { GNUNET_assert (0 < os->refcnt); os->refcnt--; /* removed #internal_key_state reference */ if (0 != os->refcnt) os = NULL; /* other aliases are still active, do not yet free */ } GNUNET_assert (0 == pthread_mutex_unlock (&internal_key_state_mutex)); if (NULL != os) ks_free (os); /* RC did hit zero, free */ } break; case SIGTERM: case SIGINT: /* terminate */ ret = GNUNET_OK; break; case SIGHUP: /* restart updated binary */ ret = GNUNET_NO; break; #if HAVE_DEVELOPER case SIGCHLD: /* running in test-mode, test finished, terminate */ ret = GNUNET_OK; break; #endif default: /* unexpected character */ GNUNET_break (0); break; } } return ret; } static struct GNUNET_SIGNAL_Context *sigusr1; static struct GNUNET_SIGNAL_Context *sigterm; static struct GNUNET_SIGNAL_Context *sigint; static struct GNUNET_SIGNAL_Context *sighup; static struct GNUNET_SIGNAL_Context *sigchld; /** * Setup initial #internal_key_state and our signal handlers. */ int TEH_KS_init (void) { if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (TEH_cfg, "exchange", "LOOKAHEAD_PROVIDE", &conf_duration_provide)) { GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, "exchange", "LOOKAHEAD_PROVIDE", "time value required"); return GNUNET_SYSERR; } if (0 == conf_duration_provide.rel_value_us) { GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, "exchange", "LOOKAHEAD_PROVIDE", "value cannot be zero"); return GNUNET_SYSERR; } if (0 != pipe (reload_pipe)) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "pipe"); return GNUNET_SYSERR; } sigusr1 = GNUNET_SIGNAL_handler_install (SIGUSR1, &handle_sigusr1); sigterm = GNUNET_SIGNAL_handler_install (SIGTERM, &handle_sigterm); sigint = GNUNET_SIGNAL_handler_install (SIGINT, &handle_sigint); sighup = GNUNET_SIGNAL_handler_install (SIGHUP, &handle_sighup); sigchld = GNUNET_SIGNAL_handler_install (SIGCHLD, &handle_sigchld); /* no need to lock here, as we are still single-threaded */ internal_key_state = make_fresh_key_state (GNUNET_TIME_absolute_get ()); if (NULL == internal_key_state) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to setup initial key state. This exchange cannot work.\n"); return GNUNET_SYSERR; } internal_key_state->refcnt = 1; return GNUNET_OK; } /** * Finally release #internal_key_state and our signal handlers. */ void TEH_KS_free (void) { struct TEH_KS_StateHandle *ks; /* Note: locking is no longer be required, as we are again single-threaded. */ ks = internal_key_state; if (NULL != ks) { GNUNET_assert (1 == ks->refcnt); ks->refcnt--; ks_free (ks); } if (NULL != sigusr1) { GNUNET_SIGNAL_handler_uninstall (sigusr1); sigusr1 = NULL; } if (NULL != sigterm) { GNUNET_SIGNAL_handler_uninstall (sigterm); sigterm = NULL; } if (NULL != sigint) { GNUNET_SIGNAL_handler_uninstall (sigint); sigint = NULL; } if (NULL != sighup) { GNUNET_SIGNAL_handler_uninstall (sighup); sighup = NULL; } if (NULL != sigchld) { GNUNET_SIGNAL_handler_uninstall (sigchld); sigchld = NULL; } if (-1 != reload_pipe[0]) { GNUNET_break (0 == close (reload_pipe[0])); GNUNET_break (0 == close (reload_pipe[1])); reload_pipe[0] = reload_pipe[1] = -1; } } /** * Sign the message in @a purpose with the exchange's signing key. * * The @a purpose data is the beginning of the data of which the signature is * to be created. The `size` field in @a purpose must correctly indicate the * number of bytes of the data structure, including its header. Use * #TEH_KS_sign() instead of calling this function directly! * * @param purpose the message to sign * @param[out] pub set to the current public signing key of the exchange * @param[out] sig signature over purpose using current signing key * @return #GNUNET_OK on success, #GNUNET_SYSERR if we lack key material */ int TEH_KS_sign_ (const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose, struct TALER_ExchangePublicKeyP *pub, struct TALER_ExchangeSignatureP *sig) { struct TEH_KS_StateHandle *key_state; key_state = TEH_KS_acquire (GNUNET_TIME_absolute_get ()); if (NULL == key_state) { /* This *can* happen if the exchange's keys are not properly maintained (i.e. administrator forgets to provision us with non-expired signing keys or to send signal to reload keys after provisioning). */ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Cannot sign request, no valid signing keys available. Were they properly provisioned and did you signal the exchange to reload the keys?\n"); return GNUNET_SYSERR; } *pub = key_state->current_sign_key_issue.issue.signkey_pub; GNUNET_assert (GNUNET_OK == GNUNET_CRYPTO_eddsa_sign_ ( &key_state->current_sign_key_issue.signkey_priv.eddsa_priv, purpose, &sig->eddsa_signature)); TEH_KS_release (key_state); return GNUNET_OK; } /** * 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 value pointer to a `struct KeysResponseData` array entry * @return 0 if time matches, -1 if key is smaller, 1 if key is larger */ static int krd_search_comparator (const void *key, const void *value) { const struct GNUNET_TIME_Absolute *kd = key; const struct KeysResponseData *krd = value; if (kd->abs_value_us > krd->cherry_pick_date.abs_value_us) return 1; if (kd->abs_value_us < krd->cherry_pick_date.abs_value_us) return -1; return 0; } /** * Function to call to handle the request by sending * back static data from the @a rh. * * @param rh context of the handler * @param connection the MHD connection to handle * @param args array of additional options (must be empty for this function) * @return MHD result code */ MHD_RESULT TEH_handler_keys (const struct TEH_RequestHandler *rh, struct MHD_Connection *connection, const char *const args[]) { MHD_RESULT ret; const char *have_cherrypick; const char *have_fakenow; struct GNUNET_TIME_Absolute last_issue_date; struct GNUNET_TIME_Absolute now; const struct KeysResponseData *krd; (void) rh; (void) args; have_cherrypick = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "last_issue_date"); if (NULL != have_cherrypick) { unsigned long long cherrypickn; if (1 != sscanf (have_cherrypick, "%llu", &cherrypickn)) { GNUNET_break_op (0); return TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PARAMETER_MALFORMED, have_cherrypick); } /* The following multiplication may overflow; but this should not really 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.abs_value_us = (uint64_t) cherrypickn * 1000000LLU; } else { last_issue_date.abs_value_us = 0LLU; } now = GNUNET_TIME_absolute_get (); have_fakenow = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "now"); if (NULL != have_fakenow) { unsigned long long fakenown; if (1 != sscanf (have_fakenow, "%llu", &fakenown)) { GNUNET_break_op (0); return TALER_MHD_reply_with_error (connection, MHD_HTTP_FORBIDDEN, TALER_EC_GENERIC_PARAMETER_MALFORMED, have_fakenow); } if (TEH_allow_keys_timetravel) { /* The following multiplication may overflow; but this should not really 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 */ now.abs_value_us = (uint64_t) fakenown * 1000000LLU; } else { /* Option not allowed by configuration */ return TALER_MHD_reply_with_error (connection, MHD_HTTP_FORBIDDEN, TALER_EC_EXCHANGE_KEYS_TIMETRAVEL_FORBIDDEN, NULL); } } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Handling request for /keys (%s/%s)\n", have_cherrypick, have_fakenow); { struct TEH_KS_StateHandle *key_state; key_state = TEH_KS_acquire (now); if (NULL == key_state) { /* Maybe client picked time stamp too far in the future? In that case, #MHD_HTTP_INTERNAL_SERVER_ERROR might be misleading, could be more like a NOT_FOUND situation. But, OTOH, for 'sane' clients it is more likely to be our fault, so let's speculatively assume we are to blame ;-) */ return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION, NULL); } krd = bsearch (&last_issue_date, key_state->krd_array, key_state->krd_array_length, sizeof (struct KeysResponseData), &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), (unsigned int) (krd - key_state->krd_array), key_state->krd_array_length); if ( (NULL == krd) && (key_state->krd_array_length > 0) ) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Client provided invalid cherry picking timestamp %s, returning full response\n", GNUNET_STRINGS_absolute_time_to_string (last_issue_date)); krd = &key_state->krd_array[0]; } if (NULL == krd) { /* Maybe client picked time stamp too far in the future? In that case, "INTERNAL_SERVER_ERROR" might be misleading, could be more like a NOT_FOUND situation. But, OTOH, for 'sane' clients it is more likely to be our fault, so let's speculatively assume we are to blame ;-) */// GNUNET_break (0); TEH_KS_release (key_state); return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, NULL); } ret = MHD_queue_response (connection, MHD_HTTP_OK, (MHD_YES == TALER_MHD_can_compress (connection)) ? krd->response_compressed : krd->response_uncompressed); TEH_KS_release (key_state); } return ret; } /* end of taler-exchange-httpd_keystate.c */