aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2020-11-14 22:27:50 +0100
committerChristian Grothoff <christian@grothoff.org>2020-11-14 22:27:50 +0100
commit4f0e38ab12340cea7297fc661c98ddba2d2ef681 (patch)
tree7b0a02dc118e28e32a90e48abcb0e53a3d56c9e1
parent81d6f8e0df4a32ff83b2af5d9d9087594f216c26 (diff)
more work on crypto RSA helper
-rw-r--r--ChangeLog7
-rw-r--r--src/exchange/taler-exchange-transfer.c23
-rw-r--r--src/exchangedb/Makefile.am4
-rw-r--r--src/exchangedb/drop0002.sql26
-rw-r--r--src/exchangedb/exchange-0002.sql48
-rw-r--r--src/exchangedb/plugin_exchangedb_postgres.c48
-rw-r--r--src/include/taler_exchangedb_plugin.h14
-rw-r--r--src/util/taler-helper-crypto-rsa.c999
-rw-r--r--src/util/taler-helper-crypto-rsa.h90
9 files changed, 1043 insertions, 216 deletions
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 <http://www.gnu.org/licenses/>
+--
+
+-- 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 <http://www.gnu.org/licenses/>
+--
+
+-- 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),
@@ -5242,6 +5248,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
@@ -2430,6 +2430,20 @@ struct TALER_EXCHANGEDB_Plugin
/**
+ * 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 <gcrypt.h>
@@ -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.
+ */
+ 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_EXCHANGEDB_DenominationKey dki;
+ 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;
@@ -90,18 +140,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
* the same key, but large enough to still provide a reasonable
@@ -110,34 +148,44 @@ 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;
+ char *section;
/**
- * What is the fee charged for withdrawal?
+ * Length of (new) RSA keys (in bits).
*/
- struct TALER_Amount fee_withdraw;
+ uint32_t rsa_keysize;
+};
+
+
+/**
+ * Information we keep for a client connected to us.
+ */
+struct Client
+{
/**
- * What is the fee charged for deposits?
+ * Kept in a DLL.
*/
- struct TALER_Amount fee_deposit;
+ struct Client *next;
/**
- * What is the fee charged for melting?
+ * Kept in a DLL.
*/
- struct TALER_Amount fee_refresh;
+ struct Client *prev;
/**
- * What is the fee charged for refunds?
+ * Client socket.
*/
- struct TALER_Amount fee_refund;
+ struct GNUNET_NETWORK_Handle *sock;
/**
- * Length of (new) RSA keys (in bits).
+ * Client task to read from @e sock. NULL if we are working.
*/
- uint32_t rsa_keysize;
+ struct GNUNET_SCHEDULER_Task *task;
+
};
@@ -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
@@ -176,21 +224,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?
*/
static struct GNUNET_TIME_Absolute lookahead_sign_stamp;
@@ -226,30 +264,709 @@ 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;
/**
- * Load the various duration values from #kcfg.
+ * 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
-load_durations (void)
+notify_client_dk_add (const struct Client *client,
+ const struct DenominationKey *dk)
{
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_time (kcfg,
- "exchange",
- "LEGAL_DURATION",
- &legal_duration))
+
+ // 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_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- "exchange",
- "LEGAL_DURATION",
- "fails to specify valid timeframe");
+ 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;
}
- GNUNET_TIME_round_rel (&legal_duration);
+ 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.
+ *
+ * @return #GNUNET_OK on success
+ */
+static int
+load_durations (void)
+{
+ if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_time (kcfg,
"exchangedb",
"OVERLAP_DURATION",
@@ -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,113 +1053,12 @@ 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).
*
* @param cls NULL
@@ -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,
- &currency))
- {
- 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 <http://www.gnu.org/licenses/>
+*/
+/**
+ * @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