diff --git a/ChangeLog b/ChangeLog index 7c64dae55..d1836d955 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +Sat 14 Nov 2020 05:47:30 PM CET + Modify taler-exchange-transfer to continue even after a + wire transfer failed due to the bank refusing it because + the target account does not exist. Changed the database + to track such failures in the respective table. + Opens new issue #6647. -CG + Tue 10 Nov 2020 01:03:22 PM CET Updates to error codes and HTTP status codes for improved consistency. Fixed spelling issues. Ensure main() returns diff --git a/src/exchange/taler-exchange-transfer.c b/src/exchange/taler-exchange-transfer.c index d21f9ccf1..9c350220e 100644 --- a/src/exchange/taler-exchange-transfer.c +++ b/src/exchange/taler-exchange-transfer.c @@ -264,8 +264,26 @@ wire_confirm_cb (void *cls, (void) row_id; (void) wire_timestamp; wpd->eh = NULL; - if (MHD_HTTP_OK != http_status_code) + switch (http_status_code) { + case MHD_HTTP_OK: + qs = db_plugin->wire_prepare_data_mark_finished (db_plugin->cls, + session, + wpd->row_id); + /* continued below */ + break; + case MHD_HTTP_NOT_FOUND: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Wire transaction %llu failed: %u/%d\n", + (unsigned long long) wpd->row_id, + http_status_code, + ec); + qs = db_plugin->wire_prepare_data_mark_failed (db_plugin->cls, + session, + wpd->row_id); + /* continued below */ + break; + default: GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Wire transaction failed: %u/%d\n", http_status_code, @@ -278,9 +296,6 @@ wire_confirm_cb (void *cls, wpd = NULL; return; } - qs = db_plugin->wire_prepare_data_mark_finished (db_plugin->cls, - session, - wpd->row_id); if (0 >= qs) { GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); diff --git a/src/exchangedb/Makefile.am b/src/exchangedb/Makefile.am index 74512636b..6736ce379 100644 --- a/src/exchangedb/Makefile.am +++ b/src/exchangedb/Makefile.am @@ -17,7 +17,9 @@ sqldir = $(prefix)/share/taler/sql/exchange/ sql_DATA = \ exchange-0000.sql \ exchange-0001.sql \ - drop0001.sql + exchange-0002.sql \ + drop0001.sql \ + drop0002.sql EXTRA_DIST = \ exchangedb.conf \ diff --git a/src/exchangedb/drop0002.sql b/src/exchangedb/drop0002.sql new file mode 100644 index 000000000..224c7f50c --- /dev/null +++ b/src/exchangedb/drop0002.sql @@ -0,0 +1,26 @@ +-- +-- This file is part of TALER +-- Copyright (C) 2020 Taler Systems SA +-- +-- TALER is free software; you can redistribute it and/or modify it under the +-- terms of the GNU General Public License as published by the Free Software +-- Foundation; either version 3, or (at your option) any later version. +-- +-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY +-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +-- A PARTICULAR PURPOSE. See the GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License along with +-- TALER; see the file COPYING. If not, see +-- + +-- Everything in one big transaction +BEGIN; + +-- exchange-0002 did not create new tables, so nothing to do here. + +-- Unregister patch (0002.sql) +SELECT _v.unregister_patch('exchange-0002'); + +-- And we're out of here... +COMMIT; diff --git a/src/exchangedb/exchange-0002.sql b/src/exchangedb/exchange-0002.sql new file mode 100644 index 000000000..9a2793f1a --- /dev/null +++ b/src/exchangedb/exchange-0002.sql @@ -0,0 +1,48 @@ +-- +-- This file is part of TALER +-- Copyright (C) 2020 Taler Systems SA +-- +-- TALER is free software; you can redistribute it and/or modify it under the +-- terms of the GNU General Public License as published by the Free Software +-- Foundation; either version 3, or (at your option) any later version. +-- +-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY +-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +-- A PARTICULAR PURPOSE. See the GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License along with +-- TALER; see the file COPYING. If not, see +-- + +-- Everything in one big transaction +BEGIN; + +-- Check patch versioning is in place. +SELECT _v.register_patch('exchange-0002', NULL, NULL); + +ALTER TABLE prewire + ADD failed BOOLEAN NOT NULL DEFAULT false; + +COMMENT ON COLUMN prewire.failed + IS 'set to TRUE if the bank responded with a non-transient failure to our transfer request'; +COMMENT ON COLUMN prewire.finished + IS 'set to TRUE once bank confirmed receiving the wire transfer request'; +COMMENT ON COLUMN prewire.buf + IS 'serialized data to send to the bank to execute the wire transfer'; + +-- change comment, existing index is still useful, but only for gc_prewire. +COMMENT ON INDEX prepare_iteration_index + IS 'for gc_prewire'; + +-- need a new index for updated wire_prepare_data_get statement: +CREATE INDEX IF NOT EXISTS prepare_get_index + ON prewire + (failed,finished); +COMMENT ON INDEX prepare_get_index + IS 'for wire_prepare_data_get'; + + + + +-- Complete transaction +COMMIT; diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index 47b741390..f94dd7395 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -1098,7 +1098,13 @@ postgres_get_session (void *cls) /* Used in #postgres_wire_prepare_data_mark_finished() */ GNUNET_PQ_make_prepare ("wire_prepare_data_mark_done", "UPDATE prewire" - " SET finished=true" + " SET finished=TRUE" + " WHERE prewire_uuid=$1;", + 1), + /* Used in #postgres_wire_prepare_data_mark_failed() */ + GNUNET_PQ_make_prepare ("wire_prepare_data_mark_failed", + "UPDATE prewire" + " SET failed=TRUE" " WHERE prewire_uuid=$1;", 1), /* Used in #postgres_wire_prepare_data_get() */ @@ -1108,11 +1114,11 @@ postgres_get_session (void *cls) ",type" ",buf" " FROM prewire" - " WHERE finished=false" + " WHERE finished=FALSE" + " AND failed=FALSE" " ORDER BY prewire_uuid ASC" " LIMIT 1;", 0), - /* Used in #postgres_select_deposits_missing_wire */ GNUNET_PQ_make_prepare ("deposits_get_overdue", "SELECT" @@ -5224,10 +5230,10 @@ postgres_wire_prepare_data_insert (void *cls, * @return transaction status code */ static enum GNUNET_DB_QueryStatus -postgres_wire_prepare_data_mark_finished (void *cls, - struct TALER_EXCHANGEDB_Session * - session, - uint64_t rowid) +postgres_wire_prepare_data_mark_finished ( + void *cls, + struct TALER_EXCHANGEDB_Session *session, + uint64_t rowid) { struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_uint64 (&rowid), @@ -5241,6 +5247,32 @@ postgres_wire_prepare_data_mark_finished (void *cls, } +/** + * Function called to mark wire transfer commit data as failed. + * + * @param cls closure + * @param session database connection + * @param rowid which entry to mark as failed + * @return transaction status code + */ +static enum GNUNET_DB_QueryStatus +postgres_wire_prepare_data_mark_failed ( + void *cls, + struct TALER_EXCHANGEDB_Session *session, + uint64_t rowid) +{ + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_uint64 (&rowid), + GNUNET_PQ_query_param_end + }; + + (void) cls; + return GNUNET_PQ_eval_prepared_non_select (session->conn, + "wire_prepare_data_mark_failed", + params); +} + + /** * Function called to get an unfinished wire transfer * preparation data. Fetches at most one item. @@ -7379,6 +7411,8 @@ libtaler_plugin_exchangedb_postgres_init (void *cls) plugin->wire_prepare_data_insert = &postgres_wire_prepare_data_insert; plugin->wire_prepare_data_mark_finished = &postgres_wire_prepare_data_mark_finished; + plugin->wire_prepare_data_mark_failed = + &postgres_wire_prepare_data_mark_failed; plugin->wire_prepare_data_get = &postgres_wire_prepare_data_get; plugin->start_deferred_wire_out = &postgres_start_deferred_wire_out; plugin->store_wire_transfer_out = &postgres_store_wire_transfer_out; diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index 4f27daefb..e2abb8a6a 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -2429,6 +2429,20 @@ struct TALER_EXCHANGEDB_Plugin uint64_t rowid); + /** + * Function called to mark wire transfer as failed. + * + * @param cls closure + * @param session database connection + * @param rowid which entry to mark as failed + * @return transaction status code + */ + enum GNUNET_DB_QueryStatus + (*wire_prepare_data_mark_failed)(void *cls, + struct TALER_EXCHANGEDB_Session *session, + uint64_t rowid); + + /** * Function called to get an unfinished wire transfer * preparation data. Fetches at most one item. diff --git a/src/util/taler-helper-crypto-rsa.c b/src/util/taler-helper-crypto-rsa.c index 3aadd0566..33d2ee723 100644 --- a/src/util/taler-helper-crypto-rsa.c +++ b/src/util/taler-helper-crypto-rsa.c @@ -17,9 +17,33 @@ * @file util/taler-helper-crypto-rsa.c * @brief Standalone process to perform private key RSA operations * @author Christian Grothoff + * + * NOTES: + * - Option 'DURATION_OVERLAP' renamed to 'OVERLAP_DURATION' for consistency; + * => need to update in deployment scripts and default configuration! + * - option 'KEYDIR' moved from section 'exchange' to 'taler-helper-crypto-rsa'! + * + * Key design points: + * - EVERY thread of the exchange will have its own pair of connections to the + * crypto helpers. This way, every threat will also have its own /keys state + * and avoid the need to synchronize on those. + * - auditor signatures and master signatures are to be kept in the exchange DB, + * and merged with the public keys of the helper by the exchange HTTPD! + * - the main loop of the helper is SINGLE-THREADED, but there are + * threads for crypto-workers which (only) do the signing in parallel, + * working of a work-queue. + * - thread-safety: signing happens in parallel, thus when REMOVING private keys, + * we must ensure that all signers are done before we fully free() the + * private key. This is done by reference counting (as work is always + * assigned and collected by the main thread). + * + * TODO: + * - actual networking + * - actual signing */ #include "platform.h" #include "taler_util.h" +#include "taler-helper-crypto-rsa.h" #include @@ -36,12 +60,12 @@ struct DenominationKey { /** - * Kept in a DLL of the respective denomination. + * Kept in a DLL of the respective denomination. Sorted by anchor time. */ struct DenominationKey *next; /** - * Kept in a DLL of the respective denomination. + * Kept in a DLL of the respective denomination. Sorted by anchor time. */ struct DenominationKey *prev; @@ -51,18 +75,44 @@ struct DenominationKey struct Denomination *denom; /** - * Denomination key details. Note that the "dki.issue.signature" - * IS ALWAYS uninitialized (all zeros). The private key is in - * 'dki.denom_priv.rsa_private_key' and must be free'd explicitly - * (as it is a pointer to a variable-size data structure). + * Name of the file this key is stored under. */ - struct TALER_EXCHANGEDB_DenominationKey dki; + char *filename; + + /** + * The private key of the denomination. Will be NULL if the private + * key is not available (this is the case after the key has expired + * for signing coins, but is still valid for depositing coins). + */ + struct TALER_DenominationPrivateKey denom_priv; + + /** + * The public key of the denomination. + */ + struct TALER_DenominationPublicKey denom_pub; + + /** + * Hash of this denomination's public key. + */ + struct GNUNET_HashCode h_pub; /** * Time at which this key is supposed to become valid. */ struct GNUNET_TIME_Absolute anchor; + /** + * Reference counter. Counts the number of threads that are + * using this key at this time. + */ + unsigned int rc; + + /** + * Flag set to true if this key has been purged and the memory + * must be freed as soon as @e rc hits zero. + */ + bool purge; + }; @@ -70,12 +120,12 @@ struct Denomination { /** - * Kept in a DLL. Sorted? + * Kept in a DLL. Sorted by #denomination_action_time(). */ struct Denomination *next; /** - * Kept in a DLL. Sorted? + * Kept in a DLL. Sorted by #denomination_action_time(). */ struct Denomination *prev; @@ -89,18 +139,6 @@ struct Denomination */ struct DenominationKey *keys_tail; - /** - * How long are the signatures legally valid? Should be - * significantly larger than @e duration_spend (i.e. years). - */ - struct GNUNET_TIME_Relative duration_legal; - - /** - * How long can the coins be spend? Should be significantly - * larger than @e duration_withdraw (i.e. years). - */ - struct GNUNET_TIME_Relative duration_spend; - /** * How long can coins be withdrawn (generated)? Should be small * enough to limit how many coins will be signed into existence with @@ -110,29 +148,10 @@ struct Denomination struct GNUNET_TIME_Relative duration_withdraw; /** - * What is the value of each coin? + * What is the configuration section of this denomination type? Also used + * for the directory name where the denomination keys are stored. */ - struct TALER_Amount value; - - /** - * What is the fee charged for withdrawal? - */ - struct TALER_Amount fee_withdraw; - - /** - * What is the fee charged for deposits? - */ - struct TALER_Amount fee_deposit; - - /** - * What is the fee charged for melting? - */ - struct TALER_Amount fee_refresh; - - /** - * What is the fee charged for refunds? - */ - struct TALER_Amount fee_refund; + char *section; /** * Length of (new) RSA keys (in bits). @@ -141,6 +160,35 @@ struct Denomination }; +/** + * Information we keep for a client connected to us. + */ +struct Client +{ + + /** + * Kept in a DLL. + */ + struct Client *next; + + /** + * Kept in a DLL. + */ + struct Client *prev; + + /** + * Client socket. + */ + struct GNUNET_NETWORK_Handle *sock; + + /** + * Client task to read from @e sock. NULL if we are working. + */ + struct GNUNET_SCHEDULER_Task *task; + +}; + + /** * Return value from main(). */ @@ -164,9 +212,9 @@ static struct GNUNET_TIME_Absolute now_tmp; static const struct GNUNET_CONFIGURATION_Handle *kcfg; /** - * The configured currency. + * Where do we store the keys? */ -static char *currency; +static const char *keydir; /** * How much should coin creation (@e duration_withdraw) duration overlap @@ -175,21 +223,11 @@ static char *currency; */ static struct GNUNET_TIME_Relative overlap_duration; -/** - * How long should keys be legally valid? - */ -static struct GNUNET_TIME_Relative legal_duration; - /** * How long into the future do we pre-generate keys? */ static struct GNUNET_TIME_Relative lookahead_sign; -/** - * Largest duration for spending of any key. - */ -static struct GNUNET_TIME_Relative max_duration_spend; - /** * Until what time do we provide keys? */ @@ -226,6 +264,699 @@ static struct GNUNET_SCHEDULER_Task *accept_task; */ static struct GNUNET_SCHEDULER_Task *keygen_task; +/** + * Head of DLL of clients connected to us. + */ +static struct Client *clients_head; + +/** + * Tail of DLL of clients connected to us. + */ +static struct Client *clients_tail; + + +/** + * Function run to read incoming requests from a client. + * + * @param cls the `struct Client` + */ +static void +read_job (void *cls) +{ + struct Client *client = cls; + + // FIXME: DO WORK! + // check for: + // - sign requests + // - revocation requests!? +} + + +/** + * Notify @a client about @a dk becoming available. + * + * @param client the client to notify + * @param dk the key to notify @a client about + * @return #GNUNET_OK on success + */ +static int +notify_client_dk_add (const struct Client *client, + const struct DenominationKey *dk) +{ + + // FIXME: send msg! + return GNUNET_SYSERR; +} + + +/** + * Notify @a client about @a dk being purged. + * + * @param client the client to notify + * @param dk the key to notify @a client about + * @return #GNUNET_OK on success + */ +static int +notify_client_dk_del (const struct Client *client, + const struct DenominationKey *dk) +{ + struct TALER_CRYPTO_RsaKeyPurgeNotification pn = { + .header.type = htons (TALER_HELPER_RSA_MT_PURGE), + .header.size = htons (sizeof (pn)), + .h_denom_pub = dk->h_pub + }; + ssize_t ret; + + ret = send (GNUNET_NETWORK_get_fd (client->sock), + &pn, + sizeof (pn), + 0); + if (sizeof (pn) != ret) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "send"); + GNUNET_NETWORK_socket_close (client->sock); + GNUNET_CONTAINER_DLL_remove (client_head, + client_tail, + client); + GNUNET_free (client); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Function run to accept incoming connections on #sock. + * + * @param cls NULL + */ +static void +accept_job (void *cls) +{ + struct GNUNET_NETWORK_Handle *sock; + struct sockaddr_storage addr; + socklen_t alen; + + accept_task = NULL; + alen = sizeof (addr); + sock = GNUNET_NETWORK_socket_accept (lsock, + (struct sockaddr *) &addr, + &alen); + if (NULL != sock) + { + struct Client *client; + + client = GNUNET_new (struct Client); + client->sock = sock; + GNUNET_CONTAINER_DLL_insert (clients_head, + clients_tail, + client); + client->task = GNUNET_SCHEDULER_add_read (GNUNET_TIME_UNIT_FOREVER_REL, + sock, + &read_job, + client); + for (struct Denomination *denom = denom_head; + NULL != denom; + denom = denom->next) + { + for (struct DenominationKey *dk = denom->keys_head; + NULL != dk; + dk = dk->next) + { + if (GNUNET_OK != + notify_client_dk_add (client, + dk)) + { + /* client died, skip the rest */ + client = NULL; + break; + } + } + if (NULL == client) + break; + } + } + accept_task = GNUNET_SCHEDULER_add_read (GNUNET_TIME_UNIT_FOREVER_REL, + lsock, + &accept_job, + NULL); +} + + +/** + * Create a new denomination key (we do not have enough). + * + * @param denom denomination key to create + * @return #GNUNET_OK on success + */ +static int +create_key (struct Denomination *denom) +{ + struct DenominationKey *dk; + struct GNUNET_TIME_Absolute anchor; + struct GNUNET_CRYPTO_RsaPrivateKey *priv; + struct GNUNET_CRYPTO_RsaPublicKey *pub; + size_t buf_size; + void *buf; + + if (NULL == denom->keys_tail) + { + anchor = GNUNET_TIME_absolute_get (); + (void) GNUNET_TIME_absolute_round (&anchor); + } + else + { + anchor = GNUNET_TIME_absolute_add (denom->keys_tail.anchor, + GNUNET_TIME_relative_subtract ( + denom->duration_withdraw, + overlap_duration)); + } + priv = GNUNET_CRYPTO_rsa_private_key_create (denom->rsa_keysize); + if (NULL == priv) + { + GNUNET_break (0); + GNUNET_SCHEDULER_shutdown (); + global_ret = 40; + return GNUNET_SYSERR; + } + pub = GNUNET_CRYPTO_rsa_private_key_get_public (priv); + if (NULL == pub) + { + GNUNET_break (0); + GNUNET_CRYPTO_rsa_private_key_free (priv); + GNUNET_SCHEDULER_shutdown (); + global_ret = 41; + return; + } + buf_size = GNUNET_CRYPTO_rsa_private_key_encode (priv, + &buf); + dk = GNUNET_new (struct DenominationKey); + dk->denom = denom; + dk->anchor = anchor; + dk->denom_priv.rsa_priv = priv; + GNUNET_CRYPTO_rsa_public_key_hash (pub, + &dk->h_pub); + dk->denom_pub.rsa_pub = pub; + GNUNET_asprintf (&dk->filename, + "%s/%s/%llu", + keydir, + denom->section, + anchor.abs_value_us / GNUNET_TIME_UNIT_SECONDS.rel_value_us); + if (buf_size != + GNUNET_DISK_fn_write (dk->filename, + buf, + buf_size, + GNUNET_DISK_PERM_USER_READ)) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "write", + dk->filename); + GNUNET_free (buf); + GNUNET_CRYPTO_rsa_private_key_free (priv); + GNUNET_CRYPTO_rsa_public_key_free (pub); + GNUNET_free (dk); + GNUNET_SCHEDULER_shutdown (); + global_ret = 42; + return GNUNET_SYSERR; + } + GNUNET_free (buf); + + if (GNUNET_OK != + GNUNET_CONTAINER_multihashmap_put ( + keys, + &dk->h_pub, + dk, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Duplicate private key created! Terminating.\n"); + GNUNET_CRYPTO_rsa_private_key_free (priv); + GNUNET_CRYPTO_rsa_public_key_free (pub); + GNUNET_free (dk); + GNUNET_SCHEDULER_shutdown (); + global_ret = 43; + return; + } + GNUNET_CONTAINER_DLL_insert_tail (denom_keys_head, + denom_keys_tail, + dk); + { + struct Client *nxt; + + for (struct Client *client = clients_head; + NULL != client; + client = nxt) + { + nxt = client->next; + if (GNUNET_OK != + notify_client_dk_add (client, + dk)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Failed to notify client about new key, client dropped\n"); + } + } + } +} + + +/** + * At what time does this denomination require its next action? + * Basically, the minimum of the withdraw expiration time of the + * oldest denomination key, and the withdraw expiration time of + * the newest denomination key minus the #lookahead_sign time. + * + * @param denon denomination to compute action time for + */ +static struct GNUNET_TIME_Absolute +denomination_action_time (const struct Denomination *denom) +{ + return GNUNET_TIME_absolute_min ( + GNUNET_TIME_absolute_add (denom->keys_head->anchor, + denom->duration_withdraw), + GNUNET_TIME_absolute_subtract ( + GNUNET_TIME_absolute_subtract ( + GNUNET_TIME_absolute_add (denom->keys_tail->anchor, + denom->duration_withdraw), + lookahead_sign), + overlap_duration)); +} + + +/** + * The withdraw period of a key @a dk has expired. Purge it. + * + * @param[in] dk expired denomination key to purge and free + */ +static void +purge_key (struct DenominationKey *dk) +{ + struct Denomination *denom = dk->denom; + struct Client *nxt; + + for (struct Client *client = clients_head; + NULL != client; + client = nxt) + { + nxt = client->next; + if (GNUNET_OK != + notify_client_dk_del (client, + dk)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Failed to notify client about purged key, client dropped\n"); + } + } + GNUNET_CONTAINER_DLL_remove (denom->keys_head, + denom->keys_tail, + dk); + GNUNET_assert (GNUNET_OK == + GNUNET_CONTAINER_multihashmap_remove (keys, + &dk->h_pub, + dk)); + if (0 != unlink (dk->filename)) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "unlink", + dk->filename); + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Purged expired private key `%s'\n", + dk->filename); + } + GNUNET_free (dk->filename); + if (0 != dk->rc) + { + /* delay until all signing threads are done with this key */ + dk->purge = true; + return; + } + GNUNET_CRYPTO_rsa_private_key_free (dk->denom_priv.rsa_priv); + GNUNET_free (dk); +} + + +/** + * Create new keys and expire ancient keys of the given denomination @a denom. + * Removes the @a denom from the #denom_head DLL and re-insert its at the + * correct location sorted by next maintenance activity. + * + * @param[in,out] denom denomination to update material for + */ +static void +update_keys (struct Denomination *denom) +{ + /* create new denomination keys */ + while ( (NULL == denom->denom_tail) || + (0 == + GNUNET_TIME_absolute_get_remaining + GNUNET_TIME_absolute_subtract ( + GNUNET_TIME_absolute_subtract ( + GNUNET_TIME_absolute_add (denom->keys_tail->anchor, + denom->duration_withdraw), + lookahead_sign), + overlap_duration)) ) + if (GNUNET_OK != + create_key (denom)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to create keys for `%s'\n", + denom->section); + return; + } + /* remove expired denomination keys */ + while ( (NULL != denom->denom_head) && + (0 == + GNUNET_TIME_absolute_get_remaining + (GNUNET_TIME_absolute_add (denom->denom_head.anchor, + denom->duration_withdraw))) ) + purge_key (denom->denom_head); + + /* Update position of 'denom' in #denom_head DLL: sort by action time */ + { + struct Denomination *before; + struct GNUNET_TIME_Absolute at; + + at = denomination_action_time (denom); + before = NULL; + GNUNET_CONTAINER_DLL_remove (denom_head, + denom_tail, + denom); + for (struct Denomination *pos = denom_head; + NULL != pos; + pos = pos->next) + { + if (denomination_action_time (pos).abs_value_us > at.abs_value_us) + break; + before = pos; + } + GNUNET_CONTAINER_DLL_insert_after (denom_head, + denom_tail, + before, + denom); + } +} + + +/** + * Task run periodically to expire keys and/or generate fresh ones. + * + * @param cls NULL + */ +static void +update_denominations (void *cls) +{ + struct Denomination *denom; + + (void) cls; + keygen_task = NULL; + do { + denom = denom_head; + update_keys (denom); + } while (denom != denom_head); + keygen_task = GNUNET_SCHEDULER_add_at (TIME, + &update_denominations, + denomination_action_time (denom)); +} + + +/** + * Parse private key of denomination @a denom in @a buf. + * + * @param[out] denom denomination of the key + * @param filename name of the file we are parsing, for logging + * @param buf key material + * @param buf_size number of bytes in @a buf + */ +static void +parse_key (struct Denomination *denom, + const char *filename, + const void *buf, + size_t buf_size) +{ + struct GNUNET_CRYPTO_RsaPrivateKey *priv; + char *anchor_s; + char dummy; + unsigned long long anchor_ll; + struct GNUNET_TIME_Absolute anchor; + + anchor_s = strrchr (filename, + '/'); + if (NULL == anchor_s) + { + /* File in a directory without '/' in the name, this makes no sense. */ + GNUNET_break (0); + return; + } + anchor_s++; + if (1 != sscanf (anchor_s, + "%llu%c", + &anchor_ll, + &dummy)) + { + /* Filenames in KEYDIR must ONLY be the anchor time in seconds! */ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Filename `%s' invalid for key file, skipping\n", + filename); + return; + } + anchor.abs_time_us = anchor_ll * GNUNET_TIME_UNIT_SECONDS.rel_value_us; + if (anchor_ll != anchor.abs_time_us / GNUNET_TIME_UNIT_SECONDS.rel_value_us) + { + /* Integer overflow. Bad, invalid filename. */ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Filename `%s' invalid for key file, skipping\n", + filename); + return; + } + priv = GNUNET_CRYPTO_rsa_private_key_decode (buf, + buf_size); + if (NULL == priv) + { + /* Parser failure. */ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "File `%s' is malformed, skipping\n", + filename); + return; + } + + { + struct GNUNET_CRYPTO_RsaPublicKey *pub; + struct DenominationKey *dk; + struct DenominationKey *before; + + pub = GNUNET_CRYPTO_rsa_private_key_get_public (priv); + if (NULL == pub) + { + GNUNET_break (0); + GNUNET_CRYPTO_rsa_private_key_free (priv); + return; + } + dk = GNUNET_new (struct DenominationKey); + dk->denom_priv.rsa_priv = priv; + dk->denomination = denom; + dk->anchor = anchor; + dk->filename = GNUNET_strdup (filename); + GNUNET_CRYPTO_rsa_public_key_hash (pub, + &dk->h_pub); + dk->denom_pub.rsa_pub = pub; + if (GNUNET_OK != + GNUNET_CONTAINER_multihashmap_put ( + keys, + &dk->h_pub, + dk, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Duplicate private key detected in file `%s'. Skipping.\n", + filename); + GNUNET_CRYPTO_rsa_private_key_free (priv); + GNUNET_CRYPTO_rsa_public_key_free (pub); + GNUNET_free (dk); + return; + } + before = NULL; + for (struct DenominationKey *pos = denom->keys_head; + NULL != pos; + pos = pos->next) + { + if (pos->anchor.abs_value_us > anchor.abs_value_us) + break; + before = pos; + } + GNUNET_CONTAINER_DLL_insert_after (denom->keys_head, + denom->keys_tail, + before, + dk); + } +} + + +/** + * Import a private key from @a filename for the denomination + * given in @a cls. + * + * @param[in,out] cls a `struct Denomiantion` + * @param filename name of a file in the directory + */ +static int +import_key (void *cls, + const char *filename) +{ + struct Denomination *denom = cls; + struct GNUNET_DISK_FileHandle *fh; + struct GNUNET_DISK_MapHandle *map; + off_t fsize; + void *ptr; + int fd; + struct stat sbuf; + + { + struct stat lsbuf; + + if (0 != lstat (filename, + &lsbuf)) + { + GNUNET_log_strerror_filename (GNUNET_ERROR_TYPE_WARNING, + "lstat", + filename); + return GNUNET_OK; + } + if (! S_ISREG (lsbuf.st_mode)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "File `%s' is not a regular file, which is not allowed for private keys!\n", + filename); + return GNUNET_OK; + } + } + + fd = open (filename, + O_CLOEXEC); + if (-1 == fd) + { + GNUNET_log_strerror_filename (GNUNET_ERROR_TYPE_WARNING, + "open", + filename); + return GNUNET_OK; + } + if (0 != fstat (fd, + &sbuf)) + { + GNUNET_log_strerror_filename (GNUNET_ERROR_TYPE_WARNING, + "stat", + filename); + return GNUNET_OK; + } + if (! S_ISREG (sbuf.st_mode)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "File `%s' is not a regular file, which is not allowed for private keys!\n", + filename); + return GNUNET_OK; + } + if (0 != (sbuf.st_mode & (S_IWUSR | S_IRWXG | S_IRWXO))) + { + /* permission are NOT tight, try to patch them up! */ + if (0 != + fchmod (fd, + S_IRUSR)) + { + GNUNET_log_strerror_filename (GNUNET_ERROR_TYPE_WARNING, + "fchmod", + filename); + /* refuse to use key if file has wrong permissions */ + GNUNET_break (0 == close (fd)); + return GNUNET_OK; + } + } + fh = GNUNET_DISK_get_handle_from_int_fd (fd); + if (NULL == fh) + { + GNUNET_log_strerror_filename (GNUNET_ERROR_TYPE_WARNING, + "open", + filename); + GNUNET_break (0 == close (fd)); + return GNUNET_OK; + } + if (sbuf.st_size > 2048) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "File `%s' to big to be a private key\n", + filename); + GNUNET_DISK_file_close (fh); + return GNUNET_OK; + } + ptr = GNUNET_DISK_file_map (fh, + &map, + GNUNET_DISK_MAP_TYPE_READ, + (size_t) sbuf.st_size); + if (NULL == ptr) + { + GNUNET_log_strerror_filename (GNUNET_ERROR_TYPE_WARNING, + "mmap", + filename); + GNUNET_DISK_file_close (fh); + return GNUNET_OK; + } + parse_key (denom, + filename, + ptr, + (size_t) sbuf.st_size); + GNUNET_DISK_file_unmap (map); + GNUNET_DISK_file_close (fh); + return GNUNET_OK; +} + + +/** + * Generate new denomination signing keys for the denomination type of the given @a + * denomination_alias. + * + * @param cls a `int *`, to be set to #GNUNET_SYSERR on failure + * @param denomination_alias name of the denomination's section in the configuration + */ +static void +load_denominations (void *cls, + const char *denomination_alias) +{ + int *ret = cls; + struct Denomination *denom; + + if (0 != strncasecmp (denomination_alias, + "coin_", + strlen ("coin_"))) + return; /* not a denomination type definition */ + denom = GNUNET_new (struct Denomination); + if (GNUNET_OK != + parse_denomination_cfg (denomination_alias, + denom)) + { + *ret = GNUNET_SYSERR; + GNUNET_free (denom); + return; + } + { + char *dname; + + GNUNET_asprintf (&dname, + "%s/%s", + keydir, + denom->section); + GNUNET_DISK_directory_scan (dname, + &import_key, + denom); + GNUNET_free (dname); + } + GNUNET_CONTAINER_DLL_insert (denom_head, + denom_tail, + denom); + update_keys (denom); +} + /** * Load the various duration values from #kcfg. @@ -235,20 +966,6 @@ static struct GNUNET_SCHEDULER_Task *keygen_task; static int load_durations (void) { - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_time (kcfg, - "exchange", - "LEGAL_DURATION", - &legal_duration)) - { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - "exchange", - "LEGAL_DURATION", - "fails to specify valid timeframe"); - return GNUNET_SYSERR; - } - GNUNET_TIME_round_rel (&legal_duration); - if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (kcfg, "exchangedb", @@ -306,32 +1023,6 @@ parse_denomination_cfg (const char *ct, return GNUNET_SYSERR; } GNUNET_TIME_round_rel (&denom->duration_withdraw); - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_time (kcfg, - ct, - "DURATION_SPEND", - &denom->duration_spend)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - ct, - "DURATION_SPEND"); - return GNUNET_SYSERR; - } - GNUNET_TIME_round_rel (&denom->duration_spend); - max_duration_spend = GNUNET_TIME_relative_max (max_duration_spend, - denom->duration_spend); - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_time (kcfg, - ct, - "DURATION_LEGAL", - &denom->duration_legal)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - ct, - "DURATION_LEGAL"); - return GNUNET_SYSERR; - } - GNUNET_TIME_round_rel (&denom->duration_legal); if (duration_overlap.rel_value_us >= denom->duration_withdraw.rel_value_us) { @@ -362,112 +1053,11 @@ parse_denomination_cfg (const char *ct, return GNUNET_SYSERR; } denom->rsa_keysize = (unsigned int) rsa_keysize; - if (GNUNET_OK != - TALER_config_get_amount (kcfg, - ct, - "VALUE", - &denom->value)) - { - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_config_get_amount (kcfg, - ct, - "FEE_WITHDRAW", - &denom->fee_withdraw)) - { - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_config_get_amount (kcfg, - ct, - "FEE_DEPOSIT", - &denom->fee_deposit)) - { - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_config_get_amount (kcfg, - ct, - "FEE_REFRESH", - &denom->fee_refresh)) - { - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_config_get_amount (kcfg, - ct, - "fee_refund", - &denom->fee_refund)) - { - return GNUNET_SYSERR; - } + denom->section = GNUNET_strdup (ct); return GNUNET_OK; } -/** - * Generate new denomination signing keys for the denomination type of the given @a - * denomination_alias. - * - * @param cls a `int *`, to be set to #GNUNET_SYSERR on failure - * @param denomination_alias name of the denomination's section in the configuration - */ -static void -load_denominations (void *cls, - const char *denomination_alias) -{ - int *ret = cls; - struct Denomination *denom; - - if (0 != strncasecmp (denomination_alias, - "coin_", - strlen ("coin_"))) - return; /* not a denomination type definition */ - denom = GNUNET_new (struct Denomination); - if (GNUNET_OK != - parse_denomination_cfg (denomination_alias, - denom)) - { - *ret = GNUNET_SYSERR; - GNUNET_free (denom); - return; - } - GNUNET_CONTAINER_DLL_insert (denom_head, - denom_tail, - denom); - // FIXME: load all existing denomination keys for this denom from disk! -} - - -/** - * Function run to accept incoming connections on #sock. - * - * @param cls NULL - */ -static void -accept_job (void *cls) -{ - struct GNUNET_NETWORK_Handle *sock; - struct sockaddr_storage addr; - socklen_t alen; - - accept_task = NULL; - alen = sizeof (addr); - sock = GNUNET_NETWORK_socket_accept (lsock, - (struct sockaddr *) &addr, - &alen); - // FIXME: add to list of managed connections; - // then send all known keys; - // start to listen for incoming requests; - - accept_task = GNUNET_SCHEDULER_add_read (GNUNET_TIME_UNIT_FOREVER_REL, - lsock, - &accept_job, - NULL); -} - - /** * Function run on shutdown. Stops the various jobs (nicely). * @@ -497,7 +1087,7 @@ do_shutdown (void *cls) /** - * Main function that will be run. + * Main function that will be run under the GNUnet scheduler. * * @param cls closure * @param args remaining command-line arguments @@ -514,13 +1104,6 @@ run (void *cls, (void) args; (void) cfgfile; kcfg = cfg; - if (GNUNET_OK != - TALER_config_get_currency (cfg, - ¤cy)) - { - global_ret = 1; - return; - } if (now.abs_value_us != now_tmp.abs_value_us) { /* The user gave "--now", use it! */ @@ -540,9 +1123,9 @@ run (void *cls, } if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (kcfg, - "exchange", + "taler-helper-crypto-rsa", "KEYDIR", - &exchange_directory)) + &keydir)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "exchange", @@ -605,15 +1188,10 @@ run (void *cls, GNUNET_SCHEDULER_add_shutdown (&do_shutdown, NULL); - /* FIXME: start job to accept incoming requests on 'sock' */ - accept_task = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL, - lsock, - &accept_job, - NULL); /* Load denominations */ keys = GNUNET_CONTAINER_multihashmap_create (65536, - GNUNET_NO); + GNUNET_YES); { int ok; @@ -637,12 +1215,25 @@ run (void *cls, return; } - // FIXME: begin job to create additional denomination keys based on - // next needs! - // FIXME: same job or extra job for private key expiration/purge? + /* start job to accept incoming requests on 'sock' */ + accept_task = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL, + lsock, + &accept_job, + NULL); + + /* start job to keep keys up-to-date */ + keygen_task = GNUNET_SCHEDULER_add_now (&update_denominations, + NULL); } +/** + * The entry point. + * + * @param argc number of arguments in @a argv + * @param argv command-line arguments + * @return 0 on normal termination + */ int main (int argc, char **argv) diff --git a/src/util/taler-helper-crypto-rsa.h b/src/util/taler-helper-crypto-rsa.h new file mode 100644 index 000000000..0f03d12f2 --- /dev/null +++ b/src/util/taler-helper-crypto-rsa.h @@ -0,0 +1,90 @@ +/* + This file is part of TALER + Copyright (C) 2020 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see +*/ +/** + * @file util/taler-helper-crypto-rsa.h + * @brief IPC messages for the RSA crypto helper. + * @author Christian Grothoff + */ +#ifndef TALER_HELPER_CRYPTO_RSA_H +#define TALER_HELPER_CRYPTO_RSA_H + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Message sent if a key is available. + */ +struct TALER_CRYPTO_RsaKeyAvailableNotification +{ + /** + * Type is PURGE. + */ + struct GNUNET_MessageHeader header; + + /** + * Number of bytes of the public key. + */ + uint16_t pub_size; + + /** + * Number of bytes of the section name. + */ + uint16_t section_name; + + /** + * When does the key become available? + */ + struct GNUNET_TIME_AbsoluteNBO anchor_time; + + /** + * How long is the key available after @e anchor_time? + */ + struct GNUNET_TIME_RelativeNBO duration_withdraw; + + /* followed by @e pub_size bytes of the public key */ + + /* followed by @e section_name bytes of the configuration section name + of the denomination of this key */ + +}; + + +/** + * Message sent if a key was purged. + */ +struct TALER_CRYPTO_RsaKeyPurgeNotification +{ + /** + * Type is PURGE. + */ + struct GNUNET_MessageHeader header; + + /** + * For now, always zero. + */ + uint32_t reserved; + + /** + * Hash of the public key of the purged RSA key. + */ + struct GNUNET_HashCode h_denom_pub; + +}; + + +GNUNET_NETWORK_STRUCT_END + + +#endif