aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Grothoff <grothoff@gnunet.org>2024-11-20 17:02:04 +0100
committerChristian Grothoff <grothoff@gnunet.org>2024-11-20 17:02:04 +0100
commitc93c46cc0e4bd9571825c2460d48812d443fadb4 (patch)
tree1685c92c405a66dd3fe0dcc14f80490928b7289e
parentafc996aecbe0f7dc75fdc9c451f2510f5ad8ba38 (diff)
create library functions for secmods
-rw-r--r--src/include/taler_util.h12
-rwxr-xr-xsrc/testing/taler-unified-setup.sh6
-rw-r--r--src/util/Makefile.am15
-rw-r--r--src/util/secmod_cs.c2252
-rw-r--r--src/util/secmod_eddsa.c1130
-rw-r--r--src/util/secmod_rsa.c2053
-rw-r--r--src/util/taler-exchange-secmod-cs.c2286
-rw-r--r--src/util/taler-exchange-secmod-eddsa.c1147
-rw-r--r--src/util/taler-exchange-secmod-rsa.c2077
9 files changed, 5487 insertions, 5491 deletions
diff --git a/src/include/taler_util.h b/src/include/taler_util.h
index 1c65610b3..527a34d9e 100644
--- a/src/include/taler_util.h
+++ b/src/include/taler_util.h
@@ -912,7 +912,7 @@ struct TALER_SECMOD_Options
* Number of workers to launch. Note that connections to
* exchanges are NOT workers.
*/
- unsigned int max_workers = 16;
+ unsigned int max_workers;
/**
* Time when the key update is executed.
@@ -931,6 +931,12 @@ struct TALER_SECMOD_Options
*/
const char *section;
+ /**
+ * Return value from main().
+ */
+ int global_ret;
+
+
};
#define TALER_SECMOD_OPTIONS(opt) \
@@ -940,12 +946,12 @@ struct TALER_SECMOD_Options
"time", \
"TIMESTAMP", \
"pretend it is a different time for the update", \
- &opt->global_now_tmp), \
+ &(opt)->global_now_tmp), \
GNUNET_GETOPT_option_uint ('w', \
"workers", \
"COUNT", \
"use COUNT workers for parallel processing of batch requests", \
- &opt->max_workers)
+ &(opt)->max_workers)
/**
diff --git a/src/testing/taler-unified-setup.sh b/src/testing/taler-unified-setup.sh
index 5a95f8f17..87829c244 100755
--- a/src/testing/taler-unified-setup.sh
+++ b/src/testing/taler-unified-setup.sh
@@ -542,9 +542,9 @@ then
DONAU_URL="http://localhost:${DONAU_PORT}/"
fi
donau-dbinit -c "$CONF" --reset
- $USE_VALGRIND taler-exchange-secmod-eddsa -c "$CONF" -L "$LOGLEVEL" -s donau 2> donau-secmod-eddsa.log &
- $USE_VALGRIND taler-exchange-secmod-rsa -c "$CONF" -L "$LOGLEVEL" -s donau 2> donau-secmod-rsa.log &
- $USE_VALGRIND taler-exchange-secmod-cs -c "$CONF" -L "$LOGLEVEL" -s donau 2> donau-secmod-cs.log &
+ $USE_VALGRIND donau-secmod-eddsa -c "$CONF" -L "$LOGLEVEL" 2> donau-secmod-eddsa.log &
+ $USE_VALGRIND donau-secmod-rsa -c "$CONF" -L "$LOGLEVEL" 2> donau-secmod-rsa.log &
+ $USE_VALGRIND donau-secmod-cs -c "$CONF" -L "$LOGLEVEL" 2> donau-secmod-cs.log &
$USE_VALGRIND donau-httpd -c "$CONF" -L "$LOGLEVEL" 2> donau-httpd.log &
echo " DONE"
fi
diff --git a/src/util/Makefile.am b/src/util/Makefile.am
index 200955616..b4315ddea 100644
--- a/src/util/Makefile.am
+++ b/src/util/Makefile.am
@@ -48,11 +48,8 @@ taler_exchange_config_LDADD = \
-lgnunetutil \
$(XLIB)
-
-
taler_exchange_secmod_rsa_SOURCES = \
- taler-exchange-secmod-rsa.c taler-exchange-secmod-rsa.h \
- secmod_common.c secmod_common.h
+ taler-exchange-secmod-rsa.c taler-exchange-secmod-rsa.h
taler_exchange_secmod_rsa_LDADD = \
libtalerutil.la \
-lgnunetutil \
@@ -61,8 +58,7 @@ taler_exchange_secmod_rsa_LDADD = \
$(XLIB)
taler_exchange_secmod_cs_SOURCES = \
- taler-exchange-secmod-cs.c taler-exchange-secmod-cs.h \
- secmod_common.c secmod_common.h
+ taler-exchange-secmod-cs.c taler-exchange-secmod-cs.h
taler_exchange_secmod_cs_LDADD = \
libtalerutil.la \
-lgnunetutil \
@@ -71,8 +67,7 @@ taler_exchange_secmod_cs_LDADD = \
$(XLIB)
taler_exchange_secmod_eddsa_SOURCES = \
- taler-exchange-secmod-eddsa.c taler-exchange-secmod-eddsa.h \
- secmod_common.c secmod_common.h
+ taler-exchange-secmod-eddsa.c taler-exchange-secmod-eddsa.h
taler_exchange_secmod_eddsa_LDADD = \
libtalerutil.la \
-lgnunetutil \
@@ -107,6 +102,10 @@ libtalerutil_la_SOURCES = \
mhd.c \
offline_signatures.c \
payto.c \
+ secmod_common.c secmod_common.h \
+ secmod_cs.c \
+ secmod_eddsa.c \
+ secmod_rsa.c \
secmod_signatures.c \
taler_error_codes.c \
tokens.c \
diff --git a/src/util/secmod_cs.c b/src/util/secmod_cs.c
new file mode 100644
index 000000000..54a5789e6
--- /dev/null
+++ b/src/util/secmod_cs.c
@@ -0,0 +1,2252 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2024 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/secmod_cs.c
+ * @brief Standalone process to perform private key CS operations
+ * @author Christian Grothoff
+ *
+ * Key design points:
+ * - EVERY thread of the exchange will have its own pair of connections to the
+ * crypto helpers. This way, every thread 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 do the signing in parallel, one per client.
+ * - 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).
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler-exchange-secmod-cs.h"
+#include <gcrypt.h>
+#include <pthread.h>
+#include <sys/eventfd.h>
+#include "taler_error_codes.h"
+#include "taler_signatures.h"
+#include "secmod_common.h"
+#include <poll.h>
+
+
+/**
+ * Information we keep per denomination.
+ */
+struct Denomination;
+
+
+/**
+ * One particular denomination key.
+ */
+struct DenominationKey
+{
+
+ /**
+ * Kept in a DLL of the respective denomination. Sorted by anchor time.
+ */
+ struct DenominationKey *next;
+
+ /**
+ * Kept in a DLL of the respective denomination. Sorted by anchor time.
+ */
+ struct DenominationKey *prev;
+
+ /**
+ * Denomination this key belongs to.
+ */
+ struct Denomination *denom;
+
+ /**
+ * Name of the file this key is stored under.
+ */
+ char *filename;
+
+ /**
+ * The private key of the denomination.
+ */
+ struct GNUNET_CRYPTO_CsPrivateKey denom_priv;
+
+ /**
+ * The public key of the denomination.
+ */
+ struct GNUNET_CRYPTO_CsPublicKey denom_pub;
+
+ /**
+ * Message to transmit to clients to introduce this public key.
+ */
+ struct TALER_CRYPTO_CsKeyAvailableNotification *an;
+
+ /**
+ * Hash of this denomination's public key.
+ */
+ struct TALER_CsPubHashP h_cs;
+
+ /**
+ * Time at which this key is supposed to become valid.
+ */
+ struct GNUNET_TIME_Timestamp anchor;
+
+ /**
+ * Generation when this key was created or revoked.
+ */
+ uint64_t key_gen;
+
+ /**
+ * 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;
+
+};
+
+
+struct Denomination
+{
+
+ /**
+ * Kept in a DLL. Sorted by #denomination_action_time().
+ */
+ struct Denomination *next;
+
+ /**
+ * Kept in a DLL. Sorted by #denomination_action_time().
+ */
+ struct Denomination *prev;
+
+ /**
+ * Head of DLL of actual keys of this denomination.
+ */
+ struct DenominationKey *keys_head;
+
+ /**
+ * Tail of DLL of actual keys of this denomination.
+ */
+ struct DenominationKey *keys_tail;
+
+ /**
+ * 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
+ * anonymity set.
+ */
+ struct GNUNET_TIME_Relative duration_withdraw;
+
+ /**
+ * What is the configuration section of this denomination type? Also used
+ * for the directory name where the denomination keys are stored.
+ */
+ char *section;
+
+};
+
+
+/**
+ * A semaphore.
+ */
+struct Semaphore
+{
+ /**
+ * Mutex for the semaphore.
+ */
+ pthread_mutex_t mutex;
+
+ /**
+ * Condition variable for the semaphore.
+ */
+ pthread_cond_t cv;
+
+ /**
+ * Counter of the semaphore.
+ */
+ unsigned int ctr;
+};
+
+
+/**
+ * Job in a batch sign request.
+ */
+struct BatchJob;
+
+/**
+ * Handle for a thread that does work in batch signing.
+ */
+struct Worker
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct Worker *prev;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct Worker *next;
+
+ /**
+ * Job this worker should do next.
+ */
+ struct BatchJob *job;
+
+ /**
+ * Semaphore to signal the worker that a job is available.
+ */
+ struct Semaphore sem;
+
+ /**
+ * Handle for this thread.
+ */
+ pthread_t pt;
+
+ /**
+ * Set to true if the worker should terminate.
+ */
+ bool do_shutdown;
+};
+
+
+/**
+ * Job in a batch sign request.
+ */
+struct BatchJob
+{
+
+ /**
+ * Thread doing the work.
+ */
+ struct Worker *worker;
+
+ /**
+ * Semaphore to signal that the job is finished.
+ */
+ struct Semaphore sem;
+
+ /**
+ * Computation status.
+ */
+ enum TALER_ErrorCode ec;
+
+ /**
+ * Which type of request is this?
+ */
+ enum { TYPE_SIGN, TYPE_RDERIVE } type;
+
+ /**
+ * Details depending on @e type.
+ */
+ union
+ {
+
+ /**
+ * Details if @e type is TYPE_SIGN.
+ */
+ struct
+ {
+ /**
+ * Request we are working on.
+ */
+ const struct TALER_CRYPTO_CsSignRequestMessage *sr;
+
+ /**
+ * Result with the signature.
+ */
+ struct GNUNET_CRYPTO_CsBlindSignature cs_answer;
+ } sign;
+
+ /**
+ * Details if type is TYPE_RDERIVE.
+ */
+ struct
+ {
+ /**
+ * Request we are answering.
+ */
+ const struct TALER_CRYPTO_CsRDeriveRequest *rdr;
+
+ /**
+ * Pair of points to return.
+ */
+ struct GNUNET_CRYPTO_CSPublicRPairP rpairp;
+
+ } rderive;
+
+ } details;
+
+};
+
+/**
+ * Head of DLL of workers ready for more work.
+ */
+static struct Worker *worker_head;
+
+/**
+ * Tail of DLL of workers ready for more work.
+ */
+static struct Worker *worker_tail;
+
+/**
+ * Lock for manipulating the worker DLL.
+ */
+static pthread_mutex_t worker_lock;
+
+/**
+ * Total number of workers that were started.
+ */
+static unsigned int workers;
+
+/**
+ * Semaphore used to grab a worker.
+ */
+static struct Semaphore worker_sem;
+
+/**
+ * Command-line options for various TALER_SECMOD_XXX_run() functions.
+ */
+static struct TALER_SECMOD_Options *globals;
+
+/**
+ * Where do we store the keys?
+ */
+static char *keydir;
+
+/**
+ * How much should coin creation (@e duration_withdraw) duration overlap
+ * with the next denomination? Basically, the starting time of two
+ * denominations is always @e duration_withdraw - #overlap_duration apart.
+ */
+static struct GNUNET_TIME_Relative overlap_duration;
+
+/**
+ * How long into the future do we pre-generate keys?
+ */
+static struct GNUNET_TIME_Relative lookahead_sign;
+
+/**
+ * All of our denominations, in a DLL. Sorted?
+ */
+static struct Denomination *denom_head;
+
+/**
+ * All of our denominations, in a DLL. Sorted?
+ */
+static struct Denomination *denom_tail;
+
+/**
+ * Map of hashes of public (CS) keys to `struct DenominationKey *`
+ * with the respective private keys.
+ */
+static struct GNUNET_CONTAINER_MultiHashMap *keys;
+
+/**
+ * Task run to generate new keys.
+ */
+static struct GNUNET_SCHEDULER_Task *keygen_task;
+
+/**
+ * Lock for the keys queue.
+ */
+static pthread_mutex_t keys_lock;
+
+/**
+ * Current key generation.
+ */
+static uint64_t key_gen;
+
+/**
+ * Generate the announcement message for @a dk.
+ *
+ * @param[in,out] dk denomination key to generate the announcement for
+ */
+static void
+generate_response (struct DenominationKey *dk)
+{
+ struct Denomination *denom = dk->denom;
+ size_t nlen = strlen (denom->section) + 1;
+ struct TALER_CRYPTO_CsKeyAvailableNotification *an;
+ void *p;
+ size_t tlen;
+
+ GNUNET_assert (sizeof(dk->denom_pub) < UINT16_MAX);
+ GNUNET_assert (nlen < UINT16_MAX);
+ tlen = nlen + sizeof (*an);
+ GNUNET_assert (tlen < UINT16_MAX);
+ an = GNUNET_malloc (tlen);
+ an->header.size = htons ((uint16_t) tlen);
+ an->header.type = htons (TALER_HELPER_CS_MT_AVAIL);
+ an->section_name_len = htons ((uint16_t) nlen);
+ an->anchor_time = GNUNET_TIME_timestamp_hton (dk->anchor);
+ an->duration_withdraw = GNUNET_TIME_relative_hton (denom->duration_withdraw);
+ an->denom_pub = dk->denom_pub;
+ TALER_exchange_secmod_cs_sign (&dk->h_cs,
+ denom->section,
+ dk->anchor,
+ denom->duration_withdraw,
+ &TES_smpriv,
+ &an->secm_sig);
+ an->secm_pub = TES_smpub;
+ p = (void *) &an[1];
+ GNUNET_memcpy (p,
+ denom->section,
+ nlen);
+ dk->an = an;
+}
+
+
+/**
+ * Do the actual signing work.
+ *
+ * @param h_cs hash of key to sign with
+ * @param planchet message to sign
+ * @param for_melt true if for melting
+ * @param[out] cs_sigp set to the CS signature
+ * @return #TALER_EC_NONE on success
+ */
+static enum TALER_ErrorCode
+do_sign (const struct TALER_CsPubHashP *h_cs,
+ const struct GNUNET_CRYPTO_CsBlindedMessage *planchet,
+ bool for_melt,
+ struct GNUNET_CRYPTO_CsBlindSignature *cs_sigp)
+{
+ struct GNUNET_CRYPTO_CsRSecret r[2];
+ struct DenominationKey *dk;
+
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ dk = GNUNET_CONTAINER_multihashmap_get (keys,
+ &h_cs->hash);
+ if (NULL == dk)
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Signing request failed, denomination key %s unknown\n",
+ GNUNET_h2s (&h_cs->hash));
+ return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
+ }
+ if (GNUNET_TIME_absolute_is_future (dk->anchor.abs_time))
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Signing request failed, denomination key %s is not yet valid\n",
+ GNUNET_h2s (&h_cs->hash));
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_TOO_EARLY;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received request to sign over bytes with key %s\n",
+ GNUNET_h2s (&h_cs->hash));
+ GNUNET_assert (dk->rc < UINT_MAX);
+ dk->rc++;
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_CRYPTO_cs_r_derive (&planchet->nonce,
+ for_melt ? "rm" : "rw",
+ &dk->denom_priv,
+ r);
+ GNUNET_CRYPTO_cs_sign_derive (&dk->denom_priv,
+ r,
+ planchet,
+ cs_sigp);
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ GNUNET_assert (dk->rc > 0);
+ dk->rc--;
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ return TALER_EC_NONE;
+}
+
+
+/**
+ * Generate error response that signing failed.
+ *
+ * @param client client to send response to
+ * @param ec error code to include
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+fail_sign (struct TES_Client *client,
+ enum TALER_ErrorCode ec)
+{
+ struct TALER_CRYPTO_SignFailure sf = {
+ .header.size = htons (sizeof (sf)),
+ .header.type = htons (TALER_HELPER_CS_MT_RES_SIGN_FAILURE),
+ .ec = htonl (ec)
+ };
+
+ return TES_transmit (client->csock,
+ &sf.header);
+}
+
+
+/**
+ * Generate error response that deriving failed.
+ *
+ * @param client client to send response to
+ * @param ec error code to include
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+fail_derive (struct TES_Client *client,
+ enum TALER_ErrorCode ec)
+{
+ struct TALER_CRYPTO_RDeriveFailure sf = {
+ .header.size = htons (sizeof (sf)),
+ .header.type = htons (TALER_HELPER_CS_MT_RES_RDERIVE_FAILURE),
+ .ec = htonl (ec)
+ };
+
+ return TES_transmit (client->csock,
+ &sf.header);
+}
+
+
+/**
+ * Generate signature response.
+ *
+ * @param client client to send response to
+ * @param cs_answer signature to send
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+send_signature (struct TES_Client *client,
+ const struct GNUNET_CRYPTO_CsBlindSignature *cs_answer)
+{
+ struct TALER_CRYPTO_SignResponse sres;
+
+ sres.header.size = htons (sizeof (sres));
+ sres.header.type = htons (TALER_HELPER_CS_MT_RES_SIGNATURE);
+ sres.b = htonl (cs_answer->b);
+ sres.cs_answer = cs_answer->s_scalar;
+ return TES_transmit (client->csock,
+ &sres.header);
+}
+
+
+/**
+ * Handle @a client request @a sr to create signature. Create the
+ * signature using the respective key and return the result to
+ * the client.
+ *
+ * @param client the client making the request
+ * @param sr the request details
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_sign_request (struct TES_Client *client,
+ const struct TALER_CRYPTO_CsSignRequestMessage *sr)
+{
+ struct GNUNET_CRYPTO_CsBlindSignature cs_answer;
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+ enum TALER_ErrorCode ec;
+ enum GNUNET_GenericReturnValue ret;
+
+ ec = do_sign (&sr->h_cs,
+ &sr->message,
+ (0 != ntohl (sr->for_melt)),
+ &cs_answer);
+ if (TALER_EC_NONE != ec)
+ {
+ return fail_sign (client,
+ ec);
+ }
+ ret = send_signature (client,
+ &cs_answer);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Sent CS signature after %s\n",
+ GNUNET_TIME_relative2s (
+ GNUNET_TIME_absolute_get_duration (now),
+ GNUNET_YES));
+ return ret;
+}
+
+
+/**
+ * Do the actual deriving work.
+ *
+ * @param h_cs key to sign with
+ * @param nonce nonce to derive from
+ * @param for_melt true if for melting
+ * @param[out] rpairp set to the derived values
+ * @return #TALER_EC_NONE on success
+ */
+static enum TALER_ErrorCode
+do_derive (const struct TALER_CsPubHashP *h_cs,
+ const struct GNUNET_CRYPTO_CsSessionNonce *nonce,
+ bool for_melt,
+ struct GNUNET_CRYPTO_CSPublicRPairP *rpairp)
+{
+ struct DenominationKey *dk;
+ struct GNUNET_CRYPTO_CSPrivateRPairP r_priv;
+
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ dk = GNUNET_CONTAINER_multihashmap_get (keys,
+ &h_cs->hash);
+ if (NULL == dk)
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "R Derive request failed, denomination key %s unknown\n",
+ GNUNET_h2s (&h_cs->hash));
+ return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
+ }
+ if (GNUNET_TIME_absolute_is_future (dk->anchor.abs_time))
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "R Derive request failed, denomination key %s is not yet valid\n",
+ GNUNET_h2s (&h_cs->hash));
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_TOO_EARLY;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received request to derive R with key %s\n",
+ GNUNET_h2s (&h_cs->hash));
+ GNUNET_assert (dk->rc < UINT_MAX);
+ dk->rc++;
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_CRYPTO_cs_r_derive (nonce,
+ for_melt ? "rm" : "rw",
+ &dk->denom_priv,
+ r_priv.r);
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ GNUNET_assert (dk->rc > 0);
+ dk->rc--;
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_CRYPTO_cs_r_get_public (&r_priv.r[0],
+ &rpairp->r_pub[0]);
+ GNUNET_CRYPTO_cs_r_get_public (&r_priv.r[1],
+ &rpairp->r_pub[1]);
+ return TALER_EC_NONE;
+}
+
+
+/**
+ * Generate derivation response.
+ *
+ * @param client client to send response to
+ * @param r_pub public point value pair to send
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+send_derivation (struct TES_Client *client,
+ const struct GNUNET_CRYPTO_CSPublicRPairP *r_pub)
+{
+ struct TALER_CRYPTO_RDeriveResponse rdr = {
+ .header.size = htons (sizeof (rdr)),
+ .header.type = htons (TALER_HELPER_CS_MT_RES_RDERIVE),
+ .r_pub = *r_pub
+ };
+
+ return TES_transmit (client->csock,
+ &rdr.header);
+}
+
+
+/**
+ * Initialize a semaphore @a sem with a value of @a val.
+ *
+ * @param[out] sem semaphore to initialize
+ * @param val initial value of the semaphore
+ */
+static void
+sem_init (struct Semaphore *sem,
+ unsigned int val)
+{
+ GNUNET_assert (0 ==
+ pthread_mutex_init (&sem->mutex,
+ NULL));
+ GNUNET_assert (0 ==
+ pthread_cond_init (&sem->cv,
+ NULL));
+ sem->ctr = val;
+}
+
+
+/**
+ * Decrement semaphore, blocks until this is possible.
+ *
+ * @param[in,out] sem semaphore to decrement
+ */
+static void
+sem_down (struct Semaphore *sem)
+{
+ GNUNET_assert (0 == pthread_mutex_lock (&sem->mutex));
+ while (0 == sem->ctr)
+ {
+ pthread_cond_wait (&sem->cv,
+ &sem->mutex);
+ }
+ sem->ctr--;
+ GNUNET_assert (0 == pthread_mutex_unlock (&sem->mutex));
+}
+
+
+/**
+ * Increment semaphore, blocks until this is possible.
+ *
+ * @param[in,out] sem semaphore to decrement
+ */
+static void
+sem_up (struct Semaphore *sem)
+{
+ GNUNET_assert (0 == pthread_mutex_lock (&sem->mutex));
+ sem->ctr++;
+ GNUNET_assert (0 == pthread_mutex_unlock (&sem->mutex));
+ pthread_cond_signal (&sem->cv);
+}
+
+
+/**
+ * Release resources used by @a sem.
+ *
+ * @param[in] sem semaphore to release (except the memory itself)
+ */
+static void
+sem_done (struct Semaphore *sem)
+{
+ GNUNET_break (0 == pthread_cond_destroy (&sem->cv));
+ GNUNET_break (0 == pthread_mutex_destroy (&sem->mutex));
+}
+
+
+/**
+ * Main logic of a worker thread. Grabs work, does it,
+ * grabs more work.
+ *
+ * @param cls a `struct Worker *`
+ * @returns cls
+ */
+static void *
+worker (void *cls)
+{
+ struct Worker *w = cls;
+
+ while (true)
+ {
+ GNUNET_assert (0 == pthread_mutex_lock (&worker_lock));
+ GNUNET_CONTAINER_DLL_insert (worker_head,
+ worker_tail,
+ w);
+ GNUNET_assert (0 == pthread_mutex_unlock (&worker_lock));
+ sem_up (&worker_sem);
+ sem_down (&w->sem);
+ if (w->do_shutdown)
+ break;
+ {
+ struct BatchJob *bj = w->job;
+
+ switch (bj->type)
+ {
+ case TYPE_SIGN:
+ {
+ const struct TALER_CRYPTO_CsSignRequestMessage *sr
+ = bj->details.sign.sr;
+
+ bj->ec = do_sign (&sr->h_cs,
+ &sr->message,
+ (0 != ntohl (sr->for_melt)),
+ &bj->details.sign.cs_answer);
+ break;
+ }
+ case TYPE_RDERIVE:
+ {
+ const struct TALER_CRYPTO_CsRDeriveRequest *rdr
+ = bj->details.rderive.rdr;
+ bj->ec = do_derive (&rdr->h_cs,
+ &rdr->nonce,
+ (0 != ntohl (rdr->for_melt)),
+ &bj->details.rderive.rpairp);
+ break;
+ }
+ }
+ sem_up (&bj->sem);
+ w->job = NULL;
+ }
+ }
+ return w;
+}
+
+
+/**
+ * Start batch job @a bj to sign @a sr.
+ *
+ * @param sr signature request to answer
+ * @param[out] bj job data structure
+ */
+static void
+start_sign_job (const struct TALER_CRYPTO_CsSignRequestMessage *sr,
+ struct BatchJob *bj)
+{
+ sem_init (&bj->sem,
+ 0);
+ bj->type = TYPE_SIGN;
+ bj->details.sign.sr = sr;
+ sem_down (&worker_sem);
+ GNUNET_assert (0 == pthread_mutex_lock (&worker_lock));
+ bj->worker = worker_head;
+ GNUNET_CONTAINER_DLL_remove (worker_head,
+ worker_tail,
+ bj->worker);
+ GNUNET_assert (0 == pthread_mutex_unlock (&worker_lock));
+ bj->worker->job = bj;
+ sem_up (&bj->worker->sem);
+}
+
+
+/**
+ * Start batch job @a bj to derive @a rdr.
+ *
+ * @param rdr derivation request to answer
+ * @param[out] bj job data structure
+ */
+static void
+start_derive_job (const struct TALER_CRYPTO_CsRDeriveRequest *rdr,
+ struct BatchJob *bj)
+{
+ sem_init (&bj->sem,
+ 0);
+ bj->type = TYPE_RDERIVE;
+ bj->details.rderive.rdr = rdr;
+ sem_down (&worker_sem);
+ GNUNET_assert (0 == pthread_mutex_lock (&worker_lock));
+ bj->worker = worker_head;
+ GNUNET_CONTAINER_DLL_remove (worker_head,
+ worker_tail,
+ bj->worker);
+ GNUNET_assert (0 == pthread_mutex_unlock (&worker_lock));
+ bj->worker->job = bj;
+ sem_up (&bj->worker->sem);
+}
+
+
+/**
+ * Finish a job @a bj for a @a client.
+ *
+ * @param client who made the request
+ * @param[in,out] bj job to finish
+ */
+static void
+finish_job (struct TES_Client *client,
+ struct BatchJob *bj)
+{
+ sem_down (&bj->sem);
+ sem_done (&bj->sem);
+ switch (bj->type)
+ {
+ case TYPE_SIGN:
+ if (TALER_EC_NONE != bj->ec)
+ {
+ fail_sign (client,
+ bj->ec);
+ return;
+ }
+ send_signature (client,
+ &bj->details.sign.cs_answer);
+ break;
+ case TYPE_RDERIVE:
+ if (TALER_EC_NONE != bj->ec)
+ {
+ fail_derive (client,
+ bj->ec);
+ return;
+ }
+ send_derivation (client,
+ &bj->details.rderive.rpairp);
+ break;
+ }
+}
+
+
+/**
+ * Handle @a client request @a sr to create a batch of signature. Creates the
+ * signatures using the respective key and return the results to the client.
+ *
+ * @param client the client making the request
+ * @param bsr the request details
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_batch_sign_request (struct TES_Client *client,
+ const struct TALER_CRYPTO_BatchSignRequest *bsr)
+{
+ uint32_t bs = ntohl (bsr->batch_size);
+ uint16_t size = ntohs (bsr->header.size) - sizeof (*bsr);
+ const void *off = (const void *) &bsr[1];
+ unsigned int idx = 0;
+ struct BatchJob jobs[GNUNET_NZL (bs)];
+ bool failure = false;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Handling batch sign request of size %u\n",
+ (unsigned int) bs);
+ if (bs > TALER_MAX_FRESH_COINS)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ while ( (bs > 0) &&
+ (size >= sizeof (struct TALER_CRYPTO_CsSignRequestMessage)) )
+ {
+ const struct TALER_CRYPTO_CsSignRequestMessage *sr = off;
+ uint16_t s = ntohs (sr->header.size);
+
+ if (s > size)
+ {
+ failure = true;
+ bs = idx;
+ break;
+ }
+ start_sign_job (sr,
+ &jobs[idx++]);
+ off += s;
+ size -= s;
+ }
+ GNUNET_break_op (0 == size);
+ bs = GNUNET_MIN (bs,
+ idx);
+ for (unsigned int i = 0; i<bs; i++)
+ finish_job (client,
+ &jobs[i]);
+ if (failure)
+ {
+ struct TALER_CRYPTO_SignFailure sf = {
+ .header.size = htons (sizeof (sf)),
+ .header.type = htons (TALER_HELPER_CS_MT_RES_BATCH_SIGN_FAILURE),
+ .ec = htonl (TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE)
+ };
+
+ GNUNET_break (0);
+ return TES_transmit (client->csock,
+ &sf.header);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Handle @a client request @a sr to create a batch of derivations. Creates the
+ * derivations using the respective key and return the results to the client.
+ *
+ * @param client the client making the request
+ * @param bdr the request details
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_batch_derive_request (struct TES_Client *client,
+ const struct TALER_CRYPTO_BatchDeriveRequest *bdr)
+{
+ uint32_t bs = ntohl (bdr->batch_size);
+ uint16_t size = ntohs (bdr->header.size) - sizeof (*bdr);
+ const void *off = (const void *) &bdr[1];
+ unsigned int idx = 0;
+ struct BatchJob jobs[bs];
+ bool failure = false;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Handling batch derivation request of size %u\n",
+ (unsigned int) bs);
+ if (bs > TALER_MAX_FRESH_COINS)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ while ( (bs > 0) &&
+ (size >= sizeof (struct TALER_CRYPTO_CsRDeriveRequest)) )
+ {
+ const struct TALER_CRYPTO_CsRDeriveRequest *rdr = off;
+ uint16_t s = ntohs (rdr->header.size);
+
+ if ( (s > size) ||
+ (s != sizeof (*rdr)) )
+ {
+ failure = true;
+ bs = idx;
+ break;
+ }
+ start_derive_job (rdr,
+ &jobs[idx++]);
+ off += s;
+ size -= s;
+ }
+ GNUNET_break_op (0 == size);
+ bs = GNUNET_MIN (bs,
+ idx);
+ for (unsigned int i = 0; i<bs; i++)
+ finish_job (client,
+ &jobs[i]);
+ if (failure)
+ {
+ GNUNET_break (0);
+ return fail_derive (client,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Start worker thread for batch processing.
+ *
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+start_worker (void)
+{
+ struct Worker *w;
+
+ w = GNUNET_new (struct Worker);
+ sem_init (&w->sem,
+ 0);
+ if (0 != pthread_create (&w->pt,
+ NULL,
+ &worker,
+ w))
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "pthread_create");
+ GNUNET_free (w);
+ return GNUNET_SYSERR;
+ }
+ workers++;
+ return GNUNET_OK;
+}
+
+
+/**
+ * Stop all worker threads.
+ */
+static void
+stop_workers (void)
+{
+ while (workers > 0)
+ {
+ struct Worker *w;
+ void *result;
+
+ sem_down (&worker_sem);
+ GNUNET_assert (0 == pthread_mutex_lock (&worker_lock));
+ w = worker_head;
+ GNUNET_CONTAINER_DLL_remove (worker_head,
+ worker_tail,
+ w);
+ GNUNET_assert (0 == pthread_mutex_unlock (&worker_lock));
+ w->do_shutdown = true;
+ sem_up (&w->sem);
+ pthread_join (w->pt,
+ &result);
+ GNUNET_assert (result == w);
+ sem_done (&w->sem);
+ GNUNET_free (w);
+ workers--;
+ }
+}
+
+
+/**
+ * Initialize key material for denomination key @a dk (also on disk).
+ *
+ * @param[in,out] dk denomination key to compute key material for
+ * @param position where in the DLL will the @a dk go
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+setup_key (struct DenominationKey *dk,
+ struct DenominationKey *position)
+{
+ struct Denomination *denom = dk->denom;
+ struct GNUNET_CRYPTO_CsPrivateKey priv;
+ struct GNUNET_CRYPTO_CsPublicKey pub;
+
+ GNUNET_CRYPTO_cs_private_key_generate (&priv);
+ GNUNET_CRYPTO_cs_private_key_get_public (&priv,
+ &pub);
+ GNUNET_CRYPTO_hash (&pub,
+ sizeof (pub),
+ &dk->h_cs.hash);
+ GNUNET_asprintf (&dk->filename,
+ "%s/%s/%llu",
+ keydir,
+ denom->section,
+ (unsigned long long) (dk->anchor.abs_time.abs_value_us
+ / GNUNET_TIME_UNIT_SECONDS.rel_value_us
+ ));
+ if (GNUNET_OK !=
+ GNUNET_DISK_fn_write (dk->filename,
+ &priv,
+ sizeof(priv),
+ GNUNET_DISK_PERM_USER_READ))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "write",
+ dk->filename);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Setup fresh private key %s at %s in `%s' (generation #%llu)\n",
+ GNUNET_h2s (&dk->h_cs.hash),
+ GNUNET_TIME_timestamp2s (dk->anchor),
+ dk->filename,
+ (unsigned long long) key_gen);
+ dk->denom_priv = priv;
+ dk->denom_pub = pub;
+ dk->key_gen = key_gen;
+ generate_response (dk);
+ if (GNUNET_OK !=
+ GNUNET_CONTAINER_multihashmap_put (
+ keys,
+ &dk->h_cs.hash,
+ dk,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Duplicate private key created! Terminating.\n");
+ GNUNET_free (dk->filename);
+ GNUNET_free (dk->an);
+ GNUNET_free (dk);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_CONTAINER_DLL_insert_after (denom->keys_head,
+ denom->keys_tail,
+ position,
+ dk);
+ return GNUNET_OK;
+}
+
+
+/**
+ * The withdraw period of a key @a dk has expired. Purge it.
+ *
+ * @param[in] dk expired denomination key to purge
+ */
+static void
+purge_key (struct DenominationKey *dk)
+{
+ if (dk->purge)
+ return;
+ if (0 != unlink (dk->filename))
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "unlink",
+ dk->filename);
+ GNUNET_free (dk->filename);
+ dk->purge = true;
+ dk->key_gen = key_gen;
+}
+
+
+/**
+ * A @a client informs us that a key has been revoked.
+ * Check if the key is still in use, and if so replace (!)
+ * it with a fresh key.
+ *
+ * @param client the client making the request
+ * @param rr the revocation request
+ */
+static enum GNUNET_GenericReturnValue
+handle_revoke_request (struct TES_Client *client,
+ const struct TALER_CRYPTO_CsRevokeRequest *rr)
+{
+ struct DenominationKey *dk;
+ struct DenominationKey *ndk;
+ struct Denomination *denom;
+
+ (void) client;
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ dk = GNUNET_CONTAINER_multihashmap_get (keys,
+ &rr->h_cs.hash);
+ if (NULL == dk)
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Revocation request ignored, denomination key %s unknown\n",
+ GNUNET_h2s (&rr->h_cs.hash));
+ return GNUNET_OK;
+ }
+ if (dk->purge)
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Revocation request ignored, denomination key %s already revoked\n",
+ GNUNET_h2s (&rr->h_cs.hash));
+ return GNUNET_OK;
+ }
+
+ key_gen++;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Revoking key %s, bumping generation to %llu\n",
+ GNUNET_h2s (&rr->h_cs.hash),
+ (unsigned long long) key_gen);
+ purge_key (dk);
+
+ /* Setup replacement key */
+ denom = dk->denom;
+ ndk = GNUNET_new (struct DenominationKey);
+ ndk->denom = denom;
+ ndk->anchor = dk->anchor;
+ if (GNUNET_OK !=
+ setup_key (ndk,
+ dk))
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ globals->global_ret = EXIT_FAILURE;
+ return GNUNET_SYSERR;
+ }
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ TES_wake_clients ();
+ return GNUNET_OK;
+}
+
+
+/**
+ * Handle @a client request @a rdr to create signature. Create the
+ * signature using the respective key and return the result to
+ * the client.
+ *
+ * @param client the client making the request
+ * @param rdr the request details
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_r_derive_request (struct TES_Client *client,
+ const struct TALER_CRYPTO_CsRDeriveRequest *rdr)
+{
+ struct GNUNET_CRYPTO_CSPublicRPairP r_pub;
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+ enum TALER_ErrorCode ec;
+ enum GNUNET_GenericReturnValue ret;
+
+ ec = do_derive (&rdr->h_cs,
+ &rdr->nonce,
+ (0 != ntohl (rdr->for_melt)),
+ &r_pub);
+ if (TALER_EC_NONE != ec)
+ {
+ return fail_derive (client,
+ ec);
+ }
+
+ ret = send_derivation (client,
+ &r_pub);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Sent CS Derived R after %s\n",
+ GNUNET_TIME_relative2s (
+ GNUNET_TIME_absolute_get_duration (now),
+ GNUNET_YES));
+ return ret;
+}
+
+
+/**
+ * Handle @a hdr message received from @a client.
+ *
+ * @param client the client that received the message
+ * @param hdr message that was received
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+cs_work_dispatch (struct TES_Client *client,
+ const struct GNUNET_MessageHeader *hdr)
+{
+ uint16_t msize = ntohs (hdr->size);
+
+ switch (ntohs (hdr->type))
+ {
+ case TALER_HELPER_CS_MT_REQ_SIGN:
+ if (msize < sizeof (struct TALER_CRYPTO_CsSignRequestMessage))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return handle_sign_request (
+ client,
+ (const struct TALER_CRYPTO_CsSignRequestMessage *) hdr);
+ case TALER_HELPER_CS_MT_REQ_REVOKE:
+ if (msize != sizeof (struct TALER_CRYPTO_CsRevokeRequest))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return handle_revoke_request (
+ client,
+ (const struct TALER_CRYPTO_CsRevokeRequest *) hdr);
+ case TALER_HELPER_CS_MT_REQ_BATCH_SIGN:
+ if (msize <= sizeof (struct TALER_CRYPTO_BatchSignRequest))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return handle_batch_sign_request (
+ client,
+ (const struct TALER_CRYPTO_BatchSignRequest *) hdr);
+ case TALER_HELPER_CS_MT_REQ_BATCH_RDERIVE:
+ if (msize <= sizeof (struct TALER_CRYPTO_BatchDeriveRequest))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return handle_batch_derive_request (
+ client,
+ (const struct TALER_CRYPTO_BatchDeriveRequest *) hdr);
+ case TALER_HELPER_CS_MT_REQ_RDERIVE:
+ if (msize != sizeof (struct TALER_CRYPTO_CsRDeriveRequest))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return handle_r_derive_request (client,
+ (const struct
+ TALER_CRYPTO_CsRDeriveRequest *) hdr);
+ default:
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+}
+
+
+/**
+ * Send our initial key set to @a client together with the
+ * "sync" terminator.
+ *
+ * @param client the client to inform
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+cs_client_init (struct TES_Client *client)
+{
+ size_t obs = 0;
+ char *buf;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Initializing new client %p\n",
+ client);
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ for (struct Denomination *denom = denom_head;
+ NULL != denom;
+ denom = denom->next)
+ {
+ for (struct DenominationKey *dk = denom->keys_head;
+ NULL != dk;
+ dk = dk->next)
+ {
+ obs += ntohs (dk->an->header.size);
+ }
+ }
+ buf = GNUNET_malloc (obs);
+ obs = 0;
+ for (struct Denomination *denom = denom_head;
+ NULL != denom;
+ denom = denom->next)
+ {
+ for (struct DenominationKey *dk = denom->keys_head;
+ NULL != dk;
+ dk = dk->next)
+ {
+ GNUNET_memcpy (&buf[obs],
+ dk->an,
+ ntohs (dk->an->header.size));
+ obs += ntohs (dk->an->header.size);
+ }
+ }
+ client->key_gen = key_gen;
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ if (GNUNET_OK !=
+ TES_transmit_raw (client->csock,
+ obs,
+ buf))
+ {
+ GNUNET_free (buf);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Client %p must have disconnected\n",
+ client);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (buf);
+ {
+ struct GNUNET_MessageHeader synced = {
+ .type = htons (TALER_HELPER_CS_SYNCED),
+ .size = htons (sizeof (synced))
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Sending CS SYNCED message to %p\n",
+ client);
+ if (GNUNET_OK !=
+ TES_transmit (client->csock,
+ &synced))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Notify @a client about all changes to the keys since
+ * the last generation known to the @a client.
+ *
+ * @param client the client to notify
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+cs_update_client_keys (struct TES_Client *client)
+{
+ size_t obs = 0;
+ char *buf;
+ enum GNUNET_GenericReturnValue ret;
+
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ for (struct Denomination *denom = denom_head;
+ NULL != denom;
+ denom = denom->next)
+ {
+ for (struct DenominationKey *key = denom->keys_head;
+ NULL != key;
+ key = key->next)
+ {
+ if (key->key_gen <= client->key_gen)
+ continue;
+ if (key->purge)
+ obs += sizeof (struct TALER_CRYPTO_CsKeyPurgeNotification);
+ else
+ obs += ntohs (key->an->header.size);
+ }
+ }
+ if (0 == obs)
+ {
+ /* nothing to do */
+ client->key_gen = key_gen;
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ return GNUNET_OK;
+ }
+ buf = GNUNET_malloc (obs);
+ obs = 0;
+ for (struct Denomination *denom = denom_head;
+ NULL != denom;
+ denom = denom->next)
+ {
+ for (struct DenominationKey *key = denom->keys_head;
+ NULL != key;
+ key = key->next)
+ {
+ if (key->key_gen <= client->key_gen)
+ continue;
+ if (key->purge)
+ {
+ struct TALER_CRYPTO_CsKeyPurgeNotification pn = {
+ .header.type = htons (TALER_HELPER_CS_MT_PURGE),
+ .header.size = htons (sizeof (pn)),
+ .h_cs = key->h_cs
+ };
+
+ GNUNET_memcpy (&buf[obs],
+ &pn,
+ sizeof (pn));
+ GNUNET_assert (obs + sizeof (pn)
+ > obs);
+ obs += sizeof (pn);
+ }
+ else
+ {
+ GNUNET_memcpy (&buf[obs],
+ key->an,
+ ntohs (key->an->header.size));
+ GNUNET_assert (obs + ntohs (key->an->header.size)
+ > obs);
+ obs += ntohs (key->an->header.size);
+ }
+ }
+ }
+ client->key_gen = key_gen;
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ ret = TES_transmit_raw (client->csock,
+ obs,
+ buf);
+ GNUNET_free (buf);
+ return ret;
+}
+
+
+/**
+ * Create a new denomination key (we do not have enough).
+ *
+ * @param denom denomination key to create
+ * @param now current time to use (to get many keys to use the exact same time)
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+create_key (struct Denomination *denom,
+ struct GNUNET_TIME_Timestamp now)
+{
+ struct DenominationKey *dk;
+ struct GNUNET_TIME_Timestamp anchor;
+
+ anchor = now;
+ if (NULL != denom->keys_tail)
+ {
+ struct GNUNET_TIME_Absolute abs;
+
+ abs = GNUNET_TIME_absolute_add (denom->keys_tail->anchor.abs_time,
+ GNUNET_TIME_relative_subtract (
+ denom->duration_withdraw,
+ overlap_duration));
+ if (GNUNET_TIME_absolute_cmp (now.abs_time, <, abs))
+ anchor = GNUNET_TIME_absolute_to_timestamp (abs);
+ }
+ dk = GNUNET_new (struct DenominationKey);
+ dk->denom = denom;
+ dk->anchor = anchor;
+ if (GNUNET_OK !=
+ setup_key (dk,
+ denom->keys_tail))
+ {
+ GNUNET_break (0);
+ GNUNET_free (dk);
+ GNUNET_SCHEDULER_shutdown ();
+ globals->global_ret = EXIT_FAILURE;
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * 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 denom denomination to compute action time for
+ */
+static struct GNUNET_TIME_Absolute
+denomination_action_time (const struct Denomination *denom)
+{
+ struct DenominationKey *head = denom->keys_head;
+ struct DenominationKey *tail = denom->keys_tail;
+ struct GNUNET_TIME_Absolute tt;
+
+ if (NULL == head)
+ return GNUNET_TIME_UNIT_ZERO_ABS;
+ tt = GNUNET_TIME_absolute_subtract (
+ GNUNET_TIME_absolute_subtract (
+ GNUNET_TIME_absolute_add (tail->anchor.abs_time,
+ denom->duration_withdraw),
+ lookahead_sign),
+ overlap_duration);
+ if (head->rc > 0)
+ return tt; /* head expiration does not count due to rc > 0 */
+ return GNUNET_TIME_absolute_min (
+ GNUNET_TIME_absolute_add (head->anchor.abs_time,
+ denom->duration_withdraw),
+ tt);
+}
+
+
+/**
+ * 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
+ * @param now current time to use (to get many keys to use the exact same time)
+ * @param[in,out] wake set to true if we should wake the clients
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+update_keys (struct Denomination *denom,
+ struct GNUNET_TIME_Timestamp now,
+ bool *wake)
+{
+ /* create new denomination keys */
+ if (NULL != denom->keys_tail)
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Updating keys of denomination `%s', last key %s valid for another %s\n",
+ denom->section,
+ GNUNET_h2s (&denom->keys_tail->h_cs.hash),
+ GNUNET_TIME_relative2s (
+ GNUNET_TIME_absolute_get_remaining (
+ GNUNET_TIME_absolute_subtract (
+ GNUNET_TIME_absolute_add (
+ denom->keys_tail->anchor.abs_time,
+ denom->duration_withdraw),
+ overlap_duration)),
+ GNUNET_YES));
+ while ( (NULL == denom->keys_tail) ||
+ GNUNET_TIME_absolute_is_past (
+ GNUNET_TIME_absolute_subtract (
+ GNUNET_TIME_absolute_subtract (
+ GNUNET_TIME_absolute_add (denom->keys_tail->anchor.abs_time,
+ denom->duration_withdraw),
+ lookahead_sign),
+ overlap_duration)) )
+ {
+ if (! *wake)
+ {
+ key_gen++;
+ *wake = true;
+ }
+ if (GNUNET_OK !=
+ create_key (denom,
+ now))
+ {
+ GNUNET_break (0);
+ globals->global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return GNUNET_SYSERR;
+ }
+ }
+ /* remove expired denomination keys */
+ while ( (NULL != denom->keys_head) &&
+ GNUNET_TIME_absolute_is_past
+ (GNUNET_TIME_absolute_add (denom->keys_head->anchor.abs_time,
+ denom->duration_withdraw)) )
+ {
+ struct DenominationKey *key = denom->keys_head;
+ struct DenominationKey *nxt = key->next;
+
+ if (0 != key->rc)
+ break; /* later */
+ GNUNET_CONTAINER_DLL_remove (denom->keys_head,
+ denom->keys_tail,
+ key);
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_remove (
+ keys,
+ &key->h_cs.hash,
+ key));
+ if ( (! key->purge) &&
+ (0 != unlink (key->filename)) )
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "unlink",
+ key->filename);
+ GNUNET_free (key->filename);
+ GNUNET_free (key->an);
+ GNUNET_free (key);
+ key = nxt;
+ }
+
+ /* 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);
+ GNUNET_CONTAINER_DLL_remove (denom_head,
+ denom_tail,
+ denom);
+ before = NULL;
+ for (struct Denomination *pos = denom_head;
+ NULL != pos;
+ pos = pos->next)
+ {
+ if (GNUNET_TIME_absolute_cmp (denomination_action_time (pos), >=, at))
+ break;
+ before = pos;
+ }
+ GNUNET_CONTAINER_DLL_insert_after (denom_head,
+ denom_tail,
+ before,
+ denom);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Task run periodically to expire keys and/or generate fresh ones.
+ *
+ * @param cls NULL
+ */
+static void
+update_denominations (void *cls)
+{
+ struct Denomination *denom;
+ struct GNUNET_TIME_Absolute now;
+ struct GNUNET_TIME_Timestamp t;
+ bool wake = false;
+
+ (void) cls;
+ keygen_task = NULL;
+ now = GNUNET_TIME_absolute_get ();
+ t = GNUNET_TIME_absolute_to_timestamp (now);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Updating denominations ...\n");
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ do {
+ denom = denom_head;
+ if (GNUNET_OK !=
+ update_keys (denom,
+ t,
+ &wake))
+ return;
+ } while (denom != denom_head);
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Updating denominations finished ...\n");
+ if (wake)
+ TES_wake_clients ();
+ keygen_task = GNUNET_SCHEDULER_add_at (denomination_action_time (denom),
+ &update_denominations,
+ NULL);
+}
+
+
+/**
+ * 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 priv key material
+ */
+static void
+parse_key (struct Denomination *denom,
+ const char *filename,
+ const struct GNUNET_CRYPTO_CsPrivateKey *priv)
+{
+ char *anchor_s;
+ char dummy;
+ unsigned long long anchor_ll;
+ struct GNUNET_TIME_Timestamp 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.abs_value_us
+ = anchor_ll * GNUNET_TIME_UNIT_SECONDS.rel_value_us;
+ if (anchor_ll != anchor.abs_time.abs_value_us
+ / GNUNET_TIME_UNIT_SECONDS.rel_value_us)
+ {
+ /* Integer overflow. Bad, invalid filename. */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Filename `%s' invalid for key file, skipping\n",
+ filename);
+ return;
+ }
+ {
+ struct DenominationKey *dk;
+ struct DenominationKey *before;
+
+ dk = GNUNET_new (struct DenominationKey);
+ dk->denom_priv = *priv;
+ dk->denom = denom;
+ dk->anchor = anchor;
+ dk->filename = GNUNET_strdup (filename);
+ GNUNET_CRYPTO_cs_private_key_get_public (priv,
+ &dk->denom_pub);
+ GNUNET_CRYPTO_hash (&dk->denom_pub,
+ sizeof (dk->denom_pub),
+ &dk->h_cs.hash);
+ generate_response (dk);
+ if (GNUNET_OK !=
+ GNUNET_CONTAINER_multihashmap_put (
+ keys,
+ &dk->h_cs.hash,
+ dk,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Duplicate private key %s detected in file `%s'. Skipping.\n",
+ GNUNET_h2s (&dk->h_cs.hash),
+ filename);
+ GNUNET_free (dk->an);
+ GNUNET_free (dk);
+ return;
+ }
+ before = NULL;
+ for (struct DenominationKey *pos = denom->keys_head;
+ NULL != pos;
+ pos = pos->next)
+ {
+ if (GNUNET_TIME_timestamp_cmp (pos->anchor,
+ >,
+ anchor))
+ break;
+ before = pos;
+ }
+ GNUNET_CONTAINER_DLL_insert_after (denom->keys_head,
+ denom->keys_tail,
+ before,
+ dk);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Imported key %s from `%s'\n",
+ GNUNET_h2s (&dk->h_cs.hash),
+ filename);
+ }
+}
+
+
+/**
+ * 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
+ * @return #GNUNET_OK (always, continue to iterate)
+ */
+static enum GNUNET_GenericReturnValue
+import_key (void *cls,
+ const char *filename)
+{
+ struct Denomination *denom = cls;
+ struct GNUNET_DISK_FileHandle *fh;
+ struct GNUNET_DISK_MapHandle *map;
+ void *ptr;
+ int fd;
+ struct stat sbuf;
+
+ {
+ struct stat lsbuf;
+
+ if (0 != lstat (filename,
+ &lsbuf))
+ {
+ GNUNET_log_strerror_file (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_RDONLY | O_CLOEXEC);
+ if (-1 == fd)
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "open",
+ filename);
+ return GNUNET_OK;
+ }
+ if (0 != fstat (fd,
+ &sbuf))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "stat",
+ filename);
+ GNUNET_break (0 == close (fd));
+ 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);
+ GNUNET_break (0 == close (fd));
+ 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_file (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_file (GNUNET_ERROR_TYPE_WARNING,
+ "open",
+ filename);
+ GNUNET_break (0 == close (fd));
+ return GNUNET_OK;
+ }
+ if (sbuf.st_size != sizeof(struct GNUNET_CRYPTO_CsPrivateKey))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "File `%s' too 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_file (GNUNET_ERROR_TYPE_WARNING,
+ "mmap",
+ filename);
+ GNUNET_DISK_file_close (fh);
+ return GNUNET_OK;
+ }
+ parse_key (denom,
+ filename,
+ (const struct GNUNET_CRYPTO_CsPrivateKey *) ptr);
+ GNUNET_DISK_file_unmap (map);
+ GNUNET_DISK_file_close (fh);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse configuration for denomination type parameters. Also determines
+ * our anchor by looking at the existing denominations of the same type.
+ *
+ * @param cfg configuration to use
+ * @param ct section in the configuration file giving the denomination type parameters
+ * @param[out] denom set to the denomination parameters from the configuration
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR if the configuration is invalid
+ */
+static enum GNUNET_GenericReturnValue
+parse_denomination_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *ct,
+ struct Denomination *denom)
+{
+ char *secname;
+
+ GNUNET_asprintf (&secname,
+ "%s-secmod-cs",
+ globals->section);
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ ct,
+ "DURATION_WITHDRAW",
+ &denom->duration_withdraw))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ ct,
+ "DURATION_WITHDRAW");
+ GNUNET_free (secname);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_TIME_relative_cmp (overlap_duration,
+ >=,
+ denom->duration_withdraw))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ secname,
+ "OVERLAP_DURATION",
+ "Value given must be smaller than value for DURATION_WITHDRAW!");
+ GNUNET_free (secname);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (secname);
+ denom->section = GNUNET_strdup (ct);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Closure for #load_denominations.
+ */
+struct LoadContext
+{
+
+ /**
+ * Configuration to use.
+ */
+ const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+ /**
+ * Current time to use.
+ */
+ struct GNUNET_TIME_Timestamp t;
+
+ /**
+ * Status, to be set to #GNUNET_SYSERR on failure
+ */
+ enum GNUNET_GenericReturnValue ret;
+};
+
+
+/**
+ * Generate new denomination signing keys for the denomination type of the given @a
+ * denomination_alias.
+ *
+ * @param cls a `struct LoadContext`, with 'ret' 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)
+{
+ struct LoadContext *ctx = cls;
+ struct Denomination *denom;
+ bool wake = true;
+ char *cipher;
+
+ if ( (0 != strncasecmp (denomination_alias,
+ "coin_",
+ strlen ("coin_"))) &&
+ (0 != strncasecmp (denomination_alias,
+ "coin-",
+ strlen ("coin-"))) )
+ return; /* not a denomination type definition */
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (ctx->cfg,
+ denomination_alias,
+ "CIPHER",
+ &cipher))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ denomination_alias,
+ "CIPHER");
+ return;
+ }
+ if (0 != strcmp (cipher, "CS"))
+ {
+ GNUNET_free (cipher);
+ return; /* Ignore denominations of other types than CS*/
+ }
+ GNUNET_free (cipher);
+
+ denom = GNUNET_new (struct Denomination);
+ if (GNUNET_OK !=
+ parse_denomination_cfg (ctx->cfg,
+ denomination_alias,
+ denom))
+ {
+ ctx->ret = GNUNET_SYSERR;
+ GNUNET_free (denom);
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Loading keys for denomination %s\n",
+ denom->section);
+ {
+ char *dname;
+
+ GNUNET_asprintf (&dname,
+ "%s/%s",
+ keydir,
+ denom->section);
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_DISK_directory_create (dname));
+ GNUNET_DISK_directory_scan (dname,
+ &import_key,
+ denom);
+ GNUNET_free (dname);
+ }
+ GNUNET_CONTAINER_DLL_insert (denom_head,
+ denom_tail,
+ denom);
+ update_keys (denom,
+ ctx->t,
+ &wake);
+}
+
+
+/**
+ * Load the various duration values from @a cfg
+ *
+ * @param cfg configuration to use
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+load_durations (const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ char *secname;
+
+ GNUNET_asprintf (&secname,
+ "%s-secmod-cs",
+ globals->section);
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ secname,
+ "OVERLAP_DURATION",
+ &overlap_duration))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ secname,
+ "OVERLAP_DURATION");
+ GNUNET_free (secname);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ secname,
+ "LOOKAHEAD_SIGN",
+ &lookahead_sign))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ secname,
+ "LOOKAHEAD_SIGN");
+ GNUNET_free (secname);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (secname);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function run on shutdown. Stops the various jobs (nicely).
+ *
+ * @param cls a `struct TALER_SECMOD_Options`
+ */
+static void
+do_shutdown (void *cls)
+{
+ (void) cls;
+ TES_listen_stop ();
+ if (NULL != keygen_task)
+ {
+ GNUNET_SCHEDULER_cancel (keygen_task);
+ keygen_task = NULL;
+ }
+ stop_workers ();
+ sem_done (&worker_sem);
+}
+
+
+void
+TALER_SECMOD_cs_run (void *cls,
+ char *const *args,
+ const char *cfgfile,
+ const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ static struct TES_Callbacks cb = {
+ .dispatch = &cs_work_dispatch,
+ .updater = &cs_update_client_keys,
+ .init = &cs_client_init
+ };
+ struct TALER_SECMOD_Options *opt = cls;
+ char *secname;
+
+ (void) args;
+ (void) cfgfile;
+ globals = opt;
+ if (GNUNET_TIME_timestamp_cmp (opt->global_now,
+ !=,
+ opt->global_now_tmp))
+ {
+ /* The user gave "--now", use it! */
+ opt->global_now = opt->global_now_tmp;
+ }
+ else
+ {
+ /* get current time again, we may be timetraveling! */
+ opt->global_now = GNUNET_TIME_timestamp_get ();
+ }
+ GNUNET_asprintf (&secname,
+ "%s-secmod-cs",
+ opt->section);
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_filename (cfg,
+ secname,
+ "KEY_DIR",
+ &keydir))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ secname,
+ "KEY_DIR");
+ GNUNET_free (secname);
+ opt->global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+ if (GNUNET_OK !=
+ load_durations (cfg))
+ {
+ opt->global_ret = EXIT_NOTCONFIGURED;
+ GNUNET_free (secname);
+ return;
+ }
+ opt->global_ret = TES_listen_start (cfg,
+ secname,
+ &cb);
+ GNUNET_free (secname);
+ if (0 != opt->global_ret)
+ return;
+ sem_init (&worker_sem,
+ 0);
+ GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
+ opt);
+ if (0 == opt->max_workers)
+ {
+ long lret;
+
+ lret = sysconf (_SC_NPROCESSORS_CONF);
+ if (lret <= 0)
+ lret = 1;
+ opt->max_workers = (unsigned int) lret;
+ }
+ for (unsigned int i = 0; i<opt->max_workers; i++)
+ if (GNUNET_OK !=
+ start_worker ())
+ {
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ /* Load denominations */
+ keys = GNUNET_CONTAINER_multihashmap_create (65536,
+ true);
+ {
+ struct LoadContext lc = {
+ .cfg = cfg,
+ .ret = GNUNET_OK,
+ .t = opt->global_now
+ };
+
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ GNUNET_CONFIGURATION_iterate_sections (cfg,
+ &load_denominations,
+ &lc);
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ if (GNUNET_OK != lc.ret)
+ {
+ opt->global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ }
+ if (NULL == denom_head)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "No CS denominations configured\n");
+ TES_wake_clients ();
+ return;
+ }
+ /* start job to keep keys up-to-date; MUST be run before the #listen_task,
+ hence with priority. */
+ keygen_task = GNUNET_SCHEDULER_add_with_priority (
+ GNUNET_SCHEDULER_PRIORITY_URGENT,
+ &update_denominations,
+ NULL);
+}
diff --git a/src/util/secmod_eddsa.c b/src/util/secmod_eddsa.c
new file mode 100644
index 000000000..9661534a8
--- /dev/null
+++ b/src/util/secmod_eddsa.c
@@ -0,0 +1,1130 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2021 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/secmod_eddsa.c
+ * @brief Standalone process to perform private key EDDSA operations
+ * @author Christian Grothoff
+ *
+ * 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,
+ * one per client.
+ * - 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).
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler-exchange-secmod-eddsa.h"
+#include <gcrypt.h>
+#include <pthread.h>
+#include "taler_error_codes.h"
+#include "taler_signatures.h"
+#include "secmod_common.h"
+#include <poll.h>
+
+
+/**
+ * One particular key.
+ */
+struct Key
+{
+
+ /**
+ * Kept in a DLL. Sorted by anchor time.
+ */
+ struct Key *next;
+
+ /**
+ * Kept in a DLL. Sorted by anchor time.
+ */
+ struct Key *prev;
+
+ /**
+ * Name of the file this key is stored under.
+ */
+ char *filename;
+
+ /**
+ * The private key.
+ */
+ struct TALER_ExchangePrivateKeyP exchange_priv;
+
+ /**
+ * The public key.
+ */
+ struct TALER_ExchangePublicKeyP exchange_pub;
+
+ /**
+ * Time at which this key is supposed to become valid.
+ */
+ struct GNUNET_TIME_Timestamp anchor;
+
+ /**
+ * Generation when this key was created or revoked.
+ */
+ uint64_t key_gen;
+
+ /**
+ * 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;
+
+};
+
+
+/**
+ * Head of DLL of actual keys, sorted by anchor.
+ */
+static struct Key *keys_head;
+
+/**
+ * Tail of DLL of actual keys.
+ */
+static struct Key *keys_tail;
+
+/**
+ * How long can a key be used?
+ */
+static struct GNUNET_TIME_Relative duration;
+
+/**
+ * Command-line options for various TALER_SECMOD_XXX_run() functions.
+ */
+static struct TALER_SECMOD_Options *globals;
+
+/**
+ * Where do we store the keys?
+ */
+static char *keydir;
+
+/**
+ * How much should coin creation duration overlap
+ * with the next key? Basically, the starting time of two
+ * keys is always #duration - #overlap_duration apart.
+ */
+static struct GNUNET_TIME_Relative overlap_duration;
+
+/**
+ * How long into the future do we pre-generate keys?
+ */
+static struct GNUNET_TIME_Relative lookahead_sign;
+
+/**
+ * Task run to generate new keys.
+ */
+static struct GNUNET_SCHEDULER_Task *keygen_task;
+
+/**
+ * Lock for the keys queue.
+ */
+static pthread_mutex_t keys_lock;
+
+/**
+ * Current key generation.
+ */
+static uint64_t key_gen;
+
+
+/**
+ * Notify @a client about @a key becoming available.
+ *
+ * @param[in,out] client the client to notify; possible freed if transmission fails
+ * @param key the key to notify @a client about
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+notify_client_key_add (struct TES_Client *client,
+ const struct Key *key)
+{
+ struct TALER_CRYPTO_EddsaKeyAvailableNotification an = {
+ .header.size = htons (sizeof (an)),
+ .header.type = htons (TALER_HELPER_EDDSA_MT_AVAIL),
+ .anchor_time = GNUNET_TIME_timestamp_hton (key->anchor),
+ .duration = GNUNET_TIME_relative_hton (duration),
+ .exchange_pub = key->exchange_pub,
+ .secm_pub = TES_smpub
+ };
+
+ TALER_exchange_secmod_eddsa_sign (&key->exchange_pub,
+ key->anchor,
+ duration,
+ &TES_smpriv,
+ &an.secm_sig);
+ if (GNUNET_OK !=
+ TES_transmit (client->csock,
+ &an.header))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Client %p must have disconnected\n",
+ client);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Notify @a client about @a key being purged.
+ *
+ * @param[in,out] client the client to notify; possible freed if transmission fails
+ * @param key the key to notify @a client about
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+notify_client_key_del (struct TES_Client *client,
+ const struct Key *key)
+{
+ struct TALER_CRYPTO_EddsaKeyPurgeNotification pn = {
+ .header.type = htons (TALER_HELPER_EDDSA_MT_PURGE),
+ .header.size = htons (sizeof (pn)),
+ .exchange_pub = key->exchange_pub
+ };
+
+ if (GNUNET_OK !=
+ TES_transmit (client->csock,
+ &pn.header))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Client %p must have disconnected\n",
+ client);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Handle @a client request @a sr to create signature. Create the
+ * signature using the respective key and return the result to
+ * the client.
+ *
+ * @param client the client making the request
+ * @param sr the request details
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_sign_request (struct TES_Client *client,
+ const struct TALER_CRYPTO_EddsaSignRequest *sr)
+{
+ const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose = &sr->purpose;
+ size_t purpose_size = ntohs (sr->header.size) - sizeof (*sr)
+ + sizeof (*purpose);
+ struct Key *key;
+ struct TALER_CRYPTO_EddsaSignResponse sres = {
+ .header.size = htons (sizeof (sres)),
+ .header.type = htons (TALER_HELPER_EDDSA_MT_RES_SIGNATURE)
+ };
+ enum TALER_ErrorCode ec;
+
+ if (purpose_size != htonl (purpose->size))
+ {
+ struct TALER_CRYPTO_EddsaSignFailure sf = {
+ .header.size = htons (sizeof (sr)),
+ .header.type = htons (TALER_HELPER_EDDSA_MT_RES_SIGN_FAILURE),
+ .ec = htonl (TALER_EC_GENERIC_PARAMETER_MALFORMED)
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Signing request failed, request malformed\n");
+ return TES_transmit (client->csock,
+ &sf.header);
+ }
+
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ key = keys_head;
+ while ( (NULL != key) &&
+ (GNUNET_TIME_absolute_is_past (
+ GNUNET_TIME_absolute_add (key->anchor.abs_time,
+ duration))) )
+ {
+ struct Key *nxt = key->next;
+
+ if (0 != key->rc)
+ break; /* do later */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Deleting past key %s (expired %s ago)\n",
+ TALER_B2S (&nxt->exchange_pub),
+ GNUNET_TIME_relative2s (
+ GNUNET_TIME_absolute_get_duration (
+ GNUNET_TIME_absolute_add (key->anchor.abs_time,
+ duration)),
+ GNUNET_YES));
+ GNUNET_CONTAINER_DLL_remove (keys_head,
+ keys_tail,
+ key);
+ if ( (! key->purge) &&
+ (0 != unlink (key->filename)) )
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "unlink",
+ key->filename);
+ GNUNET_free (key->filename);
+ GNUNET_free (key);
+ key = nxt;
+ }
+ if (NULL == key)
+ {
+ GNUNET_break (0);
+ ec = TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING;
+ }
+ else
+ {
+ GNUNET_assert (key->rc < UINT_MAX);
+ key->rc++;
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_sign_ (&key->exchange_priv.eddsa_priv,
+ purpose,
+ &sres.exchange_sig.eddsa_signature))
+ ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ else
+ ec = TALER_EC_NONE;
+ sres.exchange_pub = key->exchange_pub;
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ GNUNET_assert (key->rc > 0);
+ key->rc--;
+ }
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ if (TALER_EC_NONE != ec)
+ {
+ struct TALER_CRYPTO_EddsaSignFailure sf = {
+ .header.size = htons (sizeof (sf)),
+ .header.type = htons (TALER_HELPER_EDDSA_MT_RES_SIGN_FAILURE),
+ .ec = htonl ((uint32_t) ec)
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Signing request %p failed, worker failed to produce signature\n",
+ client);
+ return TES_transmit (client->csock,
+ &sf.header);
+ }
+ return TES_transmit (client->csock,
+ &sres.header);
+}
+
+
+/**
+ * Initialize key material for key @a key (also on disk).
+ *
+ * @param[in,out] key to compute key material for
+ * @param position where in the DLL will the @a key go
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+setup_key (struct Key *key,
+ struct Key *position)
+{
+ struct GNUNET_CRYPTO_EddsaPrivateKey priv;
+ struct GNUNET_CRYPTO_EddsaPublicKey pub;
+
+ GNUNET_CRYPTO_eddsa_key_create (&priv);
+ GNUNET_CRYPTO_eddsa_key_get_public (&priv,
+ &pub);
+ GNUNET_asprintf (&key->filename,
+ "%s/%llu",
+ keydir,
+ (unsigned long long) (key->anchor.abs_time.abs_value_us
+ / GNUNET_TIME_UNIT_SECONDS.rel_value_us
+ ));
+ if (GNUNET_OK !=
+ GNUNET_DISK_fn_write (key->filename,
+ &priv,
+ sizeof (priv),
+ GNUNET_DISK_PERM_USER_READ))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "write",
+ key->filename);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Setup fresh private key in `%s'\n",
+ key->filename);
+ key->key_gen = key_gen;
+ key->exchange_priv.eddsa_priv = priv;
+ key->exchange_pub.eddsa_pub = pub;
+ GNUNET_CONTAINER_DLL_insert_after (keys_head,
+ keys_tail,
+ position,
+ key);
+ return GNUNET_OK;
+}
+
+
+/**
+ * The validity period of a key @a key has expired. Purge it.
+ *
+ * @param[in] key expired or revoked key to purge
+ */
+static void
+purge_key (struct Key *key)
+{
+ if (key->purge)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Key %s already purged, skipping\n",
+ TALER_B2S (&key->exchange_pub));
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Purging key %s\n",
+ TALER_B2S (&key->exchange_pub));
+ if (0 != unlink (key->filename))
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "unlink",
+ key->filename);
+ key->purge = true;
+ key->key_gen = key_gen;
+ GNUNET_free (key->filename);
+}
+
+
+/**
+ * A @a client informs us that a key has been revoked.
+ * Check if the key is still in use, and if so replace (!)
+ * it with a fresh key.
+ *
+ * @param client the client making the request
+ * @param rr the revocation request
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_revoke_request (struct TES_Client *client,
+ const struct TALER_CRYPTO_EddsaRevokeRequest *rr)
+{
+ struct Key *key;
+ struct Key *nkey;
+
+ (void) client;
+ key = NULL;
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ for (struct Key *pos = keys_head;
+ NULL != pos;
+ pos = pos->next)
+ if (0 == GNUNET_memcmp (&pos->exchange_pub,
+ &rr->exchange_pub))
+ {
+ key = pos;
+ break;
+ }
+ if (NULL == key)
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Revocation request ignored, key unknown\n");
+ return GNUNET_OK;
+ }
+ if (key->purge)
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Revocation request ignored, key %s already revoked\n",
+ TALER_B2S (&key->exchange_pub));
+ return GNUNET_OK;
+ }
+ key_gen++;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Revoking key %s, bumping generation to %llu\n",
+ TALER_B2S (&key->exchange_pub),
+ (unsigned long long) key_gen);
+ purge_key (key);
+
+ /* Setup replacement key */
+ nkey = GNUNET_new (struct Key);
+ nkey->anchor = key->anchor;
+ if (GNUNET_OK !=
+ setup_key (nkey,
+ key))
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ globals->global_ret = EXIT_FAILURE;
+ return GNUNET_SYSERR;
+ }
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ TES_wake_clients ();
+ return GNUNET_OK;
+}
+
+
+/**
+ * Handle @a hdr message received from @a client.
+ *
+ * @param client the client that received the message
+ * @param hdr message that was received
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+eddsa_work_dispatch (struct TES_Client *client,
+ const struct GNUNET_MessageHeader *hdr)
+{
+ uint16_t msize = ntohs (hdr->size);
+
+ switch (ntohs (hdr->type))
+ {
+ case TALER_HELPER_EDDSA_MT_REQ_SIGN:
+ if (msize < sizeof (struct TALER_CRYPTO_EddsaSignRequest))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return handle_sign_request (
+ client,
+ (const struct TALER_CRYPTO_EddsaSignRequest *) hdr);
+ case TALER_HELPER_EDDSA_MT_REQ_REVOKE:
+ if (msize != sizeof (struct TALER_CRYPTO_EddsaRevokeRequest))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return handle_revoke_request (
+ client,
+ (const struct TALER_CRYPTO_EddsaRevokeRequest *) hdr);
+ default:
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+}
+
+
+/**
+ * Send our initial key set to @a client together with the
+ * "sync" terminator.
+ *
+ * @param client the client to inform
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+eddsa_client_init (struct TES_Client *client)
+{
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ for (struct Key *key = keys_head;
+ NULL != key;
+ key = key->next)
+ {
+ if (GNUNET_OK !=
+ notify_client_key_add (client,
+ key))
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ client->key_gen = key_gen;
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ {
+ struct GNUNET_MessageHeader synced = {
+ .type = htons (TALER_HELPER_EDDSA_SYNCED),
+ .size = htons (sizeof (synced))
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Client %p synced\n",
+ client);
+ if (GNUNET_OK !=
+ TES_transmit (client->csock,
+ &synced))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Notify @a client about all changes to the keys since
+ * the last generation known to the @a client.
+ *
+ * @param client the client to notify
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+eddsa_update_client_keys (struct TES_Client *client)
+{
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Updating client %p to generation %llu\n",
+ client,
+ (unsigned long long) key_gen);
+ for (struct Key *key = keys_head;
+ NULL != key;
+ key = key->next)
+ {
+ if (key->key_gen <= client->key_gen)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Skipping key %s, no change since generation %llu\n",
+ TALER_B2S (&key->exchange_pub),
+ (unsigned long long) client->key_gen);
+ continue;
+ }
+ if (key->purge)
+ {
+ if (GNUNET_OK !=
+ notify_client_key_del (client,
+ key))
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ return GNUNET_SYSERR;
+ }
+ }
+ else
+ {
+ if (GNUNET_OK !=
+ notify_client_key_add (client,
+ key))
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ return GNUNET_SYSERR;
+ }
+ }
+ }
+ client->key_gen = key_gen;
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ return GNUNET_OK;
+}
+
+
+/**
+ * Create a new key (we do not have enough).
+ *
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+create_key (void)
+{
+ struct Key *key;
+ struct GNUNET_TIME_Timestamp anchor;
+
+ anchor = GNUNET_TIME_timestamp_get ();
+ if (NULL != keys_tail)
+ {
+ struct GNUNET_TIME_Absolute abs;
+
+ abs = GNUNET_TIME_absolute_add (keys_tail->anchor.abs_time,
+ GNUNET_TIME_relative_subtract (
+ duration,
+ overlap_duration));
+ if (GNUNET_TIME_absolute_cmp (anchor.abs_time,
+ <,
+ abs))
+ anchor = GNUNET_TIME_absolute_to_timestamp (abs);
+ }
+ key = GNUNET_new (struct Key);
+ key->anchor = anchor;
+ if (GNUNET_OK !=
+ setup_key (key,
+ keys_tail))
+ {
+ GNUNET_break (0);
+ GNUNET_free (key);
+ GNUNET_SCHEDULER_shutdown ();
+ globals->global_ret = EXIT_FAILURE;
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * At what time does the current key set require its next action? Basically,
+ * the minimum of the expiration time of the oldest key, and the expiration
+ * time of the newest key minus the #lookahead_sign time.
+ */
+static struct GNUNET_TIME_Absolute
+key_action_time (void)
+{
+ struct Key *nxt;
+
+ nxt = keys_head;
+ while ( (NULL != nxt) &&
+ (nxt->purge) )
+ nxt = nxt->next;
+ if (NULL == nxt)
+ return GNUNET_TIME_UNIT_ZERO_ABS;
+ return GNUNET_TIME_absolute_min (
+ GNUNET_TIME_absolute_add (nxt->anchor.abs_time,
+ duration),
+ GNUNET_TIME_absolute_subtract (
+ GNUNET_TIME_absolute_subtract (
+ GNUNET_TIME_absolute_add (keys_tail->anchor.abs_time,
+ duration),
+ lookahead_sign),
+ overlap_duration));
+}
+
+
+/**
+ * Create new keys and expire ancient keys.
+ *
+ * @param cls NULL
+ */
+static void
+update_keys (void *cls)
+{
+ bool wake = false;
+ struct Key *nxt;
+
+ (void) cls;
+ keygen_task = NULL;
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ /* create new keys */
+ while ( (NULL == keys_tail) ||
+ GNUNET_TIME_absolute_is_past (
+ GNUNET_TIME_absolute_subtract (
+ GNUNET_TIME_absolute_subtract (
+ GNUNET_TIME_absolute_add (keys_tail->anchor.abs_time,
+ duration),
+ lookahead_sign),
+ overlap_duration)) )
+ {
+ if (! wake)
+ {
+ key_gen++;
+ wake = true;
+ }
+ if (GNUNET_OK !=
+ create_key ())
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_break (0);
+ globals->global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ }
+ nxt = keys_head;
+ /* purge expired keys */
+ while ( (NULL != nxt) &&
+ GNUNET_TIME_absolute_is_past (
+ GNUNET_TIME_absolute_add (nxt->anchor.abs_time,
+ duration)))
+ {
+ if (! wake)
+ {
+ key_gen++;
+ wake = true;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Purging past key %s (expired %s ago)\n",
+ TALER_B2S (&nxt->exchange_pub),
+ GNUNET_TIME_relative2s (
+ GNUNET_TIME_absolute_get_duration (
+ GNUNET_TIME_absolute_add (nxt->anchor.abs_time,
+ duration)),
+ GNUNET_YES));
+ purge_key (nxt);
+ nxt = nxt->next;
+ }
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ if (wake)
+ TES_wake_clients ();
+ keygen_task = GNUNET_SCHEDULER_add_at (key_action_time (),
+ &update_keys,
+ NULL);
+}
+
+
+/**
+ * Parse private key from @a filename in @a buf.
+ *
+ * @param filename name of the file we are parsing, for logging
+ * @param buf key material
+ * @param buf_size number of bytes in @a buf
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_key (const char *filename,
+ const void *buf,
+ size_t buf_size)
+{
+ struct GNUNET_CRYPTO_EddsaPrivateKey priv;
+ char *anchor_s;
+ char dummy;
+ unsigned long long anchor_ll;
+ struct GNUNET_TIME_Timestamp 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 GNUNET_SYSERR;
+ }
+ 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 GNUNET_SYSERR;
+ }
+ anchor.abs_time.abs_value_us = anchor_ll
+ * GNUNET_TIME_UNIT_SECONDS.rel_value_us;
+ if (anchor_ll != anchor.abs_time.abs_value_us
+ / GNUNET_TIME_UNIT_SECONDS.rel_value_us)
+ {
+ /* Integer overflow. Bad, invalid filename. */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Filename `%s' invalid for key file, skipping\n",
+ filename);
+ return GNUNET_SYSERR;
+ }
+ if (buf_size != sizeof (priv))
+ {
+ /* Parser failure. */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "File `%s' is malformed, skipping\n",
+ filename);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_memcpy (&priv,
+ buf,
+ buf_size);
+
+ {
+ struct GNUNET_CRYPTO_EddsaPublicKey pub;
+ struct Key *key;
+ struct Key *before;
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&priv,
+ &pub);
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ key = GNUNET_new (struct Key);
+ key->exchange_priv.eddsa_priv = priv;
+ key->exchange_pub.eddsa_pub = pub;
+ key->anchor = anchor;
+ key->filename = GNUNET_strdup (filename);
+ key->key_gen = key_gen;
+ before = NULL;
+ for (struct Key *pos = keys_head;
+ NULL != pos;
+ pos = pos->next)
+ {
+ if (GNUNET_TIME_timestamp_cmp (pos->anchor, >, anchor))
+ break;
+ before = pos;
+ }
+ GNUNET_CONTAINER_DLL_insert_after (keys_head,
+ keys_tail,
+ before,
+ key);
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Imported key from `%s'\n",
+ filename);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Import a private key from @a filename.
+ *
+ * @param cls NULL
+ * @param filename name of a file in the directory
+ */
+static enum GNUNET_GenericReturnValue
+import_key (void *cls,
+ const char *filename)
+{
+ struct GNUNET_DISK_FileHandle *fh;
+ struct GNUNET_DISK_MapHandle *map;
+ void *ptr;
+ int fd;
+ struct stat sbuf;
+
+ (void) cls;
+ {
+ struct stat lsbuf;
+
+ if (0 != lstat (filename,
+ &lsbuf))
+ {
+ GNUNET_log_strerror_file (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_RDONLY | O_CLOEXEC);
+ if (-1 == fd)
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "open",
+ filename);
+ return GNUNET_OK;
+ }
+ if (0 != fstat (fd,
+ &sbuf))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "stat",
+ filename);
+ GNUNET_break (0 == close (fd));
+ 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);
+ GNUNET_break (0 == close (fd));
+ 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_file (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_file (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_file (GNUNET_ERROR_TYPE_WARNING,
+ "mmap",
+ filename);
+ GNUNET_DISK_file_close (fh);
+ return GNUNET_OK;
+ }
+ (void) parse_key (filename,
+ ptr,
+ (size_t) sbuf.st_size);
+ GNUNET_DISK_file_unmap (map);
+ GNUNET_DISK_file_close (fh);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Load the various duration values from @a kcfg.
+ *
+ * @param cfg configuration to use
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+load_durations (const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ char *secname;
+
+ GNUNET_asprintf (&secname,
+ "%s-secmod-eddsa",
+ globals->section);
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ secname,
+ "OVERLAP_DURATION",
+ &overlap_duration))
+ {
+ GNUNET_free (secname);
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ secname,
+ "OVERLAP_DURATION");
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ secname,
+ "DURATION",
+ &duration))
+ {
+ GNUNET_free (secname);
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ secname,
+ "DURATION");
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ secname,
+ "LOOKAHEAD_SIGN",
+ &lookahead_sign))
+ {
+ GNUNET_free (secname);
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ secname,
+ "LOOKAHEAD_SIGN");
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (secname);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function run on shutdown. Stops the various jobs (nicely).
+ *
+ * @param cls NULL
+ */
+static void
+do_shutdown (void *cls)
+{
+ (void) cls;
+ TES_listen_stop ();
+ if (NULL != keygen_task)
+ {
+ GNUNET_SCHEDULER_cancel (keygen_task);
+ keygen_task = NULL;
+ }
+}
+
+
+void
+TALER_SECMOD_eddsa_run (void *cls,
+ char *const *args,
+ const char *cfgfile,
+ const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ static struct TES_Callbacks cb = {
+ .dispatch = eddsa_work_dispatch,
+ .updater = eddsa_update_client_keys,
+ .init = eddsa_client_init
+ };
+ struct TALER_SECMOD_Options *opt = cls;
+ char *secname;
+
+ (void) args;
+ (void) cfgfile;
+ globals = opt;
+ if (GNUNET_TIME_timestamp_cmp (opt->global_now,
+ !=,
+ opt->global_now_tmp))
+ {
+ /* The user gave "--now", use it! */
+ opt->global_now = opt->global_now_tmp;
+ }
+ else
+ {
+ /* get current time again, we may be timetraveling! */
+ opt->global_now = GNUNET_TIME_timestamp_get ();
+ }
+ GNUNET_asprintf (&secname,
+ "%s-secmod-eddsa",
+ opt->section);
+ if (GNUNET_OK !=
+ load_durations (cfg))
+ {
+ opt->global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_filename (cfg,
+ secname,
+ "KEY_DIR",
+ &keydir))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ secname,
+ "KEY_DIR");
+ GNUNET_free (secname);
+ opt->global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+ GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
+ NULL);
+ opt->global_ret = TES_listen_start (cfg,
+ secname,
+ &cb);
+ if (0 != opt->global_ret)
+ return;
+ /* Load keys */
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_DISK_directory_create (keydir));
+ GNUNET_DISK_directory_scan (keydir,
+ &import_key,
+ NULL);
+ if ( (NULL != keys_head) &&
+ (GNUNET_TIME_absolute_is_future (keys_head->anchor.abs_time)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Existing anchor is in %s the future. Refusing to start\n",
+ GNUNET_TIME_relative2s (
+ GNUNET_TIME_absolute_get_remaining (
+ keys_head->anchor.abs_time),
+ true));
+ opt->global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ /* start job to keep keys up-to-date; MUST be run before the #listen_task,
+ hence with priority. */
+ keygen_task = GNUNET_SCHEDULER_add_with_priority (
+ GNUNET_SCHEDULER_PRIORITY_URGENT,
+ &update_keys,
+ NULL);
+}
diff --git a/src/util/secmod_rsa.c b/src/util/secmod_rsa.c
new file mode 100644
index 000000000..4217cbb33
--- /dev/null
+++ b/src/util/secmod_rsa.c
@@ -0,0 +1,2053 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2024 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-exchange-secmod-rsa.c
+ * @brief Standalone process to perform private key RSA operations
+ * @author Christian Grothoff
+ *
+ * Key design points:
+ * - EVERY thread of the exchange will have its own pair of connections to the
+ * crypto helpers. This way, every thread 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 do the signing in parallel, one per client.
+ * - 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).
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler-exchange-secmod-rsa.h"
+#include <gcrypt.h>
+#include <pthread.h>
+#include "taler_error_codes.h"
+#include "taler_signatures.h"
+#include "secmod_common.h"
+#include <poll.h>
+
+
+/**
+ * Information we keep per denomination.
+ */
+struct Denomination;
+
+
+/**
+ * One particular denomination key.
+ */
+struct DenominationKey
+{
+
+ /**
+ * Kept in a DLL of the respective denomination. Sorted by anchor time.
+ */
+ struct DenominationKey *next;
+
+ /**
+ * Kept in a DLL of the respective denomination. Sorted by anchor time.
+ */
+ struct DenominationKey *prev;
+
+ /**
+ * Denomination this key belongs to.
+ */
+ struct Denomination *denom;
+
+ /**
+ * Name of the file this key is stored under.
+ */
+ char *filename;
+
+ /**
+ * The private key of the denomination.
+ */
+ struct GNUNET_CRYPTO_RsaPrivateKey *denom_priv;
+
+ /**
+ * The public key of the denomination.
+ */
+ struct GNUNET_CRYPTO_RsaPublicKey *denom_pub;
+
+ /**
+ * Message to transmit to clients to introduce this public key.
+ */
+ struct TALER_CRYPTO_RsaKeyAvailableNotification *an;
+
+ /**
+ * Hash of this denomination's public key.
+ */
+ struct TALER_RsaPubHashP h_rsa;
+
+ /**
+ * Time at which this key is supposed to become valid.
+ */
+ struct GNUNET_TIME_Timestamp anchor;
+
+ /**
+ * Generation when this key was created or revoked.
+ */
+ uint64_t key_gen;
+
+ /**
+ * 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;
+
+};
+
+
+struct Denomination
+{
+
+ /**
+ * Kept in a DLL. Sorted by #denomination_action_time().
+ */
+ struct Denomination *next;
+
+ /**
+ * Kept in a DLL. Sorted by #denomination_action_time().
+ */
+ struct Denomination *prev;
+
+ /**
+ * Head of DLL of actual keys of this denomination.
+ */
+ struct DenominationKey *keys_head;
+
+ /**
+ * Tail of DLL of actual keys of this denomination.
+ */
+ struct DenominationKey *keys_tail;
+
+ /**
+ * 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
+ * anonymity set.
+ */
+ struct GNUNET_TIME_Relative duration_withdraw;
+
+ /**
+ * What is the configuration section of this denomination type? Also used
+ * for the directory name where the denomination keys are stored.
+ */
+ char *section;
+
+ /**
+ * Length of (new) RSA keys (in bits).
+ */
+ uint32_t rsa_keysize;
+};
+
+
+/**
+ * A semaphore.
+ */
+struct Semaphore
+{
+ /**
+ * Mutex for the semaphore.
+ */
+ pthread_mutex_t mutex;
+
+ /**
+ * Condition variable for the semaphore.
+ */
+ pthread_cond_t cv;
+
+ /**
+ * Counter of the semaphore.
+ */
+ unsigned int ctr;
+};
+
+
+/**
+ * Job in a batch sign request.
+ */
+struct BatchJob;
+
+/**
+ * Handle for a thread that does work in batch signing.
+ */
+struct Worker
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct Worker *prev;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct Worker *next;
+
+ /**
+ * Job this worker should do next.
+ */
+ struct BatchJob *job;
+
+ /**
+ * Semaphore to signal the worker that a job is available.
+ */
+ struct Semaphore sem;
+
+ /**
+ * Handle for this thread.
+ */
+ pthread_t pt;
+
+ /**
+ * Set to true if the worker should terminate.
+ */
+ bool do_shutdown;
+};
+
+
+/**
+ * Job in a batch sign request.
+ */
+struct BatchJob
+{
+ /**
+ * Request we are working on.
+ */
+ const struct TALER_CRYPTO_SignRequest *sr;
+
+ /**
+ * Thread doing the work.
+ */
+ struct Worker *worker;
+
+ /**
+ * Result with the signature.
+ */
+ struct GNUNET_CRYPTO_RsaSignature *rsa_signature;
+
+ /**
+ * Semaphore to signal that the job is finished.
+ */
+ struct Semaphore sem;
+
+ /**
+ * Computation status.
+ */
+ enum TALER_ErrorCode ec;
+
+};
+
+
+/**
+ * Head of DLL of workers ready for more work.
+ */
+static struct Worker *worker_head;
+
+/**
+ * Tail of DLL of workers ready for more work.
+ */
+static struct Worker *worker_tail;
+
+/**
+ * Lock for manipulating the worker DLL.
+ */
+static pthread_mutex_t worker_lock;
+
+/**
+ * Total number of workers that were started.
+ */
+static unsigned int workers;
+
+/**
+ * Semaphore used to grab a worker.
+ */
+static struct Semaphore worker_sem;
+
+/**
+ * Command-line options for various TALER_SECMOD_XXX_run() functions.
+ */
+static struct TALER_SECMOD_Options *globals;
+
+/**
+ * Where do we store the keys?
+ */
+static char *keydir;
+
+/**
+ * How much should coin creation (@e duration_withdraw) duration overlap
+ * with the next denomination? Basically, the starting time of two
+ * denominations is always @e duration_withdraw - #overlap_duration apart.
+ */
+static struct GNUNET_TIME_Relative overlap_duration;
+
+/**
+ * How long into the future do we pre-generate keys?
+ */
+static struct GNUNET_TIME_Relative lookahead_sign;
+
+/**
+ * All of our denominations, in a DLL. Sorted?
+ */
+static struct Denomination *denom_head;
+
+/**
+ * All of our denominations, in a DLL. Sorted?
+ */
+static struct Denomination *denom_tail;
+
+/**
+ * Map of hashes of public (RSA) keys to `struct DenominationKey *`
+ * with the respective private keys.
+ */
+static struct GNUNET_CONTAINER_MultiHashMap *keys;
+
+/**
+ * Task run to generate new keys.
+ */
+static struct GNUNET_SCHEDULER_Task *keygen_task;
+
+/**
+ * Lock for the keys queue.
+ */
+static pthread_mutex_t keys_lock;
+
+/**
+ * Current key generation.
+ */
+static uint64_t key_gen;
+
+
+/**
+ * Generate the announcement message for @a dk.
+ *
+ * @param[in,out] dk denomination key to generate the announcement for
+ */
+static void
+generate_response (struct DenominationKey *dk)
+{
+ struct Denomination *denom = dk->denom;
+ size_t nlen = strlen (denom->section) + 1;
+ struct TALER_CRYPTO_RsaKeyAvailableNotification *an;
+ size_t buf_len;
+ void *buf;
+ void *p;
+ size_t tlen;
+
+ buf_len = GNUNET_CRYPTO_rsa_public_key_encode (dk->denom_pub,
+ &buf);
+ GNUNET_assert (buf_len < UINT16_MAX);
+ GNUNET_assert (nlen < UINT16_MAX);
+ tlen = buf_len + nlen + sizeof (*an);
+ GNUNET_assert (tlen < UINT16_MAX);
+ an = GNUNET_malloc (tlen);
+ an->header.size = htons ((uint16_t) tlen);
+ an->header.type = htons (TALER_HELPER_RSA_MT_AVAIL);
+ an->pub_size = htons ((uint16_t) buf_len);
+ an->section_name_len = htons ((uint16_t) nlen);
+ an->anchor_time = GNUNET_TIME_timestamp_hton (dk->anchor);
+ an->duration_withdraw = GNUNET_TIME_relative_hton (denom->duration_withdraw);
+ TALER_exchange_secmod_rsa_sign (&dk->h_rsa,
+ denom->section,
+ dk->anchor,
+ denom->duration_withdraw,
+ &TES_smpriv,
+ &an->secm_sig);
+ an->secm_pub = TES_smpub;
+ p = (void *) &an[1];
+ GNUNET_memcpy (p,
+ buf,
+ buf_len);
+ GNUNET_free (buf);
+ GNUNET_memcpy (p + buf_len,
+ denom->section,
+ nlen);
+ dk->an = an;
+}
+
+
+/**
+ * Do the actual signing work.
+ *
+ * @param h_rsa key to sign with
+ * @param bm blinded message to sign
+ * @param[out] rsa_signaturep set to the RSA signature
+ * @return #TALER_EC_NONE on success
+ */
+static enum TALER_ErrorCode
+do_sign (const struct TALER_RsaPubHashP *h_rsa,
+ const struct GNUNET_CRYPTO_RsaBlindedMessage *bm,
+ struct GNUNET_CRYPTO_RsaSignature **rsa_signaturep)
+{
+ struct DenominationKey *dk;
+ struct GNUNET_CRYPTO_RsaSignature *rsa_signature;
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ dk = GNUNET_CONTAINER_multihashmap_get (keys,
+ &h_rsa->hash);
+ if (NULL == dk)
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Signing request failed, denomination key %s unknown\n",
+ GNUNET_h2s (&h_rsa->hash));
+ return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
+ }
+ if (GNUNET_TIME_absolute_is_future (dk->anchor.abs_time))
+ {
+ /* it is too early */
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Signing request failed, denomination key %s is not yet valid\n",
+ GNUNET_h2s (&h_rsa->hash));
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_TOO_EARLY;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received request to sign over %u bytes with key %s\n",
+ (unsigned int) bm->blinded_msg_size,
+ GNUNET_h2s (&h_rsa->hash));
+ GNUNET_assert (dk->rc < UINT_MAX);
+ dk->rc++;
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ rsa_signature
+ = GNUNET_CRYPTO_rsa_sign_blinded (dk->denom_priv,
+ bm);
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ GNUNET_assert (dk->rc > 0);
+ dk->rc--;
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ if (NULL == rsa_signature)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Signing request failed, worker failed to produce signature\n");
+ return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Sending RSA signature after %s\n",
+ GNUNET_TIME_relative2s (
+ GNUNET_TIME_absolute_get_duration (now),
+ GNUNET_YES));
+ *rsa_signaturep = rsa_signature;
+ return TALER_EC_NONE;
+}
+
+
+/**
+ * Generate error response that signing failed.
+ *
+ * @param client client to send response to
+ * @param ec error code to include
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+fail_sign (struct TES_Client *client,
+ enum TALER_ErrorCode ec)
+{
+ struct TALER_CRYPTO_SignFailure sf = {
+ .header.size = htons (sizeof (sf)),
+ .header.type = htons (TALER_HELPER_RSA_MT_RES_SIGN_FAILURE),
+ .ec = htonl (ec)
+ };
+
+ return TES_transmit (client->csock,
+ &sf.header);
+}
+
+
+/**
+ * Generate signature response.
+ *
+ * @param client client to send response to
+ * @param[in] rsa_signature signature to send, freed by this function
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+send_signature (struct TES_Client *client,
+ struct GNUNET_CRYPTO_RsaSignature *rsa_signature)
+{
+ struct TALER_CRYPTO_SignResponse *sr;
+ void *buf;
+ size_t buf_size;
+ size_t tsize;
+ enum GNUNET_GenericReturnValue ret;
+
+ buf_size = GNUNET_CRYPTO_rsa_signature_encode (rsa_signature,
+ &buf);
+ GNUNET_CRYPTO_rsa_signature_free (rsa_signature);
+ tsize = sizeof (*sr) + buf_size;
+ GNUNET_assert (tsize < UINT16_MAX);
+ sr = GNUNET_malloc (tsize);
+ sr->header.size = htons (tsize);
+ sr->header.type = htons (TALER_HELPER_RSA_MT_RES_SIGNATURE);
+ GNUNET_memcpy (&sr[1],
+ buf,
+ buf_size);
+ GNUNET_free (buf);
+ ret = TES_transmit (client->csock,
+ &sr->header);
+ GNUNET_free (sr);
+ return ret;
+}
+
+
+/**
+ * Handle @a client request @a sr to create signature. Create the
+ * signature using the respective key and return the result to
+ * the client.
+ *
+ * @param client the client making the request
+ * @param sr the request details
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_sign_request (struct TES_Client *client,
+ const struct TALER_CRYPTO_SignRequest *sr)
+{
+ struct GNUNET_CRYPTO_RsaBlindedMessage bm = {
+ .blinded_msg = (void *) &sr[1],
+ .blinded_msg_size = ntohs (sr->header.size) - sizeof (*sr)
+ };
+ struct GNUNET_CRYPTO_RsaSignature *rsa_signature;
+ enum TALER_ErrorCode ec;
+
+ ec = do_sign (&sr->h_rsa,
+ &bm,
+ &rsa_signature);
+ if (TALER_EC_NONE != ec)
+ {
+ return fail_sign (client,
+ ec);
+ }
+ return send_signature (client,
+ rsa_signature);
+}
+
+
+/**
+ * Initialize a semaphore @a sem with a value of @a val.
+ *
+ * @param[out] sem semaphore to initialize
+ * @param val initial value of the semaphore
+ */
+static void
+sem_init (struct Semaphore *sem,
+ unsigned int val)
+{
+ GNUNET_assert (0 ==
+ pthread_mutex_init (&sem->mutex,
+ NULL));
+ GNUNET_assert (0 ==
+ pthread_cond_init (&sem->cv,
+ NULL));
+ sem->ctr = val;
+}
+
+
+/**
+ * Decrement semaphore, blocks until this is possible.
+ *
+ * @param[in,out] sem semaphore to decrement
+ */
+static void
+sem_down (struct Semaphore *sem)
+{
+ GNUNET_assert (0 == pthread_mutex_lock (&sem->mutex));
+ while (0 == sem->ctr)
+ {
+ pthread_cond_wait (&sem->cv,
+ &sem->mutex);
+ }
+ sem->ctr--;
+ GNUNET_assert (0 == pthread_mutex_unlock (&sem->mutex));
+}
+
+
+/**
+ * Increment semaphore, blocks until this is possible.
+ *
+ * @param[in,out] sem semaphore to decrement
+ */
+static void
+sem_up (struct Semaphore *sem)
+{
+ GNUNET_assert (0 == pthread_mutex_lock (&sem->mutex));
+ sem->ctr++;
+ GNUNET_assert (0 == pthread_mutex_unlock (&sem->mutex));
+ pthread_cond_signal (&sem->cv);
+}
+
+
+/**
+ * Release resources used by @a sem.
+ *
+ * @param[in] sem semaphore to release (except the memory itself)
+ */
+static void
+sem_done (struct Semaphore *sem)
+{
+ GNUNET_break (0 == pthread_cond_destroy (&sem->cv));
+ GNUNET_break (0 == pthread_mutex_destroy (&sem->mutex));
+}
+
+
+/**
+ * Main logic of a worker thread. Grabs work, does it,
+ * grabs more work.
+ *
+ * @param cls a `struct Worker *`
+ * @returns cls
+ */
+static void *
+worker (void *cls)
+{
+ struct Worker *w = cls;
+
+ while (true)
+ {
+ GNUNET_assert (0 == pthread_mutex_lock (&worker_lock));
+ GNUNET_CONTAINER_DLL_insert (worker_head,
+ worker_tail,
+ w);
+ GNUNET_assert (0 == pthread_mutex_unlock (&worker_lock));
+ sem_up (&worker_sem);
+ sem_down (&w->sem);
+ if (w->do_shutdown)
+ break;
+ {
+ struct BatchJob *bj = w->job;
+ const struct TALER_CRYPTO_SignRequest *sr = bj->sr;
+ struct GNUNET_CRYPTO_RsaBlindedMessage bm = {
+ .blinded_msg = (void *) &sr[1],
+ .blinded_msg_size = ntohs (sr->header.size) - sizeof (*sr)
+ };
+
+ bj->ec = do_sign (&sr->h_rsa,
+ &bm,
+ &bj->rsa_signature);
+ sem_up (&bj->sem);
+ w->job = NULL;
+ }
+ }
+ return w;
+}
+
+
+/**
+ * Start batch job @a bj to sign @a sr.
+ *
+ * @param sr signature request to answer
+ * @param[out] bj job data structure
+ */
+static void
+start_job (const struct TALER_CRYPTO_SignRequest *sr,
+ struct BatchJob *bj)
+{
+ sem_init (&bj->sem,
+ 0);
+ bj->sr = sr;
+ sem_down (&worker_sem);
+ GNUNET_assert (0 == pthread_mutex_lock (&worker_lock));
+ bj->worker = worker_head;
+ GNUNET_CONTAINER_DLL_remove (worker_head,
+ worker_tail,
+ bj->worker);
+ GNUNET_assert (0 == pthread_mutex_unlock (&worker_lock));
+ bj->worker->job = bj;
+ sem_up (&bj->worker->sem);
+}
+
+
+/**
+ * Finish a job @a bj for a @a client.
+ *
+ * @param client who made the request
+ * @param[in,out] bj job to finish
+ */
+static void
+finish_job (struct TES_Client *client,
+ struct BatchJob *bj)
+{
+ sem_down (&bj->sem);
+ sem_done (&bj->sem);
+ if (TALER_EC_NONE != bj->ec)
+ {
+ fail_sign (client,
+ bj->ec);
+ return;
+ }
+ GNUNET_assert (NULL != bj->rsa_signature);
+ send_signature (client,
+ bj->rsa_signature);
+ bj->rsa_signature = NULL; /* freed in send_signature */
+}
+
+
+/**
+ * Handle @a client request @a sr to create a batch of signature. Creates the
+ * signatures using the respective key and return the results to the client.
+ *
+ * @param client the client making the request
+ * @param bsr the request details
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_batch_sign_request (struct TES_Client *client,
+ const struct TALER_CRYPTO_BatchSignRequest *bsr)
+{
+ uint32_t bs = ntohl (bsr->batch_size);
+ uint16_t size = ntohs (bsr->header.size) - sizeof (*bsr);
+ const void *off = (const void *) &bsr[1];
+ unsigned int idx = 0;
+ struct BatchJob jobs[bs];
+ bool failure = false;
+
+ if (bs > TALER_MAX_FRESH_COINS)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ while ( (bs > 0) &&
+ (size > sizeof (struct TALER_CRYPTO_SignRequest)) )
+ {
+ const struct TALER_CRYPTO_SignRequest *sr = off;
+ uint16_t s = ntohs (sr->header.size);
+
+ if (s > size)
+ {
+ failure = true;
+ bs = idx;
+ break;
+ }
+ start_job (sr,
+ &jobs[idx++]);
+ off += s;
+ size -= s;
+ }
+ GNUNET_break_op (0 == size);
+ bs = GNUNET_MIN (bs,
+ idx);
+ for (unsigned int i = 0; i<bs; i++)
+ finish_job (client,
+ &jobs[i]);
+ if (failure)
+ {
+ struct TALER_CRYPTO_SignFailure sf = {
+ .header.size = htons (sizeof (sf)),
+ .header.type = htons (TALER_HELPER_RSA_MT_RES_BATCH_FAILURE),
+ .ec = htonl (TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE)
+ };
+
+ GNUNET_break (0);
+ return TES_transmit (client->csock,
+ &sf.header);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Start worker thread for batch processing.
+ *
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+start_worker (void)
+{
+ struct Worker *w;
+
+ w = GNUNET_new (struct Worker);
+ sem_init (&w->sem,
+ 0);
+ if (0 != pthread_create (&w->pt,
+ NULL,
+ &worker,
+ w))
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "pthread_create");
+ GNUNET_free (w);
+ return GNUNET_SYSERR;
+ }
+ workers++;
+ return GNUNET_OK;
+}
+
+
+/**
+ * Stop all worker threads.
+ */
+static void
+stop_workers (void)
+{
+ while (workers > 0)
+ {
+ struct Worker *w;
+ void *result;
+
+ sem_down (&worker_sem);
+ GNUNET_assert (0 == pthread_mutex_lock (&worker_lock));
+ w = worker_head;
+ GNUNET_CONTAINER_DLL_remove (worker_head,
+ worker_tail,
+ w);
+ GNUNET_assert (0 == pthread_mutex_unlock (&worker_lock));
+ w->do_shutdown = true;
+ sem_up (&w->sem);
+ pthread_join (w->pt,
+ &result);
+ GNUNET_assert (result == w);
+ sem_done (&w->sem);
+ GNUNET_free (w);
+ workers--;
+ }
+}
+
+
+/**
+ * Initialize key material for denomination key @a dk (also on disk).
+ *
+ * @param[in,out] dk denomination key to compute key material for
+ * @param position where in the DLL will the @a dk go
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+setup_key (struct DenominationKey *dk,
+ struct DenominationKey *position)
+{
+ struct Denomination *denom = dk->denom;
+ struct GNUNET_CRYPTO_RsaPrivateKey *priv;
+ struct GNUNET_CRYPTO_RsaPublicKey *pub;
+ size_t buf_size;
+ void *buf;
+
+ priv = GNUNET_CRYPTO_rsa_private_key_create (denom->rsa_keysize);
+ if (NULL == priv)
+ {
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ globals->global_ret = EXIT_FAILURE;
+ 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);
+ return GNUNET_SYSERR;
+ }
+ buf_size = GNUNET_CRYPTO_rsa_private_key_encode (priv,
+ &buf);
+ GNUNET_CRYPTO_rsa_public_key_hash (pub,
+ &dk->h_rsa.hash);
+ GNUNET_asprintf (&dk->filename,
+ "%s/%s/%llu",
+ keydir,
+ denom->section,
+ (unsigned long long) (dk->anchor.abs_time.abs_value_us
+ / GNUNET_TIME_UNIT_SECONDS.rel_value_us
+ ));
+ if (GNUNET_OK !=
+ 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);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (buf);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Setup fresh private key %s at %s in `%s' (generation #%llu)\n",
+ GNUNET_h2s (&dk->h_rsa.hash),
+ GNUNET_TIME_timestamp2s (dk->anchor),
+ dk->filename,
+ (unsigned long long) key_gen);
+ dk->denom_priv = priv;
+ dk->denom_pub = pub;
+ dk->key_gen = key_gen;
+ generate_response (dk);
+ if (GNUNET_OK !=
+ GNUNET_CONTAINER_multihashmap_put (
+ keys,
+ &dk->h_rsa.hash,
+ dk,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Duplicate private key created! Terminating.\n");
+ GNUNET_CRYPTO_rsa_private_key_free (dk->denom_priv);
+ GNUNET_CRYPTO_rsa_public_key_free (dk->denom_pub);
+ GNUNET_free (dk->filename);
+ GNUNET_free (dk->an);
+ GNUNET_free (dk);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_CONTAINER_DLL_insert_after (denom->keys_head,
+ denom->keys_tail,
+ position,
+ dk);
+ return GNUNET_OK;
+}
+
+
+/**
+ * The withdraw period of a key @a dk has expired. Purge it.
+ *
+ * @param[in] dk expired denomination key to purge
+ */
+static void
+purge_key (struct DenominationKey *dk)
+{
+ if (dk->purge)
+ return;
+ if (0 != unlink (dk->filename))
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "unlink",
+ dk->filename);
+ GNUNET_free (dk->filename);
+ dk->purge = true;
+ dk->key_gen = key_gen;
+}
+
+
+/**
+ * A @a client informs us that a key has been revoked.
+ * Check if the key is still in use, and if so replace (!)
+ * it with a fresh key.
+ *
+ * @param client the client making the request
+ * @param rr the revocation request
+ */
+static enum GNUNET_GenericReturnValue
+handle_revoke_request (struct TES_Client *client,
+ const struct TALER_CRYPTO_RevokeRequest *rr)
+{
+ struct DenominationKey *dk;
+ struct DenominationKey *ndk;
+ struct Denomination *denom;
+
+ (void) client;
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ dk = GNUNET_CONTAINER_multihashmap_get (keys,
+ &rr->h_rsa.hash);
+ if (NULL == dk)
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Revocation request ignored, denomination key %s unknown\n",
+ GNUNET_h2s (&rr->h_rsa.hash));
+ return GNUNET_OK;
+ }
+ if (dk->purge)
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Revocation request ignored, denomination key %s already revoked\n",
+ GNUNET_h2s (&rr->h_rsa.hash));
+ return GNUNET_OK;
+ }
+
+ key_gen++;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Revoking key %s, bumping generation to %llu\n",
+ GNUNET_h2s (&rr->h_rsa.hash),
+ (unsigned long long) key_gen);
+ purge_key (dk);
+
+ /* Setup replacement key */
+ denom = dk->denom;
+ ndk = GNUNET_new (struct DenominationKey);
+ ndk->denom = denom;
+ ndk->anchor = dk->anchor;
+ if (GNUNET_OK !=
+ setup_key (ndk,
+ dk))
+ {
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ globals->global_ret = EXIT_FAILURE;
+ return GNUNET_SYSERR;
+ }
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ TES_wake_clients ();
+ return GNUNET_OK;
+}
+
+
+/**
+ * Handle @a hdr message received from @a client.
+ *
+ * @param client the client that received the message
+ * @param hdr message that was received
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+rsa_work_dispatch (struct TES_Client *client,
+ const struct GNUNET_MessageHeader *hdr)
+{
+ uint16_t msize = ntohs (hdr->size);
+
+ switch (ntohs (hdr->type))
+ {
+ case TALER_HELPER_RSA_MT_REQ_SIGN:
+ if (msize <= sizeof (struct TALER_CRYPTO_SignRequest))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return handle_sign_request (
+ client,
+ (const struct TALER_CRYPTO_SignRequest *) hdr);
+ case TALER_HELPER_RSA_MT_REQ_REVOKE:
+ if (msize != sizeof (struct TALER_CRYPTO_RevokeRequest))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return handle_revoke_request (
+ client,
+ (const struct TALER_CRYPTO_RevokeRequest *) hdr);
+ case TALER_HELPER_RSA_MT_REQ_BATCH_SIGN:
+ if (msize <= sizeof (struct TALER_CRYPTO_BatchSignRequest))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return handle_batch_sign_request (
+ client,
+ (const struct TALER_CRYPTO_BatchSignRequest *) hdr);
+ default:
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+}
+
+
+/**
+ * Send our initial key set to @a client together with the
+ * "sync" terminator.
+ *
+ * @param client the client to inform
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+rsa_client_init (struct TES_Client *client)
+{
+ size_t obs = 0;
+ char *buf;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Initializing new client %p\n",
+ client);
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ for (struct Denomination *denom = denom_head;
+ NULL != denom;
+ denom = denom->next)
+ {
+ for (struct DenominationKey *dk = denom->keys_head;
+ NULL != dk;
+ dk = dk->next)
+ {
+ GNUNET_assert (obs + ntohs (dk->an->header.size)
+ > obs);
+ obs += ntohs (dk->an->header.size);
+ }
+ }
+ buf = GNUNET_malloc (obs);
+ obs = 0;
+ for (struct Denomination *denom = denom_head;
+ NULL != denom;
+ denom = denom->next)
+ {
+ for (struct DenominationKey *dk = denom->keys_head;
+ NULL != dk;
+ dk = dk->next)
+ {
+ GNUNET_memcpy (&buf[obs],
+ dk->an,
+ ntohs (dk->an->header.size));
+ GNUNET_assert (obs + ntohs (dk->an->header.size)
+ > obs);
+ obs += ntohs (dk->an->header.size);
+ }
+ }
+ client->key_gen = key_gen;
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ if (GNUNET_OK !=
+ TES_transmit_raw (client->csock,
+ obs,
+ buf))
+ {
+ GNUNET_free (buf);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Client %p must have disconnected\n",
+ client);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (buf);
+ {
+ struct GNUNET_MessageHeader synced = {
+ .type = htons (TALER_HELPER_RSA_SYNCED),
+ .size = htons (sizeof (synced))
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Sending RSA SYNCED message to %p\n",
+ client);
+ if (GNUNET_OK !=
+ TES_transmit (client->csock,
+ &synced))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Notify @a client about all changes to the keys since
+ * the last generation known to the @a client.
+ *
+ * @param client the client to notify
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+rsa_update_client_keys (struct TES_Client *client)
+{
+ size_t obs = 0;
+ char *buf;
+ enum GNUNET_GenericReturnValue ret;
+
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ for (struct Denomination *denom = denom_head;
+ NULL != denom;
+ denom = denom->next)
+ {
+ for (struct DenominationKey *key = denom->keys_head;
+ NULL != key;
+ key = key->next)
+ {
+ if (key->key_gen <= client->key_gen)
+ continue;
+ if (key->purge)
+ obs += sizeof (struct TALER_CRYPTO_RsaKeyPurgeNotification);
+ else
+ obs += ntohs (key->an->header.size);
+ }
+ }
+ if (0 == obs)
+ {
+ /* nothing to do */
+ client->key_gen = key_gen;
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ return GNUNET_OK;
+ }
+ buf = GNUNET_malloc (obs);
+ obs = 0;
+ for (struct Denomination *denom = denom_head;
+ NULL != denom;
+ denom = denom->next)
+ {
+ for (struct DenominationKey *key = denom->keys_head;
+ NULL != key;
+ key = key->next)
+ {
+ if (key->key_gen <= client->key_gen)
+ continue;
+ if (key->purge)
+ {
+ struct TALER_CRYPTO_RsaKeyPurgeNotification pn = {
+ .header.type = htons (TALER_HELPER_RSA_MT_PURGE),
+ .header.size = htons (sizeof (pn)),
+ .h_rsa = key->h_rsa
+ };
+
+ GNUNET_memcpy (&buf[obs],
+ &pn,
+ sizeof (pn));
+ GNUNET_assert (obs + sizeof (pn)
+ > obs);
+ obs += sizeof (pn);
+ }
+ else
+ {
+ GNUNET_memcpy (&buf[obs],
+ key->an,
+ ntohs (key->an->header.size));
+ GNUNET_assert (obs + ntohs (key->an->header.size)
+ > obs);
+ obs += ntohs (key->an->header.size);
+ }
+ }
+ }
+ client->key_gen = key_gen;
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ ret = TES_transmit_raw (client->csock,
+ obs,
+ buf);
+ GNUNET_free (buf);
+ return ret;
+}
+
+
+/**
+ * Create a new denomination key (we do not have enough).
+ *
+ * @param denom denomination key to create
+ * @param now current time to use (to get many keys to use the exact same time)
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+create_key (struct Denomination *denom,
+ struct GNUNET_TIME_Timestamp now)
+{
+ struct DenominationKey *dk;
+ struct GNUNET_TIME_Timestamp anchor;
+
+ anchor = now;
+ // FIXME: round down to multiple of 'anchor_round' value from configuration
+ if (NULL != denom->keys_tail)
+ {
+ struct GNUNET_TIME_Absolute abs;
+
+ abs = GNUNET_TIME_absolute_add (denom->keys_tail->anchor.abs_time,
+ GNUNET_TIME_relative_subtract (
+ denom->duration_withdraw,
+ overlap_duration));
+ if (GNUNET_TIME_absolute_cmp (now.abs_time, <, abs))
+ anchor = GNUNET_TIME_absolute_to_timestamp (abs);
+ }
+ dk = GNUNET_new (struct DenominationKey);
+ dk->denom = denom;
+ dk->anchor = anchor;
+ if (GNUNET_OK !=
+ setup_key (dk,
+ denom->keys_tail))
+ {
+ GNUNET_break (0);
+ GNUNET_free (dk);
+ GNUNET_SCHEDULER_shutdown ();
+ globals->global_ret = EXIT_FAILURE;
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * 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 denom denomination to compute action time for
+ */
+static struct GNUNET_TIME_Absolute
+denomination_action_time (const struct Denomination *denom)
+{
+ struct DenominationKey *head = denom->keys_head;
+ struct DenominationKey *tail = denom->keys_tail;
+ struct GNUNET_TIME_Absolute tt;
+
+ if (NULL == head)
+ return GNUNET_TIME_UNIT_ZERO_ABS;
+ tt = GNUNET_TIME_absolute_subtract (
+ GNUNET_TIME_absolute_subtract (
+ GNUNET_TIME_absolute_add (tail->anchor.abs_time,
+ denom->duration_withdraw),
+ lookahead_sign),
+ overlap_duration);
+ if (head->rc > 0)
+ return tt; /* head expiration does not count due to rc > 0 */
+ return GNUNET_TIME_absolute_min (
+ GNUNET_TIME_absolute_add (head->anchor.abs_time,
+ denom->duration_withdraw),
+ tt);
+}
+
+
+/**
+ * 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
+ * @param now current time to use (to get many keys to use the exact same time)
+ * @param[in,out] wake set to true if we should wake the clients
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+update_keys (struct Denomination *denom,
+ struct GNUNET_TIME_Timestamp now,
+ bool *wake)
+{
+ /* create new denomination keys */
+ if (NULL != denom->keys_tail)
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Updating keys of denomination `%s', last key %s valid for another %s\n",
+ denom->section,
+ GNUNET_h2s (&denom->keys_tail->h_rsa.hash),
+ GNUNET_TIME_relative2s (
+ GNUNET_TIME_absolute_get_remaining (
+ GNUNET_TIME_absolute_subtract (
+ GNUNET_TIME_absolute_add (
+ denom->keys_tail->anchor.abs_time,
+ denom->duration_withdraw),
+ overlap_duration)),
+ GNUNET_YES));
+ while ( (NULL == denom->keys_tail) ||
+ GNUNET_TIME_absolute_is_past (
+ GNUNET_TIME_absolute_subtract (
+ GNUNET_TIME_absolute_subtract (
+ GNUNET_TIME_absolute_add (denom->keys_tail->anchor.abs_time,
+ denom->duration_withdraw),
+ lookahead_sign),
+ overlap_duration)) )
+ {
+ if (! *wake)
+ {
+ key_gen++;
+ *wake = true;
+ }
+ if (GNUNET_OK !=
+ create_key (denom,
+ now))
+ {
+ GNUNET_break (0);
+ globals->global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return GNUNET_SYSERR;
+ }
+ }
+ /* remove expired denomination keys */
+ while ( (NULL != denom->keys_head) &&
+ GNUNET_TIME_absolute_is_past
+ (GNUNET_TIME_absolute_add (denom->keys_head->anchor.abs_time,
+ denom->duration_withdraw)) )
+ {
+ struct DenominationKey *key = denom->keys_head;
+ struct DenominationKey *nxt = key->next;
+
+ if (0 != key->rc)
+ break; /* later */
+ GNUNET_CONTAINER_DLL_remove (denom->keys_head,
+ denom->keys_tail,
+ key);
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_remove (
+ keys,
+ &key->h_rsa.hash,
+ key));
+ if ( (! key->purge) &&
+ (0 != unlink (key->filename)) )
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "unlink",
+ key->filename);
+ GNUNET_free (key->filename);
+ GNUNET_CRYPTO_rsa_private_key_free (key->denom_priv);
+ GNUNET_CRYPTO_rsa_public_key_free (key->denom_pub);
+ GNUNET_free (key->an);
+ GNUNET_free (key);
+ key = nxt;
+ }
+
+ /* 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);
+ GNUNET_CONTAINER_DLL_remove (denom_head,
+ denom_tail,
+ denom);
+ before = NULL;
+ for (struct Denomination *pos = denom_head;
+ NULL != pos;
+ pos = pos->next)
+ {
+ if (GNUNET_TIME_absolute_cmp (denomination_action_time (pos), >=, at))
+ break;
+ before = pos;
+ }
+ GNUNET_CONTAINER_DLL_insert_after (denom_head,
+ denom_tail,
+ before,
+ denom);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Task run periodically to expire keys and/or generate fresh ones.
+ *
+ * @param cls NULL
+ */
+static void
+update_denominations (void *cls)
+{
+ struct Denomination *denom;
+ struct GNUNET_TIME_Absolute now;
+ struct GNUNET_TIME_Timestamp t;
+ bool wake = false;
+
+ (void) cls;
+ keygen_task = NULL;
+ now = GNUNET_TIME_absolute_get ();
+ t = GNUNET_TIME_absolute_to_timestamp (now);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Updating denominations ...\n");
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ do {
+ denom = denom_head;
+ if (GNUNET_OK !=
+ update_keys (denom,
+ t,
+ &wake))
+ return;
+ } while (denom != denom_head);
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Updating denominations finished ...\n");
+ if (wake)
+ TES_wake_clients ();
+ keygen_task = GNUNET_SCHEDULER_add_at (denomination_action_time (denom),
+ &update_denominations,
+ NULL);
+}
+
+
+/**
+ * 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_Timestamp 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.abs_value_us
+ = anchor_ll * GNUNET_TIME_UNIT_SECONDS.rel_value_us;
+ if (anchor_ll != anchor.abs_time.abs_value_us
+ / GNUNET_TIME_UNIT_SECONDS.rel_value_us)
+ {
+ /* 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 = priv;
+ dk->denom = denom;
+ dk->anchor = anchor;
+ dk->filename = GNUNET_strdup (filename);
+ GNUNET_CRYPTO_rsa_public_key_hash (pub,
+ &dk->h_rsa.hash);
+ dk->denom_pub = pub;
+ generate_response (dk);
+ if (GNUNET_OK !=
+ GNUNET_CONTAINER_multihashmap_put (
+ keys,
+ &dk->h_rsa.hash,
+ dk,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Duplicate private key %s detected in file `%s'. Skipping.\n",
+ GNUNET_h2s (&dk->h_rsa.hash),
+ filename);
+ GNUNET_CRYPTO_rsa_private_key_free (priv);
+ GNUNET_CRYPTO_rsa_public_key_free (pub);
+ GNUNET_free (dk->an);
+ GNUNET_free (dk);
+ return;
+ }
+ before = NULL;
+ for (struct DenominationKey *pos = denom->keys_head;
+ NULL != pos;
+ pos = pos->next)
+ {
+ if (GNUNET_TIME_timestamp_cmp (pos->anchor,
+ >,
+ anchor))
+ break;
+ before = pos;
+ }
+ GNUNET_CONTAINER_DLL_insert_after (denom->keys_head,
+ denom->keys_tail,
+ before,
+ dk);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Imported key %s from `%s'\n",
+ GNUNET_h2s (&dk->h_rsa.hash),
+ filename);
+ }
+}
+
+
+/**
+ * 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
+ * @return #GNUNET_OK (always, continue to iterate)
+ */
+static enum GNUNET_GenericReturnValue
+import_key (void *cls,
+ const char *filename)
+{
+ struct Denomination *denom = cls;
+ struct GNUNET_DISK_FileHandle *fh;
+ struct GNUNET_DISK_MapHandle *map;
+ void *ptr;
+ int fd;
+ struct stat sbuf;
+
+ {
+ struct stat lsbuf;
+
+ if (0 != lstat (filename,
+ &lsbuf))
+ {
+ GNUNET_log_strerror_file (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_RDONLY | O_CLOEXEC);
+ if (-1 == fd)
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "open",
+ filename);
+ return GNUNET_OK;
+ }
+ if (0 != fstat (fd,
+ &sbuf))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+ "stat",
+ filename);
+ GNUNET_break (0 == close (fd));
+ 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);
+ GNUNET_break (0 == close (fd));
+ 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_file (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_file (GNUNET_ERROR_TYPE_WARNING,
+ "open",
+ filename);
+ GNUNET_break (0 == close (fd));
+ return GNUNET_OK;
+ }
+ if (sbuf.st_size > 16 * 1024)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "File `%s' too 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_file (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;
+}
+
+
+/**
+ * Parse configuration for denomination type parameters. Also determines
+ * our anchor by looking at the existing denominations of the same type.
+ *
+ * @param cfg configuration to use
+ * @param ct section in the configuration file giving the denomination type parameters
+ * @param[out] denom set to the denomination parameters from the configuration
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR if the configuration is invalid
+ */
+static enum GNUNET_GenericReturnValue
+parse_denomination_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *ct,
+ struct Denomination *denom)
+{
+ unsigned long long rsa_keysize;
+ char *secname;
+
+ GNUNET_asprintf (&secname,
+ "%s-secmod-rsa",
+ globals->section);
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ ct,
+ "DURATION_WITHDRAW",
+ &denom->duration_withdraw))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ ct,
+ "DURATION_WITHDRAW");
+ GNUNET_free (secname);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_TIME_relative_cmp (overlap_duration,
+ >=,
+ denom->duration_withdraw))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ secname,
+ "OVERLAP_DURATION",
+ "Value given must be smaller than value for DURATION_WITHDRAW!");
+ GNUNET_free (secname);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_number (cfg,
+ ct,
+ "RSA_KEYSIZE",
+ &rsa_keysize))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ ct,
+ "RSA_KEYSIZE");
+ GNUNET_free (secname);
+ return GNUNET_SYSERR;
+ }
+ if ( (rsa_keysize > 4 * 2048) ||
+ (rsa_keysize < 1024) )
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ ct,
+ "RSA_KEYSIZE",
+ "Given RSA keysize outside of permitted range [1024,8192]\n");
+ GNUNET_free (secname);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (secname);
+ denom->rsa_keysize = (unsigned int) rsa_keysize;
+ denom->section = GNUNET_strdup (ct);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Closure for #load_denominations.
+ */
+struct LoadContext
+{
+
+ /**
+ * Configuration to use.
+ */
+ const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+ /**
+ * Current time to use.
+ */
+ struct GNUNET_TIME_Timestamp t;
+
+ /**
+ * Status, to be set to #GNUNET_SYSERR on failure
+ */
+ enum GNUNET_GenericReturnValue ret;
+};
+
+
+/**
+ * Generate new denomination signing keys for the denomination type of the given @a
+ * denomination_alias.
+ *
+ * @param cls a `struct LoadContext`, with 'ret' 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)
+{
+ struct LoadContext *ctx = cls;
+ struct Denomination *denom;
+ bool wake = true;
+ char *cipher;
+
+ if ( (0 != strncasecmp (denomination_alias,
+ "coin_",
+ strlen ("coin_"))) &&
+ (0 != strncasecmp (denomination_alias,
+ "coin-",
+ strlen ("coin-"))) )
+ return; /* not a denomination type definition */
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (ctx->cfg,
+ denomination_alias,
+ "CIPHER",
+ &cipher))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ denomination_alias,
+ "CIPHER");
+ return;
+ }
+ if (0 != strcmp (cipher, "RSA"))
+ {
+ GNUNET_free (cipher);
+ return; /* Ignore denominations of other types than CS */
+ }
+ GNUNET_free (cipher);
+ denom = GNUNET_new (struct Denomination);
+ if (GNUNET_OK !=
+ parse_denomination_cfg (ctx->cfg,
+ denomination_alias,
+ denom))
+ {
+ ctx->ret = GNUNET_SYSERR;
+ GNUNET_free (denom);
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Loading keys for denomination %s\n",
+ denom->section);
+ {
+ char *dname;
+
+ GNUNET_asprintf (&dname,
+ "%s/%s",
+ keydir,
+ denom->section);
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_DISK_directory_create (dname));
+ GNUNET_DISK_directory_scan (dname,
+ &import_key,
+ denom);
+ GNUNET_free (dname);
+ }
+ GNUNET_CONTAINER_DLL_insert (denom_head,
+ denom_tail,
+ denom);
+ update_keys (denom,
+ ctx->t,
+ &wake);
+}
+
+
+/**
+ * Load the various duration values from @a cfg
+ *
+ * @param cfg configuration to use
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+load_durations (const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ char *secname;
+
+ GNUNET_asprintf (&secname,
+ "%s-secmod-rsa",
+ globals->section);
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ secname,
+ "OVERLAP_DURATION",
+ &overlap_duration))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ secname,
+ "OVERLAP_DURATION");
+ GNUNET_free (secname);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ secname,
+ "LOOKAHEAD_SIGN",
+ &lookahead_sign))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ secname,
+ "LOOKAHEAD_SIGN");
+ GNUNET_free (secname);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (secname);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function run on shutdown. Stops the various jobs (nicely).
+ *
+ * @param cls NULL
+ */
+static void
+do_shutdown (void *cls)
+{
+ (void) cls;
+ TES_listen_stop ();
+ if (NULL != keygen_task)
+ {
+ GNUNET_SCHEDULER_cancel (keygen_task);
+ keygen_task = NULL;
+ }
+ stop_workers ();
+ sem_done (&worker_sem);
+}
+
+
+/**
+ * Main function that will be run under the GNUnet scheduler.
+ *
+ * @param cls closure
+ * @param args remaining command-line arguments
+ * @param cfgfile name of the configuration file used (for saving, can be NULL!)
+ * @param cfg configuration
+ */
+void
+TALER_SECMOD_rsa_run (void *cls,
+ char *const *args,
+ const char *cfgfile,
+ const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ static struct TES_Callbacks cb = {
+ .dispatch = rsa_work_dispatch,
+ .updater = rsa_update_client_keys,
+ .init = rsa_client_init
+ };
+ struct TALER_SECMOD_Options *opt = cls;
+ char *secname;
+
+ (void) args;
+ (void) cfgfile;
+ globals = opt;
+ if (GNUNET_TIME_timestamp_cmp (opt->global_now,
+ !=,
+ opt->global_now_tmp))
+ {
+ /* The user gave "--now", use it! */
+ opt->global_now = opt->global_now_tmp;
+ }
+ else
+ {
+ /* get current time again, we may be timetraveling! */
+ opt->global_now = GNUNET_TIME_timestamp_get ();
+ }
+ GNUNET_asprintf (&secname,
+ "%s-secmod-rsa",
+ opt->section);
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_filename (cfg,
+ secname,
+ "KEY_DIR",
+ &keydir))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ secname,
+ "KEY_DIR");
+ GNUNET_free (secname);
+ opt->global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+ if (GNUNET_OK !=
+ load_durations (cfg))
+ {
+ opt->global_ret = EXIT_NOTCONFIGURED;
+ GNUNET_free (secname);
+ return;
+ }
+ GNUNET_asprintf (&secname,
+ "%s-secmod-rsa",
+ opt->section);
+ opt->global_ret = TES_listen_start (cfg,
+ secname,
+ &cb);
+ GNUNET_free (secname);
+ if (0 != opt->global_ret)
+ return;
+ sem_init (&worker_sem,
+ 0);
+ GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
+ NULL);
+ if (0 == opt->max_workers)
+ {
+ long lret;
+
+ lret = sysconf (_SC_NPROCESSORS_CONF);
+ if (lret <= 0)
+ lret = 1;
+ opt->max_workers = (unsigned int) lret;
+ }
+
+ for (unsigned int i = 0; i<opt->max_workers; i++)
+ if (GNUNET_OK !=
+ start_worker ())
+ {
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ /* Load denominations */
+ keys = GNUNET_CONTAINER_multihashmap_create (65536,
+ true);
+ {
+ struct LoadContext lc = {
+ .cfg = cfg,
+ .ret = GNUNET_OK,
+ .t = opt->global_now
+ };
+
+ GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
+ GNUNET_CONFIGURATION_iterate_sections (cfg,
+ &load_denominations,
+ &lc);
+ GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+ if (GNUNET_OK != lc.ret)
+ {
+ opt->global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ }
+ if (NULL == denom_head)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "No RSA denominations configured\n");
+ TES_wake_clients ();
+ return;
+ }
+ /* start job to keep keys up-to-date; MUST be run before the #listen_task,
+ hence with priority. */
+ keygen_task = GNUNET_SCHEDULER_add_with_priority (
+ GNUNET_SCHEDULER_PRIORITY_URGENT,
+ &update_denominations,
+ NULL);
+}
diff --git a/src/util/taler-exchange-secmod-cs.c b/src/util/taler-exchange-secmod-cs.c
index a2308507e..e27240e7a 100644
--- a/src/util/taler-exchange-secmod-cs.c
+++ b/src/util/taler-exchange-secmod-cs.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2022 Taler Systems SA
+ Copyright (C) 2014-2024 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
@@ -33,2257 +33,6 @@
*/
#include "platform.h"
#include "taler_util.h"
-#include "taler-exchange-secmod-cs.h"
-#include <gcrypt.h>
-#include <pthread.h>
-#include <sys/eventfd.h>
-#include "taler_error_codes.h"
-#include "taler_signatures.h"
-#include "secmod_common.h"
-#include <poll.h>
-
-
-/**
- * Information we keep per denomination.
- */
-struct Denomination;
-
-
-/**
- * One particular denomination key.
- */
-struct DenominationKey
-{
-
- /**
- * Kept in a DLL of the respective denomination. Sorted by anchor time.
- */
- struct DenominationKey *next;
-
- /**
- * Kept in a DLL of the respective denomination. Sorted by anchor time.
- */
- struct DenominationKey *prev;
-
- /**
- * Denomination this key belongs to.
- */
- struct Denomination *denom;
-
- /**
- * Name of the file this key is stored under.
- */
- char *filename;
-
- /**
- * The private key of the denomination.
- */
- struct GNUNET_CRYPTO_CsPrivateKey denom_priv;
-
- /**
- * The public key of the denomination.
- */
- struct GNUNET_CRYPTO_CsPublicKey denom_pub;
-
- /**
- * Message to transmit to clients to introduce this public key.
- */
- struct TALER_CRYPTO_CsKeyAvailableNotification *an;
-
- /**
- * Hash of this denomination's public key.
- */
- struct TALER_CsPubHashP h_cs;
-
- /**
- * Time at which this key is supposed to become valid.
- */
- struct GNUNET_TIME_Timestamp anchor;
-
- /**
- * Generation when this key was created or revoked.
- */
- uint64_t key_gen;
-
- /**
- * 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;
-
-};
-
-
-struct Denomination
-{
-
- /**
- * Kept in a DLL. Sorted by #denomination_action_time().
- */
- struct Denomination *next;
-
- /**
- * Kept in a DLL. Sorted by #denomination_action_time().
- */
- struct Denomination *prev;
-
- /**
- * Head of DLL of actual keys of this denomination.
- */
- struct DenominationKey *keys_head;
-
- /**
- * Tail of DLL of actual keys of this denomination.
- */
- struct DenominationKey *keys_tail;
-
- /**
- * 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
- * anonymity set.
- */
- struct GNUNET_TIME_Relative duration_withdraw;
-
- /**
- * What is the configuration section of this denomination type? Also used
- * for the directory name where the denomination keys are stored.
- */
- char *section;
-
-};
-
-
-/**
- * A semaphore.
- */
-struct Semaphore
-{
- /**
- * Mutex for the semaphore.
- */
- pthread_mutex_t mutex;
-
- /**
- * Condition variable for the semaphore.
- */
- pthread_cond_t cv;
-
- /**
- * Counter of the semaphore.
- */
- unsigned int ctr;
-};
-
-
-/**
- * Job in a batch sign request.
- */
-struct BatchJob;
-
-/**
- * Handle for a thread that does work in batch signing.
- */
-struct Worker
-{
- /**
- * Kept in a DLL.
- */
- struct Worker *prev;
-
- /**
- * Kept in a DLL.
- */
- struct Worker *next;
-
- /**
- * Job this worker should do next.
- */
- struct BatchJob *job;
-
- /**
- * Semaphore to signal the worker that a job is available.
- */
- struct Semaphore sem;
-
- /**
- * Handle for this thread.
- */
- pthread_t pt;
-
- /**
- * Set to true if the worker should terminate.
- */
- bool do_shutdown;
-};
-
-
-/**
- * Job in a batch sign request.
- */
-struct BatchJob
-{
-
- /**
- * Thread doing the work.
- */
- struct Worker *worker;
-
- /**
- * Semaphore to signal that the job is finished.
- */
- struct Semaphore sem;
-
- /**
- * Computation status.
- */
- enum TALER_ErrorCode ec;
-
- /**
- * Which type of request is this?
- */
- enum { TYPE_SIGN, TYPE_RDERIVE } type;
-
- /**
- * Details depending on @e type.
- */
- union
- {
-
- /**
- * Details if @e type is TYPE_SIGN.
- */
- struct
- {
- /**
- * Request we are working on.
- */
- const struct TALER_CRYPTO_CsSignRequestMessage *sr;
-
- /**
- * Result with the signature.
- */
- struct GNUNET_CRYPTO_CsBlindSignature cs_answer;
- } sign;
-
- /**
- * Details if type is TYPE_RDERIVE.
- */
- struct
- {
- /**
- * Request we are answering.
- */
- const struct TALER_CRYPTO_CsRDeriveRequest *rdr;
-
- /**
- * Pair of points to return.
- */
- struct GNUNET_CRYPTO_CSPublicRPairP rpairp;
-
- } rderive;
-
- } details;
-
-};
-
-/**
- * Head of DLL of workers ready for more work.
- */
-static struct Worker *worker_head;
-
-/**
- * Tail of DLL of workers ready for more work.
- */
-static struct Worker *worker_tail;
-
-/**
- * Lock for manipulating the worker DLL.
- */
-static pthread_mutex_t worker_lock;
-
-/**
- * Total number of workers that were started.
- */
-static unsigned int workers;
-
-/**
- * Semaphore used to grab a worker.
- */
-static struct Semaphore worker_sem;
-
-/**
- * Return value from main().
- */
-static int global_ret;
-
-/**
- * Time when the key update is executed.
- * Either the actual current time, or a pretended time.
- */
-static struct GNUNET_TIME_Timestamp global_now;
-
-/**
- * The time for the key update, as passed by the user
- * on the command line.
- */
-static struct GNUNET_TIME_Timestamp global_now_tmp;
-
-/**
- * Where do we store the keys?
- */
-static char *keydir;
-
-/**
- * Name of the configuration section prefix to use. Usually either "taler-exchange" or
- * "donau". The actual configuration section will then be
- * "$SECTION-secmod-cs".
- */
-static char *section;
-
-/**
- * How much should coin creation (@e duration_withdraw) duration overlap
- * with the next denomination? Basically, the starting time of two
- * denominations is always @e duration_withdraw - #overlap_duration apart.
- */
-static struct GNUNET_TIME_Relative overlap_duration;
-
-/**
- * How long into the future do we pre-generate keys?
- */
-static struct GNUNET_TIME_Relative lookahead_sign;
-
-/**
- * All of our denominations, in a DLL. Sorted?
- */
-static struct Denomination *denom_head;
-
-/**
- * All of our denominations, in a DLL. Sorted?
- */
-static struct Denomination *denom_tail;
-
-/**
- * Map of hashes of public (CS) keys to `struct DenominationKey *`
- * with the respective private keys.
- */
-static struct GNUNET_CONTAINER_MultiHashMap *keys;
-
-/**
- * Task run to generate new keys.
- */
-static struct GNUNET_SCHEDULER_Task *keygen_task;
-
-/**
- * Lock for the keys queue.
- */
-static pthread_mutex_t keys_lock;
-
-/**
- * Current key generation.
- */
-static uint64_t key_gen;
-
-/**
- * Number of workers to launch. Note that connections to
- * exchanges are NOT workers.
- */
-static unsigned int max_workers = 16;
-
-
-/**
- * Generate the announcement message for @a dk.
- *
- * @param[in,out] dk denomination key to generate the announcement for
- */
-static void
-generate_response (struct DenominationKey *dk)
-{
- struct Denomination *denom = dk->denom;
- size_t nlen = strlen (denom->section) + 1;
- struct TALER_CRYPTO_CsKeyAvailableNotification *an;
- void *p;
- size_t tlen;
-
- GNUNET_assert (sizeof(dk->denom_pub) < UINT16_MAX);
- GNUNET_assert (nlen < UINT16_MAX);
- tlen = nlen + sizeof (*an);
- GNUNET_assert (tlen < UINT16_MAX);
- an = GNUNET_malloc (tlen);
- an->header.size = htons ((uint16_t) tlen);
- an->header.type = htons (TALER_HELPER_CS_MT_AVAIL);
- an->section_name_len = htons ((uint16_t) nlen);
- an->anchor_time = GNUNET_TIME_timestamp_hton (dk->anchor);
- an->duration_withdraw = GNUNET_TIME_relative_hton (denom->duration_withdraw);
- an->denom_pub = dk->denom_pub;
- TALER_exchange_secmod_cs_sign (&dk->h_cs,
- denom->section,
- dk->anchor,
- denom->duration_withdraw,
- &TES_smpriv,
- &an->secm_sig);
- an->secm_pub = TES_smpub;
- p = (void *) &an[1];
- GNUNET_memcpy (p,
- denom->section,
- nlen);
- dk->an = an;
-}
-
-
-/**
- * Do the actual signing work.
- *
- * @param h_cs hash of key to sign with
- * @param planchet message to sign
- * @param for_melt true if for melting
- * @param[out] cs_sigp set to the CS signature
- * @return #TALER_EC_NONE on success
- */
-static enum TALER_ErrorCode
-do_sign (const struct TALER_CsPubHashP *h_cs,
- const struct GNUNET_CRYPTO_CsBlindedMessage *planchet,
- bool for_melt,
- struct GNUNET_CRYPTO_CsBlindSignature *cs_sigp)
-{
- struct GNUNET_CRYPTO_CsRSecret r[2];
- struct DenominationKey *dk;
-
- GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
- dk = GNUNET_CONTAINER_multihashmap_get (keys,
- &h_cs->hash);
- if (NULL == dk)
- {
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Signing request failed, denomination key %s unknown\n",
- GNUNET_h2s (&h_cs->hash));
- return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
- }
- if (GNUNET_TIME_absolute_is_future (dk->anchor.abs_time))
- {
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Signing request failed, denomination key %s is not yet valid\n",
- GNUNET_h2s (&h_cs->hash));
- return TALER_EC_EXCHANGE_DENOMINATION_HELPER_TOO_EARLY;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Received request to sign over bytes with key %s\n",
- GNUNET_h2s (&h_cs->hash));
- GNUNET_assert (dk->rc < UINT_MAX);
- dk->rc++;
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- GNUNET_CRYPTO_cs_r_derive (&planchet->nonce,
- for_melt ? "rm" : "rw",
- &dk->denom_priv,
- r);
- GNUNET_CRYPTO_cs_sign_derive (&dk->denom_priv,
- r,
- planchet,
- cs_sigp);
- GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
- GNUNET_assert (dk->rc > 0);
- dk->rc--;
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- return TALER_EC_NONE;
-}
-
-
-/**
- * Generate error response that signing failed.
- *
- * @param client client to send response to
- * @param ec error code to include
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-fail_sign (struct TES_Client *client,
- enum TALER_ErrorCode ec)
-{
- struct TALER_CRYPTO_SignFailure sf = {
- .header.size = htons (sizeof (sf)),
- .header.type = htons (TALER_HELPER_CS_MT_RES_SIGN_FAILURE),
- .ec = htonl (ec)
- };
-
- return TES_transmit (client->csock,
- &sf.header);
-}
-
-
-/**
- * Generate error response that deriving failed.
- *
- * @param client client to send response to
- * @param ec error code to include
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-fail_derive (struct TES_Client *client,
- enum TALER_ErrorCode ec)
-{
- struct TALER_CRYPTO_RDeriveFailure sf = {
- .header.size = htons (sizeof (sf)),
- .header.type = htons (TALER_HELPER_CS_MT_RES_RDERIVE_FAILURE),
- .ec = htonl (ec)
- };
-
- return TES_transmit (client->csock,
- &sf.header);
-}
-
-
-/**
- * Generate signature response.
- *
- * @param client client to send response to
- * @param cs_answer signature to send
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-send_signature (struct TES_Client *client,
- const struct GNUNET_CRYPTO_CsBlindSignature *cs_answer)
-{
- struct TALER_CRYPTO_SignResponse sres;
-
- sres.header.size = htons (sizeof (sres));
- sres.header.type = htons (TALER_HELPER_CS_MT_RES_SIGNATURE);
- sres.b = htonl (cs_answer->b);
- sres.cs_answer = cs_answer->s_scalar;
- return TES_transmit (client->csock,
- &sres.header);
-}
-
-
-/**
- * Handle @a client request @a sr to create signature. Create the
- * signature using the respective key and return the result to
- * the client.
- *
- * @param client the client making the request
- * @param sr the request details
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-handle_sign_request (struct TES_Client *client,
- const struct TALER_CRYPTO_CsSignRequestMessage *sr)
-{
- struct GNUNET_CRYPTO_CsBlindSignature cs_answer;
- struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
- enum TALER_ErrorCode ec;
- enum GNUNET_GenericReturnValue ret;
-
- ec = do_sign (&sr->h_cs,
- &sr->message,
- (0 != ntohl (sr->for_melt)),
- &cs_answer);
- if (TALER_EC_NONE != ec)
- {
- return fail_sign (client,
- ec);
- }
- ret = send_signature (client,
- &cs_answer);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Sent CS signature after %s\n",
- GNUNET_TIME_relative2s (
- GNUNET_TIME_absolute_get_duration (now),
- GNUNET_YES));
- return ret;
-}
-
-
-/**
- * Do the actual deriving work.
- *
- * @param h_cs key to sign with
- * @param nonce nonce to derive from
- * @param for_melt true if for melting
- * @param[out] rpairp set to the derived values
- * @return #TALER_EC_NONE on success
- */
-static enum TALER_ErrorCode
-do_derive (const struct TALER_CsPubHashP *h_cs,
- const struct GNUNET_CRYPTO_CsSessionNonce *nonce,
- bool for_melt,
- struct GNUNET_CRYPTO_CSPublicRPairP *rpairp)
-{
- struct DenominationKey *dk;
- struct GNUNET_CRYPTO_CSPrivateRPairP r_priv;
-
- GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
- dk = GNUNET_CONTAINER_multihashmap_get (keys,
- &h_cs->hash);
- if (NULL == dk)
- {
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "R Derive request failed, denomination key %s unknown\n",
- GNUNET_h2s (&h_cs->hash));
- return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
- }
- if (GNUNET_TIME_absolute_is_future (dk->anchor.abs_time))
- {
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "R Derive request failed, denomination key %s is not yet valid\n",
- GNUNET_h2s (&h_cs->hash));
- return TALER_EC_EXCHANGE_DENOMINATION_HELPER_TOO_EARLY;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Received request to derive R with key %s\n",
- GNUNET_h2s (&h_cs->hash));
- GNUNET_assert (dk->rc < UINT_MAX);
- dk->rc++;
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- GNUNET_CRYPTO_cs_r_derive (nonce,
- for_melt ? "rm" : "rw",
- &dk->denom_priv,
- r_priv.r);
- GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
- GNUNET_assert (dk->rc > 0);
- dk->rc--;
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- GNUNET_CRYPTO_cs_r_get_public (&r_priv.r[0],
- &rpairp->r_pub[0]);
- GNUNET_CRYPTO_cs_r_get_public (&r_priv.r[1],
- &rpairp->r_pub[1]);
- return TALER_EC_NONE;
-}
-
-
-/**
- * Generate derivation response.
- *
- * @param client client to send response to
- * @param r_pub public point value pair to send
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-send_derivation (struct TES_Client *client,
- const struct GNUNET_CRYPTO_CSPublicRPairP *r_pub)
-{
- struct TALER_CRYPTO_RDeriveResponse rdr = {
- .header.size = htons (sizeof (rdr)),
- .header.type = htons (TALER_HELPER_CS_MT_RES_RDERIVE),
- .r_pub = *r_pub
- };
-
- return TES_transmit (client->csock,
- &rdr.header);
-}
-
-
-/**
- * Initialize a semaphore @a sem with a value of @a val.
- *
- * @param[out] sem semaphore to initialize
- * @param val initial value of the semaphore
- */
-static void
-sem_init (struct Semaphore *sem,
- unsigned int val)
-{
- GNUNET_assert (0 ==
- pthread_mutex_init (&sem->mutex,
- NULL));
- GNUNET_assert (0 ==
- pthread_cond_init (&sem->cv,
- NULL));
- sem->ctr = val;
-}
-
-
-/**
- * Decrement semaphore, blocks until this is possible.
- *
- * @param[in,out] sem semaphore to decrement
- */
-static void
-sem_down (struct Semaphore *sem)
-{
- GNUNET_assert (0 == pthread_mutex_lock (&sem->mutex));
- while (0 == sem->ctr)
- {
- pthread_cond_wait (&sem->cv,
- &sem->mutex);
- }
- sem->ctr--;
- GNUNET_assert (0 == pthread_mutex_unlock (&sem->mutex));
-}
-
-
-/**
- * Increment semaphore, blocks until this is possible.
- *
- * @param[in,out] sem semaphore to decrement
- */
-static void
-sem_up (struct Semaphore *sem)
-{
- GNUNET_assert (0 == pthread_mutex_lock (&sem->mutex));
- sem->ctr++;
- GNUNET_assert (0 == pthread_mutex_unlock (&sem->mutex));
- pthread_cond_signal (&sem->cv);
-}
-
-
-/**
- * Release resources used by @a sem.
- *
- * @param[in] sem semaphore to release (except the memory itself)
- */
-static void
-sem_done (struct Semaphore *sem)
-{
- GNUNET_break (0 == pthread_cond_destroy (&sem->cv));
- GNUNET_break (0 == pthread_mutex_destroy (&sem->mutex));
-}
-
-
-/**
- * Main logic of a worker thread. Grabs work, does it,
- * grabs more work.
- *
- * @param cls a `struct Worker *`
- * @returns cls
- */
-static void *
-worker (void *cls)
-{
- struct Worker *w = cls;
-
- while (true)
- {
- GNUNET_assert (0 == pthread_mutex_lock (&worker_lock));
- GNUNET_CONTAINER_DLL_insert (worker_head,
- worker_tail,
- w);
- GNUNET_assert (0 == pthread_mutex_unlock (&worker_lock));
- sem_up (&worker_sem);
- sem_down (&w->sem);
- if (w->do_shutdown)
- break;
- {
- struct BatchJob *bj = w->job;
-
- switch (bj->type)
- {
- case TYPE_SIGN:
- {
- const struct TALER_CRYPTO_CsSignRequestMessage *sr
- = bj->details.sign.sr;
-
- bj->ec = do_sign (&sr->h_cs,
- &sr->message,
- (0 != ntohl (sr->for_melt)),
- &bj->details.sign.cs_answer);
- break;
- }
- case TYPE_RDERIVE:
- {
- const struct TALER_CRYPTO_CsRDeriveRequest *rdr
- = bj->details.rderive.rdr;
- bj->ec = do_derive (&rdr->h_cs,
- &rdr->nonce,
- (0 != ntohl (rdr->for_melt)),
- &bj->details.rderive.rpairp);
- break;
- }
- }
- sem_up (&bj->sem);
- w->job = NULL;
- }
- }
- return w;
-}
-
-
-/**
- * Start batch job @a bj to sign @a sr.
- *
- * @param sr signature request to answer
- * @param[out] bj job data structure
- */
-static void
-start_sign_job (const struct TALER_CRYPTO_CsSignRequestMessage *sr,
- struct BatchJob *bj)
-{
- sem_init (&bj->sem,
- 0);
- bj->type = TYPE_SIGN;
- bj->details.sign.sr = sr;
- sem_down (&worker_sem);
- GNUNET_assert (0 == pthread_mutex_lock (&worker_lock));
- bj->worker = worker_head;
- GNUNET_CONTAINER_DLL_remove (worker_head,
- worker_tail,
- bj->worker);
- GNUNET_assert (0 == pthread_mutex_unlock (&worker_lock));
- bj->worker->job = bj;
- sem_up (&bj->worker->sem);
-}
-
-
-/**
- * Start batch job @a bj to derive @a rdr.
- *
- * @param rdr derivation request to answer
- * @param[out] bj job data structure
- */
-static void
-start_derive_job (const struct TALER_CRYPTO_CsRDeriveRequest *rdr,
- struct BatchJob *bj)
-{
- sem_init (&bj->sem,
- 0);
- bj->type = TYPE_RDERIVE;
- bj->details.rderive.rdr = rdr;
- sem_down (&worker_sem);
- GNUNET_assert (0 == pthread_mutex_lock (&worker_lock));
- bj->worker = worker_head;
- GNUNET_CONTAINER_DLL_remove (worker_head,
- worker_tail,
- bj->worker);
- GNUNET_assert (0 == pthread_mutex_unlock (&worker_lock));
- bj->worker->job = bj;
- sem_up (&bj->worker->sem);
-}
-
-
-/**
- * Finish a job @a bj for a @a client.
- *
- * @param client who made the request
- * @param[in,out] bj job to finish
- */
-static void
-finish_job (struct TES_Client *client,
- struct BatchJob *bj)
-{
- sem_down (&bj->sem);
- sem_done (&bj->sem);
- switch (bj->type)
- {
- case TYPE_SIGN:
- if (TALER_EC_NONE != bj->ec)
- {
- fail_sign (client,
- bj->ec);
- return;
- }
- send_signature (client,
- &bj->details.sign.cs_answer);
- break;
- case TYPE_RDERIVE:
- if (TALER_EC_NONE != bj->ec)
- {
- fail_derive (client,
- bj->ec);
- return;
- }
- send_derivation (client,
- &bj->details.rderive.rpairp);
- break;
- }
-}
-
-
-/**
- * Handle @a client request @a sr to create a batch of signature. Creates the
- * signatures using the respective key and return the results to the client.
- *
- * @param client the client making the request
- * @param bsr the request details
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-handle_batch_sign_request (struct TES_Client *client,
- const struct TALER_CRYPTO_BatchSignRequest *bsr)
-{
- uint32_t bs = ntohl (bsr->batch_size);
- uint16_t size = ntohs (bsr->header.size) - sizeof (*bsr);
- const void *off = (const void *) &bsr[1];
- unsigned int idx = 0;
- struct BatchJob jobs[GNUNET_NZL (bs)];
- bool failure = false;
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Handling batch sign request of size %u\n",
- (unsigned int) bs);
- if (bs > TALER_MAX_FRESH_COINS)
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- while ( (bs > 0) &&
- (size >= sizeof (struct TALER_CRYPTO_CsSignRequestMessage)) )
- {
- const struct TALER_CRYPTO_CsSignRequestMessage *sr = off;
- uint16_t s = ntohs (sr->header.size);
-
- if (s > size)
- {
- failure = true;
- bs = idx;
- break;
- }
- start_sign_job (sr,
- &jobs[idx++]);
- off += s;
- size -= s;
- }
- GNUNET_break_op (0 == size);
- bs = GNUNET_MIN (bs,
- idx);
- for (unsigned int i = 0; i<bs; i++)
- finish_job (client,
- &jobs[i]);
- if (failure)
- {
- struct TALER_CRYPTO_SignFailure sf = {
- .header.size = htons (sizeof (sf)),
- .header.type = htons (TALER_HELPER_CS_MT_RES_BATCH_SIGN_FAILURE),
- .ec = htonl (TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE)
- };
-
- GNUNET_break (0);
- return TES_transmit (client->csock,
- &sf.header);
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Handle @a client request @a sr to create a batch of derivations. Creates the
- * derivations using the respective key and return the results to the client.
- *
- * @param client the client making the request
- * @param bdr the request details
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-handle_batch_derive_request (struct TES_Client *client,
- const struct TALER_CRYPTO_BatchDeriveRequest *bdr)
-{
- uint32_t bs = ntohl (bdr->batch_size);
- uint16_t size = ntohs (bdr->header.size) - sizeof (*bdr);
- const void *off = (const void *) &bdr[1];
- unsigned int idx = 0;
- struct BatchJob jobs[bs];
- bool failure = false;
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Handling batch derivation request of size %u\n",
- (unsigned int) bs);
- if (bs > TALER_MAX_FRESH_COINS)
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- while ( (bs > 0) &&
- (size >= sizeof (struct TALER_CRYPTO_CsRDeriveRequest)) )
- {
- const struct TALER_CRYPTO_CsRDeriveRequest *rdr = off;
- uint16_t s = ntohs (rdr->header.size);
-
- if ( (s > size) ||
- (s != sizeof (*rdr)) )
- {
- failure = true;
- bs = idx;
- break;
- }
- start_derive_job (rdr,
- &jobs[idx++]);
- off += s;
- size -= s;
- }
- GNUNET_break_op (0 == size);
- bs = GNUNET_MIN (bs,
- idx);
- for (unsigned int i = 0; i<bs; i++)
- finish_job (client,
- &jobs[i]);
- if (failure)
- {
- GNUNET_break (0);
- return fail_derive (client,
- TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE);
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Start worker thread for batch processing.
- *
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-start_worker (void)
-{
- struct Worker *w;
-
- w = GNUNET_new (struct Worker);
- sem_init (&w->sem,
- 0);
- if (0 != pthread_create (&w->pt,
- NULL,
- &worker,
- w))
- {
- GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
- "pthread_create");
- GNUNET_free (w);
- return GNUNET_SYSERR;
- }
- workers++;
- return GNUNET_OK;
-}
-
-
-/**
- * Stop all worker threads.
- */
-static void
-stop_workers (void)
-{
- while (workers > 0)
- {
- struct Worker *w;
- void *result;
-
- sem_down (&worker_sem);
- GNUNET_assert (0 == pthread_mutex_lock (&worker_lock));
- w = worker_head;
- GNUNET_CONTAINER_DLL_remove (worker_head,
- worker_tail,
- w);
- GNUNET_assert (0 == pthread_mutex_unlock (&worker_lock));
- w->do_shutdown = true;
- sem_up (&w->sem);
- pthread_join (w->pt,
- &result);
- GNUNET_assert (result == w);
- sem_done (&w->sem);
- GNUNET_free (w);
- workers--;
- }
-}
-
-
-/**
- * Initialize key material for denomination key @a dk (also on disk).
- *
- * @param[in,out] dk denomination key to compute key material for
- * @param position where in the DLL will the @a dk go
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-setup_key (struct DenominationKey *dk,
- struct DenominationKey *position)
-{
- struct Denomination *denom = dk->denom;
- struct GNUNET_CRYPTO_CsPrivateKey priv;
- struct GNUNET_CRYPTO_CsPublicKey pub;
-
- GNUNET_CRYPTO_cs_private_key_generate (&priv);
- GNUNET_CRYPTO_cs_private_key_get_public (&priv,
- &pub);
- GNUNET_CRYPTO_hash (&pub,
- sizeof (pub),
- &dk->h_cs.hash);
- GNUNET_asprintf (&dk->filename,
- "%s/%s/%llu",
- keydir,
- denom->section,
- (unsigned long long) (dk->anchor.abs_time.abs_value_us
- / GNUNET_TIME_UNIT_SECONDS.rel_value_us
- ));
- if (GNUNET_OK !=
- GNUNET_DISK_fn_write (dk->filename,
- &priv,
- sizeof(priv),
- GNUNET_DISK_PERM_USER_READ))
- {
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
- "write",
- dk->filename);
- return GNUNET_SYSERR;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Setup fresh private key %s at %s in `%s' (generation #%llu)\n",
- GNUNET_h2s (&dk->h_cs.hash),
- GNUNET_TIME_timestamp2s (dk->anchor),
- dk->filename,
- (unsigned long long) key_gen);
- dk->denom_priv = priv;
- dk->denom_pub = pub;
- dk->key_gen = key_gen;
- generate_response (dk);
- if (GNUNET_OK !=
- GNUNET_CONTAINER_multihashmap_put (
- keys,
- &dk->h_cs.hash,
- dk,
- GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Duplicate private key created! Terminating.\n");
- GNUNET_free (dk->filename);
- GNUNET_free (dk->an);
- GNUNET_free (dk);
- return GNUNET_SYSERR;
- }
- GNUNET_CONTAINER_DLL_insert_after (denom->keys_head,
- denom->keys_tail,
- position,
- dk);
- return GNUNET_OK;
-}
-
-
-/**
- * The withdraw period of a key @a dk has expired. Purge it.
- *
- * @param[in] dk expired denomination key to purge
- */
-static void
-purge_key (struct DenominationKey *dk)
-{
- if (dk->purge)
- return;
- if (0 != unlink (dk->filename))
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
- "unlink",
- dk->filename);
- GNUNET_free (dk->filename);
- dk->purge = true;
- dk->key_gen = key_gen;
-}
-
-
-/**
- * A @a client informs us that a key has been revoked.
- * Check if the key is still in use, and if so replace (!)
- * it with a fresh key.
- *
- * @param client the client making the request
- * @param rr the revocation request
- */
-static enum GNUNET_GenericReturnValue
-handle_revoke_request (struct TES_Client *client,
- const struct TALER_CRYPTO_CsRevokeRequest *rr)
-{
- struct DenominationKey *dk;
- struct DenominationKey *ndk;
- struct Denomination *denom;
-
- (void) client;
- GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
- dk = GNUNET_CONTAINER_multihashmap_get (keys,
- &rr->h_cs.hash);
- if (NULL == dk)
- {
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Revocation request ignored, denomination key %s unknown\n",
- GNUNET_h2s (&rr->h_cs.hash));
- return GNUNET_OK;
- }
- if (dk->purge)
- {
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Revocation request ignored, denomination key %s already revoked\n",
- GNUNET_h2s (&rr->h_cs.hash));
- return GNUNET_OK;
- }
-
- key_gen++;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Revoking key %s, bumping generation to %llu\n",
- GNUNET_h2s (&rr->h_cs.hash),
- (unsigned long long) key_gen);
- purge_key (dk);
-
- /* Setup replacement key */
- denom = dk->denom;
- ndk = GNUNET_new (struct DenominationKey);
- ndk->denom = denom;
- ndk->anchor = dk->anchor;
- if (GNUNET_OK !=
- setup_key (ndk,
- dk))
- {
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- GNUNET_break (0);
- GNUNET_SCHEDULER_shutdown ();
- global_ret = EXIT_FAILURE;
- return GNUNET_SYSERR;
- }
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- TES_wake_clients ();
- return GNUNET_OK;
-}
-
-
-/**
- * Handle @a client request @a rdr to create signature. Create the
- * signature using the respective key and return the result to
- * the client.
- *
- * @param client the client making the request
- * @param rdr the request details
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-handle_r_derive_request (struct TES_Client *client,
- const struct TALER_CRYPTO_CsRDeriveRequest *rdr)
-{
- struct GNUNET_CRYPTO_CSPublicRPairP r_pub;
- struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
- enum TALER_ErrorCode ec;
- enum GNUNET_GenericReturnValue ret;
-
- ec = do_derive (&rdr->h_cs,
- &rdr->nonce,
- (0 != ntohl (rdr->for_melt)),
- &r_pub);
- if (TALER_EC_NONE != ec)
- {
- return fail_derive (client,
- ec);
- }
-
- ret = send_derivation (client,
- &r_pub);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Sent CS Derived R after %s\n",
- GNUNET_TIME_relative2s (
- GNUNET_TIME_absolute_get_duration (now),
- GNUNET_YES));
- return ret;
-}
-
-
-/**
- * Handle @a hdr message received from @a client.
- *
- * @param client the client that received the message
- * @param hdr message that was received
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-cs_work_dispatch (struct TES_Client *client,
- const struct GNUNET_MessageHeader *hdr)
-{
- uint16_t msize = ntohs (hdr->size);
-
- switch (ntohs (hdr->type))
- {
- case TALER_HELPER_CS_MT_REQ_SIGN:
- if (msize < sizeof (struct TALER_CRYPTO_CsSignRequestMessage))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return handle_sign_request (
- client,
- (const struct TALER_CRYPTO_CsSignRequestMessage *) hdr);
- case TALER_HELPER_CS_MT_REQ_REVOKE:
- if (msize != sizeof (struct TALER_CRYPTO_CsRevokeRequest))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return handle_revoke_request (
- client,
- (const struct TALER_CRYPTO_CsRevokeRequest *) hdr);
- case TALER_HELPER_CS_MT_REQ_BATCH_SIGN:
- if (msize <= sizeof (struct TALER_CRYPTO_BatchSignRequest))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return handle_batch_sign_request (
- client,
- (const struct TALER_CRYPTO_BatchSignRequest *) hdr);
- case TALER_HELPER_CS_MT_REQ_BATCH_RDERIVE:
- if (msize <= sizeof (struct TALER_CRYPTO_BatchDeriveRequest))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return handle_batch_derive_request (
- client,
- (const struct TALER_CRYPTO_BatchDeriveRequest *) hdr);
- case TALER_HELPER_CS_MT_REQ_RDERIVE:
- if (msize != sizeof (struct TALER_CRYPTO_CsRDeriveRequest))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return handle_r_derive_request (client,
- (const struct
- TALER_CRYPTO_CsRDeriveRequest *) hdr);
- default:
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-}
-
-
-/**
- * Send our initial key set to @a client together with the
- * "sync" terminator.
- *
- * @param client the client to inform
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-cs_client_init (struct TES_Client *client)
-{
- size_t obs = 0;
- char *buf;
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Initializing new client %p\n",
- client);
- GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
- for (struct Denomination *denom = denom_head;
- NULL != denom;
- denom = denom->next)
- {
- for (struct DenominationKey *dk = denom->keys_head;
- NULL != dk;
- dk = dk->next)
- {
- obs += ntohs (dk->an->header.size);
- }
- }
- buf = GNUNET_malloc (obs);
- obs = 0;
- for (struct Denomination *denom = denom_head;
- NULL != denom;
- denom = denom->next)
- {
- for (struct DenominationKey *dk = denom->keys_head;
- NULL != dk;
- dk = dk->next)
- {
- GNUNET_memcpy (&buf[obs],
- dk->an,
- ntohs (dk->an->header.size));
- obs += ntohs (dk->an->header.size);
- }
- }
- client->key_gen = key_gen;
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- if (GNUNET_OK !=
- TES_transmit_raw (client->csock,
- obs,
- buf))
- {
- GNUNET_free (buf);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Client %p must have disconnected\n",
- client);
- return GNUNET_SYSERR;
- }
- GNUNET_free (buf);
- {
- struct GNUNET_MessageHeader synced = {
- .type = htons (TALER_HELPER_CS_SYNCED),
- .size = htons (sizeof (synced))
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Sending CS SYNCED message to %p\n",
- client);
- if (GNUNET_OK !=
- TES_transmit (client->csock,
- &synced))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Notify @a client about all changes to the keys since
- * the last generation known to the @a client.
- *
- * @param client the client to notify
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-cs_update_client_keys (struct TES_Client *client)
-{
- size_t obs = 0;
- char *buf;
- enum GNUNET_GenericReturnValue ret;
-
- GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
- for (struct Denomination *denom = denom_head;
- NULL != denom;
- denom = denom->next)
- {
- for (struct DenominationKey *key = denom->keys_head;
- NULL != key;
- key = key->next)
- {
- if (key->key_gen <= client->key_gen)
- continue;
- if (key->purge)
- obs += sizeof (struct TALER_CRYPTO_CsKeyPurgeNotification);
- else
- obs += ntohs (key->an->header.size);
- }
- }
- if (0 == obs)
- {
- /* nothing to do */
- client->key_gen = key_gen;
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- return GNUNET_OK;
- }
- buf = GNUNET_malloc (obs);
- obs = 0;
- for (struct Denomination *denom = denom_head;
- NULL != denom;
- denom = denom->next)
- {
- for (struct DenominationKey *key = denom->keys_head;
- NULL != key;
- key = key->next)
- {
- if (key->key_gen <= client->key_gen)
- continue;
- if (key->purge)
- {
- struct TALER_CRYPTO_CsKeyPurgeNotification pn = {
- .header.type = htons (TALER_HELPER_CS_MT_PURGE),
- .header.size = htons (sizeof (pn)),
- .h_cs = key->h_cs
- };
-
- GNUNET_memcpy (&buf[obs],
- &pn,
- sizeof (pn));
- GNUNET_assert (obs + sizeof (pn)
- > obs);
- obs += sizeof (pn);
- }
- else
- {
- GNUNET_memcpy (&buf[obs],
- key->an,
- ntohs (key->an->header.size));
- GNUNET_assert (obs + ntohs (key->an->header.size)
- > obs);
- obs += ntohs (key->an->header.size);
- }
- }
- }
- client->key_gen = key_gen;
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- ret = TES_transmit_raw (client->csock,
- obs,
- buf);
- GNUNET_free (buf);
- return ret;
-}
-
-
-/**
- * Create a new denomination key (we do not have enough).
- *
- * @param denom denomination key to create
- * @param now current time to use (to get many keys to use the exact same time)
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-create_key (struct Denomination *denom,
- struct GNUNET_TIME_Timestamp now)
-{
- struct DenominationKey *dk;
- struct GNUNET_TIME_Timestamp anchor;
-
- anchor = now;
- if (NULL != denom->keys_tail)
- {
- struct GNUNET_TIME_Absolute abs;
-
- abs = GNUNET_TIME_absolute_add (denom->keys_tail->anchor.abs_time,
- GNUNET_TIME_relative_subtract (
- denom->duration_withdraw,
- overlap_duration));
- if (GNUNET_TIME_absolute_cmp (now.abs_time, <, abs))
- anchor = GNUNET_TIME_absolute_to_timestamp (abs);
- }
- dk = GNUNET_new (struct DenominationKey);
- dk->denom = denom;
- dk->anchor = anchor;
- if (GNUNET_OK !=
- setup_key (dk,
- denom->keys_tail))
- {
- GNUNET_break (0);
- GNUNET_free (dk);
- GNUNET_SCHEDULER_shutdown ();
- global_ret = EXIT_FAILURE;
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * 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 denom denomination to compute action time for
- */
-static struct GNUNET_TIME_Absolute
-denomination_action_time (const struct Denomination *denom)
-{
- struct DenominationKey *head = denom->keys_head;
- struct DenominationKey *tail = denom->keys_tail;
- struct GNUNET_TIME_Absolute tt;
-
- if (NULL == head)
- return GNUNET_TIME_UNIT_ZERO_ABS;
- tt = GNUNET_TIME_absolute_subtract (
- GNUNET_TIME_absolute_subtract (
- GNUNET_TIME_absolute_add (tail->anchor.abs_time,
- denom->duration_withdraw),
- lookahead_sign),
- overlap_duration);
- if (head->rc > 0)
- return tt; /* head expiration does not count due to rc > 0 */
- return GNUNET_TIME_absolute_min (
- GNUNET_TIME_absolute_add (head->anchor.abs_time,
- denom->duration_withdraw),
- tt);
-}
-
-
-/**
- * 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
- * @param now current time to use (to get many keys to use the exact same time)
- * @param[in,out] wake set to true if we should wake the clients
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-update_keys (struct Denomination *denom,
- struct GNUNET_TIME_Timestamp now,
- bool *wake)
-{
- /* create new denomination keys */
- if (NULL != denom->keys_tail)
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Updating keys of denomination `%s', last key %s valid for another %s\n",
- denom->section,
- GNUNET_h2s (&denom->keys_tail->h_cs.hash),
- GNUNET_TIME_relative2s (
- GNUNET_TIME_absolute_get_remaining (
- GNUNET_TIME_absolute_subtract (
- GNUNET_TIME_absolute_add (
- denom->keys_tail->anchor.abs_time,
- denom->duration_withdraw),
- overlap_duration)),
- GNUNET_YES));
- while ( (NULL == denom->keys_tail) ||
- GNUNET_TIME_absolute_is_past (
- GNUNET_TIME_absolute_subtract (
- GNUNET_TIME_absolute_subtract (
- GNUNET_TIME_absolute_add (denom->keys_tail->anchor.abs_time,
- denom->duration_withdraw),
- lookahead_sign),
- overlap_duration)) )
- {
- if (! *wake)
- {
- key_gen++;
- *wake = true;
- }
- if (GNUNET_OK !=
- create_key (denom,
- now))
- {
- GNUNET_break (0);
- global_ret = EXIT_FAILURE;
- GNUNET_SCHEDULER_shutdown ();
- return GNUNET_SYSERR;
- }
- }
- /* remove expired denomination keys */
- while ( (NULL != denom->keys_head) &&
- GNUNET_TIME_absolute_is_past
- (GNUNET_TIME_absolute_add (denom->keys_head->anchor.abs_time,
- denom->duration_withdraw)) )
- {
- struct DenominationKey *key = denom->keys_head;
- struct DenominationKey *nxt = key->next;
-
- if (0 != key->rc)
- break; /* later */
- GNUNET_CONTAINER_DLL_remove (denom->keys_head,
- denom->keys_tail,
- key);
- GNUNET_assert (GNUNET_OK ==
- GNUNET_CONTAINER_multihashmap_remove (
- keys,
- &key->h_cs.hash,
- key));
- if ( (! key->purge) &&
- (0 != unlink (key->filename)) )
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
- "unlink",
- key->filename);
- GNUNET_free (key->filename);
- GNUNET_free (key->an);
- GNUNET_free (key);
- key = nxt;
- }
-
- /* 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);
- GNUNET_CONTAINER_DLL_remove (denom_head,
- denom_tail,
- denom);
- before = NULL;
- for (struct Denomination *pos = denom_head;
- NULL != pos;
- pos = pos->next)
- {
- if (GNUNET_TIME_absolute_cmp (denomination_action_time (pos), >=, at))
- break;
- before = pos;
- }
- GNUNET_CONTAINER_DLL_insert_after (denom_head,
- denom_tail,
- before,
- denom);
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Task run periodically to expire keys and/or generate fresh ones.
- *
- * @param cls NULL
- */
-static void
-update_denominations (void *cls)
-{
- struct Denomination *denom;
- struct GNUNET_TIME_Absolute now;
- struct GNUNET_TIME_Timestamp t;
- bool wake = false;
-
- (void) cls;
- keygen_task = NULL;
- now = GNUNET_TIME_absolute_get ();
- t = GNUNET_TIME_absolute_to_timestamp (now);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Updating denominations ...\n");
- GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
- do {
- denom = denom_head;
- if (GNUNET_OK !=
- update_keys (denom,
- t,
- &wake))
- return;
- } while (denom != denom_head);
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Updating denominations finished ...\n");
- if (wake)
- TES_wake_clients ();
- keygen_task = GNUNET_SCHEDULER_add_at (denomination_action_time (denom),
- &update_denominations,
- NULL);
-}
-
-
-/**
- * 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 priv key material
- */
-static void
-parse_key (struct Denomination *denom,
- const char *filename,
- const struct GNUNET_CRYPTO_CsPrivateKey *priv)
-{
- char *anchor_s;
- char dummy;
- unsigned long long anchor_ll;
- struct GNUNET_TIME_Timestamp 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.abs_value_us
- = anchor_ll * GNUNET_TIME_UNIT_SECONDS.rel_value_us;
- if (anchor_ll != anchor.abs_time.abs_value_us
- / GNUNET_TIME_UNIT_SECONDS.rel_value_us)
- {
- /* Integer overflow. Bad, invalid filename. */
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Filename `%s' invalid for key file, skipping\n",
- filename);
- return;
- }
- {
- struct DenominationKey *dk;
- struct DenominationKey *before;
-
- dk = GNUNET_new (struct DenominationKey);
- dk->denom_priv = *priv;
- dk->denom = denom;
- dk->anchor = anchor;
- dk->filename = GNUNET_strdup (filename);
- GNUNET_CRYPTO_cs_private_key_get_public (priv,
- &dk->denom_pub);
- GNUNET_CRYPTO_hash (&dk->denom_pub,
- sizeof (dk->denom_pub),
- &dk->h_cs.hash);
- generate_response (dk);
- if (GNUNET_OK !=
- GNUNET_CONTAINER_multihashmap_put (
- keys,
- &dk->h_cs.hash,
- dk,
- GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Duplicate private key %s detected in file `%s'. Skipping.\n",
- GNUNET_h2s (&dk->h_cs.hash),
- filename);
- GNUNET_free (dk->an);
- GNUNET_free (dk);
- return;
- }
- before = NULL;
- for (struct DenominationKey *pos = denom->keys_head;
- NULL != pos;
- pos = pos->next)
- {
- if (GNUNET_TIME_timestamp_cmp (pos->anchor,
- >,
- anchor))
- break;
- before = pos;
- }
- GNUNET_CONTAINER_DLL_insert_after (denom->keys_head,
- denom->keys_tail,
- before,
- dk);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Imported key %s from `%s'\n",
- GNUNET_h2s (&dk->h_cs.hash),
- filename);
- }
-}
-
-
-/**
- * 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
- * @return #GNUNET_OK (always, continue to iterate)
- */
-static enum GNUNET_GenericReturnValue
-import_key (void *cls,
- const char *filename)
-{
- struct Denomination *denom = cls;
- struct GNUNET_DISK_FileHandle *fh;
- struct GNUNET_DISK_MapHandle *map;
- void *ptr;
- int fd;
- struct stat sbuf;
-
- {
- struct stat lsbuf;
-
- if (0 != lstat (filename,
- &lsbuf))
- {
- GNUNET_log_strerror_file (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_RDONLY | O_CLOEXEC);
- if (-1 == fd)
- {
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
- "open",
- filename);
- return GNUNET_OK;
- }
- if (0 != fstat (fd,
- &sbuf))
- {
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
- "stat",
- filename);
- GNUNET_break (0 == close (fd));
- 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);
- GNUNET_break (0 == close (fd));
- 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_file (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_file (GNUNET_ERROR_TYPE_WARNING,
- "open",
- filename);
- GNUNET_break (0 == close (fd));
- return GNUNET_OK;
- }
- if (sbuf.st_size != sizeof(struct GNUNET_CRYPTO_CsPrivateKey))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "File `%s' too 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_file (GNUNET_ERROR_TYPE_WARNING,
- "mmap",
- filename);
- GNUNET_DISK_file_close (fh);
- return GNUNET_OK;
- }
- parse_key (denom,
- filename,
- (const struct GNUNET_CRYPTO_CsPrivateKey *) ptr);
- GNUNET_DISK_file_unmap (map);
- GNUNET_DISK_file_close (fh);
- return GNUNET_OK;
-}
-
-
-/**
- * Parse configuration for denomination type parameters. Also determines
- * our anchor by looking at the existing denominations of the same type.
- *
- * @param cfg configuration to use
- * @param ct section in the configuration file giving the denomination type parameters
- * @param[out] denom set to the denomination parameters from the configuration
- * @return #GNUNET_OK on success, #GNUNET_SYSERR if the configuration is invalid
- */
-static enum GNUNET_GenericReturnValue
-parse_denomination_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg,
- const char *ct,
- struct Denomination *denom)
-{
- char *secname;
-
- GNUNET_asprintf (&secname,
- "%s-secmod-cs",
- section);
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_time (cfg,
- ct,
- "DURATION_WITHDRAW",
- &denom->duration_withdraw))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- ct,
- "DURATION_WITHDRAW");
- GNUNET_free (secname);
- return GNUNET_SYSERR;
- }
- if (GNUNET_TIME_relative_cmp (overlap_duration,
- >=,
- denom->duration_withdraw))
- {
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- secname,
- "OVERLAP_DURATION",
- "Value given must be smaller than value for DURATION_WITHDRAW!");
- GNUNET_free (secname);
- return GNUNET_SYSERR;
- }
- GNUNET_free (secname);
- denom->section = GNUNET_strdup (ct);
- return GNUNET_OK;
-}
-
-
-/**
- * Closure for #load_denominations.
- */
-struct LoadContext
-{
-
- /**
- * Configuration to use.
- */
- const struct GNUNET_CONFIGURATION_Handle *cfg;
-
- /**
- * Current time to use.
- */
- struct GNUNET_TIME_Timestamp t;
-
- /**
- * Status, to be set to #GNUNET_SYSERR on failure
- */
- enum GNUNET_GenericReturnValue ret;
-};
-
-
-/**
- * Generate new denomination signing keys for the denomination type of the given @a
- * denomination_alias.
- *
- * @param cls a `struct LoadContext`, with 'ret' 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)
-{
- struct LoadContext *ctx = cls;
- struct Denomination *denom;
- bool wake = true;
- char *cipher;
-
- if ( (0 != strncasecmp (denomination_alias,
- "coin_",
- strlen ("coin_"))) &&
- (0 != strncasecmp (denomination_alias,
- "coin-",
- strlen ("coin-"))) )
- return; /* not a denomination type definition */
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (ctx->cfg,
- denomination_alias,
- "CIPHER",
- &cipher))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- denomination_alias,
- "CIPHER");
- return;
- }
- if (0 != strcmp (cipher, "CS"))
- {
- GNUNET_free (cipher);
- return; /* Ignore denominations of other types than CS*/
- }
- GNUNET_free (cipher);
-
- denom = GNUNET_new (struct Denomination);
- if (GNUNET_OK !=
- parse_denomination_cfg (ctx->cfg,
- denomination_alias,
- denom))
- {
- ctx->ret = GNUNET_SYSERR;
- GNUNET_free (denom);
- return;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Loading keys for denomination %s\n",
- denom->section);
- {
- char *dname;
-
- GNUNET_asprintf (&dname,
- "%s/%s",
- keydir,
- denom->section);
- GNUNET_break (GNUNET_OK ==
- GNUNET_DISK_directory_create (dname));
- GNUNET_DISK_directory_scan (dname,
- &import_key,
- denom);
- GNUNET_free (dname);
- }
- GNUNET_CONTAINER_DLL_insert (denom_head,
- denom_tail,
- denom);
- update_keys (denom,
- ctx->t,
- &wake);
-}
-
-
-/**
- * Load the various duration values from @a cfg
- *
- * @param cfg configuration to use
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-load_durations (const struct GNUNET_CONFIGURATION_Handle *cfg)
-{
- char *secname;
-
- GNUNET_asprintf (&secname,
- "%s-secmod-cs",
- section);
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_time (cfg,
- secname,
- "OVERLAP_DURATION",
- &overlap_duration))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- secname,
- "OVERLAP_DURATION");
- GNUNET_free (secname);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_time (cfg,
- secname,
- "LOOKAHEAD_SIGN",
- &lookahead_sign))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- secname,
- "LOOKAHEAD_SIGN");
- GNUNET_free (secname);
- return GNUNET_SYSERR;
- }
- GNUNET_free (secname);
- return GNUNET_OK;
-}
-
-
-/**
- * Function run on shutdown. Stops the various jobs (nicely).
- *
- * @param cls NULL
- */
-static void
-do_shutdown (void *cls)
-{
- (void) cls;
- TES_listen_stop ();
- if (NULL != keygen_task)
- {
- GNUNET_SCHEDULER_cancel (keygen_task);
- keygen_task = NULL;
- }
- stop_workers ();
- sem_done (&worker_sem);
-}
-
-
-/**
- * Main function that will be run under the GNUnet scheduler.
- *
- * @param cls closure
- * @param args remaining command-line arguments
- * @param cfgfile name of the configuration file used (for saving, can be NULL!)
- * @param cfg configuration
- */
-static void
-run (void *cls,
- char *const *args,
- const char *cfgfile,
- const struct GNUNET_CONFIGURATION_Handle *cfg)
-{
- static struct TES_Callbacks cb = {
- .dispatch = &cs_work_dispatch,
- .updater = &cs_update_client_keys,
- .init = &cs_client_init
- };
- char *secname;
-
- (void) cls;
- (void) args;
- (void) cfgfile;
- if (GNUNET_TIME_timestamp_cmp (global_now,
- !=,
- global_now_tmp))
- {
- /* The user gave "--now", use it! */
- global_now = global_now_tmp;
- }
- else
- {
- /* get current time again, we may be timetraveling! */
- global_now = GNUNET_TIME_timestamp_get ();
- }
- GNUNET_asprintf (&secname,
- "%s-secmod-cs",
- section);
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_filename (cfg,
- secname,
- "KEY_DIR",
- &keydir))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- secname,
- "KEY_DIR");
- GNUNET_free (secname);
- global_ret = EXIT_NOTCONFIGURED;
- return;
- }
- if (GNUNET_OK !=
- load_durations (cfg))
- {
- global_ret = EXIT_NOTCONFIGURED;
- GNUNET_free (secname);
- return;
- }
- global_ret = TES_listen_start (cfg,
- secname,
- &cb);
- GNUNET_free (secname);
- if (0 != global_ret)
- return;
- sem_init (&worker_sem,
- 0);
- GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
- NULL);
- if (0 == max_workers)
- {
- long lret;
-
- lret = sysconf (_SC_NPROCESSORS_CONF);
- if (lret <= 0)
- lret = 1;
- max_workers = (unsigned int) lret;
- }
- for (unsigned int i = 0; i<max_workers; i++)
- if (GNUNET_OK !=
- start_worker ())
- {
- GNUNET_SCHEDULER_shutdown ();
- return;
- }
- /* Load denominations */
- keys = GNUNET_CONTAINER_multihashmap_create (65536,
- GNUNET_YES);
- {
- struct LoadContext lc = {
- .cfg = cfg,
- .ret = GNUNET_OK,
- .t = global_now
- };
-
- GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
- GNUNET_CONFIGURATION_iterate_sections (cfg,
- &load_denominations,
- &lc);
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- if (GNUNET_OK != lc.ret)
- {
- global_ret = EXIT_FAILURE;
- GNUNET_SCHEDULER_shutdown ();
- return;
- }
- }
- if (NULL == denom_head)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "No CS denominations configured\n");
- TES_wake_clients ();
- return;
- }
- /* start job to keep keys up-to-date; MUST be run before the #listen_task,
- hence with priority. */
- keygen_task = GNUNET_SCHEDULER_add_with_priority (
- GNUNET_SCHEDULER_PRIORITY_URGENT,
- &update_denominations,
- NULL);
-}
-
/**
* The entry point.
@@ -2296,44 +45,31 @@ int
main (int argc,
char **argv)
{
+ struct TALER_SECMOD_Options opts = {
+ .max_workers = 16,
+ .section = "taler-exchange"
+ };
struct GNUNET_GETOPT_CommandLineOption options[] = {
- GNUNET_GETOPT_option_string ('s',
- "section",
- "SECTION",
- "name of the configuration section prefix to use, default is 'taler'",
- &section),
- GNUNET_GETOPT_option_timetravel ('T',
- "timetravel"),
- GNUNET_GETOPT_option_timestamp ('t',
- "time",
- "TIMESTAMP",
- "pretend it is a different time for the update",
- &global_now_tmp),
- GNUNET_GETOPT_option_uint ('w',
- "workers",
- "COUNT",
- "use COUNT workers for parallel processing of batch requests",
- &max_workers),
+ TALER_SECMOD_OPTIONS (&opts),
GNUNET_GETOPT_OPTION_END
};
enum GNUNET_GenericReturnValue ret;
/* Restrict permissions for the key files that we create. */
(void) umask (S_IWGRP | S_IROTH | S_IWOTH | S_IXOTH);
- section = GNUNET_strdup ("taler-exchange");
- global_now_tmp
- = global_now
+ opts.global_now_tmp
+ = opts.global_now
= GNUNET_TIME_timestamp_get ();
ret = GNUNET_PROGRAM_run (TALER_EXCHANGE_project_data (),
argc, argv,
"taler-exchange-secmod-cs",
"Handle private CS key operations for a Taler exchange",
options,
- &run,
- NULL);
+ &TALER_SECMOD_cs_run,
+ &opts);
if (GNUNET_NO == ret)
return EXIT_SUCCESS;
if (GNUNET_SYSERR == ret)
return EXIT_INVALIDARGUMENT;
- return global_ret;
+ return opts.global_ret;
}
diff --git a/src/util/taler-exchange-secmod-eddsa.c b/src/util/taler-exchange-secmod-eddsa.c
index 6b314d21c..0f560c38a 100644
--- a/src/util/taler-exchange-secmod-eddsa.c
+++ b/src/util/taler-exchange-secmod-eddsa.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2021 Taler Systems SA
+ Copyright (C) 2014-2024 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
@@ -34,1124 +34,6 @@
*/
#include "platform.h"
#include "taler_util.h"
-#include "taler-exchange-secmod-eddsa.h"
-#include <gcrypt.h>
-#include <pthread.h>
-#include "taler_error_codes.h"
-#include "taler_signatures.h"
-#include "secmod_common.h"
-#include <poll.h>
-
-
-/**
- * One particular key.
- */
-struct Key
-{
-
- /**
- * Kept in a DLL. Sorted by anchor time.
- */
- struct Key *next;
-
- /**
- * Kept in a DLL. Sorted by anchor time.
- */
- struct Key *prev;
-
- /**
- * Name of the file this key is stored under.
- */
- char *filename;
-
- /**
- * The private key.
- */
- struct TALER_ExchangePrivateKeyP exchange_priv;
-
- /**
- * The public key.
- */
- struct TALER_ExchangePublicKeyP exchange_pub;
-
- /**
- * Time at which this key is supposed to become valid.
- */
- struct GNUNET_TIME_Timestamp anchor;
-
- /**
- * Generation when this key was created or revoked.
- */
- uint64_t key_gen;
-
- /**
- * 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;
-
-};
-
-
-/**
- * Head of DLL of actual keys, sorted by anchor.
- */
-static struct Key *keys_head;
-
-/**
- * Tail of DLL of actual keys.
- */
-static struct Key *keys_tail;
-
-/**
- * How long can a key be used?
- */
-static struct GNUNET_TIME_Relative duration;
-
-/**
- * Return value from main().
- */
-static int global_ret;
-
-/**
- * Time when the key update is executed.
- * Either the actual current time, or a pretended time.
- */
-static struct GNUNET_TIME_Timestamp now;
-
-/**
- * The time for the key update, as passed by the user
- * on the command line.
- */
-static struct GNUNET_TIME_Timestamp now_tmp;
-
-/**
- * Where do we store the keys?
- */
-static char *keydir;
-
-/**
- * Name of the configuration section prefix to use. Usually either "taler-exchange" or
- * "donau". The actual configuration section will then be
- * "$SECTION-secmod-eddsa".
- */
-static char *section;
-
-/**
- * How much should coin creation duration overlap
- * with the next key? Basically, the starting time of two
- * keys is always #duration - #overlap_duration apart.
- */
-static struct GNUNET_TIME_Relative overlap_duration;
-
-/**
- * How long into the future do we pre-generate keys?
- */
-static struct GNUNET_TIME_Relative lookahead_sign;
-
-/**
- * Task run to generate new keys.
- */
-static struct GNUNET_SCHEDULER_Task *keygen_task;
-
-/**
- * Lock for the keys queue.
- */
-static pthread_mutex_t keys_lock;
-
-/**
- * Current key generation.
- */
-static uint64_t key_gen;
-
-
-/**
- * Notify @a client about @a key becoming available.
- *
- * @param[in,out] client the client to notify; possible freed if transmission fails
- * @param key the key to notify @a client about
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-notify_client_key_add (struct TES_Client *client,
- const struct Key *key)
-{
- struct TALER_CRYPTO_EddsaKeyAvailableNotification an = {
- .header.size = htons (sizeof (an)),
- .header.type = htons (TALER_HELPER_EDDSA_MT_AVAIL),
- .anchor_time = GNUNET_TIME_timestamp_hton (key->anchor),
- .duration = GNUNET_TIME_relative_hton (duration),
- .exchange_pub = key->exchange_pub,
- .secm_pub = TES_smpub
- };
-
- TALER_exchange_secmod_eddsa_sign (&key->exchange_pub,
- key->anchor,
- duration,
- &TES_smpriv,
- &an.secm_sig);
- if (GNUNET_OK !=
- TES_transmit (client->csock,
- &an.header))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Client %p must have disconnected\n",
- client);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Notify @a client about @a key being purged.
- *
- * @param[in,out] client the client to notify; possible freed if transmission fails
- * @param key the key to notify @a client about
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-notify_client_key_del (struct TES_Client *client,
- const struct Key *key)
-{
- struct TALER_CRYPTO_EddsaKeyPurgeNotification pn = {
- .header.type = htons (TALER_HELPER_EDDSA_MT_PURGE),
- .header.size = htons (sizeof (pn)),
- .exchange_pub = key->exchange_pub
- };
-
- if (GNUNET_OK !=
- TES_transmit (client->csock,
- &pn.header))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Client %p must have disconnected\n",
- client);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Handle @a client request @a sr to create signature. Create the
- * signature using the respective key and return the result to
- * the client.
- *
- * @param client the client making the request
- * @param sr the request details
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-handle_sign_request (struct TES_Client *client,
- const struct TALER_CRYPTO_EddsaSignRequest *sr)
-{
- const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose = &sr->purpose;
- size_t purpose_size = ntohs (sr->header.size) - sizeof (*sr)
- + sizeof (*purpose);
- struct Key *key;
- struct TALER_CRYPTO_EddsaSignResponse sres = {
- .header.size = htons (sizeof (sres)),
- .header.type = htons (TALER_HELPER_EDDSA_MT_RES_SIGNATURE)
- };
- enum TALER_ErrorCode ec;
-
- if (purpose_size != htonl (purpose->size))
- {
- struct TALER_CRYPTO_EddsaSignFailure sf = {
- .header.size = htons (sizeof (sr)),
- .header.type = htons (TALER_HELPER_EDDSA_MT_RES_SIGN_FAILURE),
- .ec = htonl (TALER_EC_GENERIC_PARAMETER_MALFORMED)
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Signing request failed, request malformed\n");
- return TES_transmit (client->csock,
- &sf.header);
- }
-
- GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
- key = keys_head;
- while ( (NULL != key) &&
- (GNUNET_TIME_absolute_is_past (
- GNUNET_TIME_absolute_add (key->anchor.abs_time,
- duration))) )
- {
- struct Key *nxt = key->next;
-
- if (0 != key->rc)
- break; /* do later */
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Deleting past key %s (expired %s ago)\n",
- TALER_B2S (&nxt->exchange_pub),
- GNUNET_TIME_relative2s (
- GNUNET_TIME_absolute_get_duration (
- GNUNET_TIME_absolute_add (key->anchor.abs_time,
- duration)),
- GNUNET_YES));
- GNUNET_CONTAINER_DLL_remove (keys_head,
- keys_tail,
- key);
- if ( (! key->purge) &&
- (0 != unlink (key->filename)) )
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
- "unlink",
- key->filename);
- GNUNET_free (key->filename);
- GNUNET_free (key);
- key = nxt;
- }
- if (NULL == key)
- {
- GNUNET_break (0);
- ec = TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING;
- }
- else
- {
- GNUNET_assert (key->rc < UINT_MAX);
- key->rc++;
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
-
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_sign_ (&key->exchange_priv.eddsa_priv,
- purpose,
- &sres.exchange_sig.eddsa_signature))
- ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
- else
- ec = TALER_EC_NONE;
- sres.exchange_pub = key->exchange_pub;
- GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
- GNUNET_assert (key->rc > 0);
- key->rc--;
- }
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- if (TALER_EC_NONE != ec)
- {
- struct TALER_CRYPTO_EddsaSignFailure sf = {
- .header.size = htons (sizeof (sf)),
- .header.type = htons (TALER_HELPER_EDDSA_MT_RES_SIGN_FAILURE),
- .ec = htonl ((uint32_t) ec)
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Signing request %p failed, worker failed to produce signature\n",
- client);
- return TES_transmit (client->csock,
- &sf.header);
- }
- return TES_transmit (client->csock,
- &sres.header);
-}
-
-
-/**
- * Initialize key material for key @a key (also on disk).
- *
- * @param[in,out] key to compute key material for
- * @param position where in the DLL will the @a key go
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-setup_key (struct Key *key,
- struct Key *position)
-{
- struct GNUNET_CRYPTO_EddsaPrivateKey priv;
- struct GNUNET_CRYPTO_EddsaPublicKey pub;
-
- GNUNET_CRYPTO_eddsa_key_create (&priv);
- GNUNET_CRYPTO_eddsa_key_get_public (&priv,
- &pub);
- GNUNET_asprintf (&key->filename,
- "%s/%llu",
- keydir,
- (unsigned long long) (key->anchor.abs_time.abs_value_us
- / GNUNET_TIME_UNIT_SECONDS.rel_value_us
- ));
- if (GNUNET_OK !=
- GNUNET_DISK_fn_write (key->filename,
- &priv,
- sizeof (priv),
- GNUNET_DISK_PERM_USER_READ))
- {
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
- "write",
- key->filename);
- return GNUNET_SYSERR;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Setup fresh private key in `%s'\n",
- key->filename);
- key->key_gen = key_gen;
- key->exchange_priv.eddsa_priv = priv;
- key->exchange_pub.eddsa_pub = pub;
- GNUNET_CONTAINER_DLL_insert_after (keys_head,
- keys_tail,
- position,
- key);
- return GNUNET_OK;
-}
-
-
-/**
- * The validity period of a key @a key has expired. Purge it.
- *
- * @param[in] key expired or revoked key to purge
- */
-static void
-purge_key (struct Key *key)
-{
- if (key->purge)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Key %s already purged, skipping\n",
- TALER_B2S (&key->exchange_pub));
- return;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Purging key %s\n",
- TALER_B2S (&key->exchange_pub));
- if (0 != unlink (key->filename))
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
- "unlink",
- key->filename);
- key->purge = true;
- key->key_gen = key_gen;
- GNUNET_free (key->filename);
-}
-
-
-/**
- * A @a client informs us that a key has been revoked.
- * Check if the key is still in use, and if so replace (!)
- * it with a fresh key.
- *
- * @param client the client making the request
- * @param rr the revocation request
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-handle_revoke_request (struct TES_Client *client,
- const struct TALER_CRYPTO_EddsaRevokeRequest *rr)
-{
- struct Key *key;
- struct Key *nkey;
-
- (void) client;
- key = NULL;
- GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
- for (struct Key *pos = keys_head;
- NULL != pos;
- pos = pos->next)
- if (0 == GNUNET_memcmp (&pos->exchange_pub,
- &rr->exchange_pub))
- {
- key = pos;
- break;
- }
- if (NULL == key)
- {
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Revocation request ignored, key unknown\n");
- return GNUNET_OK;
- }
- if (key->purge)
- {
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Revocation request ignored, key %s already revoked\n",
- TALER_B2S (&key->exchange_pub));
- return GNUNET_OK;
- }
- key_gen++;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Revoking key %s, bumping generation to %llu\n",
- TALER_B2S (&key->exchange_pub),
- (unsigned long long) key_gen);
- purge_key (key);
-
- /* Setup replacement key */
- nkey = GNUNET_new (struct Key);
- nkey->anchor = key->anchor;
- if (GNUNET_OK !=
- setup_key (nkey,
- key))
- {
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- GNUNET_break (0);
- GNUNET_SCHEDULER_shutdown ();
- global_ret = EXIT_FAILURE;
- return GNUNET_SYSERR;
- }
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- TES_wake_clients ();
- return GNUNET_OK;
-}
-
-
-/**
- * Handle @a hdr message received from @a client.
- *
- * @param client the client that received the message
- * @param hdr message that was received
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-eddsa_work_dispatch (struct TES_Client *client,
- const struct GNUNET_MessageHeader *hdr)
-{
- uint16_t msize = ntohs (hdr->size);
-
- switch (ntohs (hdr->type))
- {
- case TALER_HELPER_EDDSA_MT_REQ_SIGN:
- if (msize < sizeof (struct TALER_CRYPTO_EddsaSignRequest))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return handle_sign_request (
- client,
- (const struct TALER_CRYPTO_EddsaSignRequest *) hdr);
- case TALER_HELPER_EDDSA_MT_REQ_REVOKE:
- if (msize != sizeof (struct TALER_CRYPTO_EddsaRevokeRequest))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return handle_revoke_request (
- client,
- (const struct TALER_CRYPTO_EddsaRevokeRequest *) hdr);
- default:
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-}
-
-
-/**
- * Send our initial key set to @a client together with the
- * "sync" terminator.
- *
- * @param client the client to inform
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-eddsa_client_init (struct TES_Client *client)
-{
- GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
- for (struct Key *key = keys_head;
- NULL != key;
- key = key->next)
- {
- if (GNUNET_OK !=
- notify_client_key_add (client,
- key))
- {
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- }
- client->key_gen = key_gen;
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- {
- struct GNUNET_MessageHeader synced = {
- .type = htons (TALER_HELPER_EDDSA_SYNCED),
- .size = htons (sizeof (synced))
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Client %p synced\n",
- client);
- if (GNUNET_OK !=
- TES_transmit (client->csock,
- &synced))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Notify @a client about all changes to the keys since
- * the last generation known to the @a client.
- *
- * @param client the client to notify
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-eddsa_update_client_keys (struct TES_Client *client)
-{
- GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Updating client %p to generation %llu\n",
- client,
- (unsigned long long) key_gen);
- for (struct Key *key = keys_head;
- NULL != key;
- key = key->next)
- {
- if (key->key_gen <= client->key_gen)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Skipping key %s, no change since generation %llu\n",
- TALER_B2S (&key->exchange_pub),
- (unsigned long long) client->key_gen);
- continue;
- }
- if (key->purge)
- {
- if (GNUNET_OK !=
- notify_client_key_del (client,
- key))
- {
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- return GNUNET_SYSERR;
- }
- }
- else
- {
- if (GNUNET_OK !=
- notify_client_key_add (client,
- key))
- {
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- return GNUNET_SYSERR;
- }
- }
- }
- client->key_gen = key_gen;
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- return GNUNET_OK;
-}
-
-
-/**
- * Create a new key (we do not have enough).
- *
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-create_key (void)
-{
- struct Key *key;
- struct GNUNET_TIME_Timestamp anchor;
-
- anchor = GNUNET_TIME_timestamp_get ();
- if (NULL != keys_tail)
- {
- struct GNUNET_TIME_Absolute abs;
-
- abs = GNUNET_TIME_absolute_add (keys_tail->anchor.abs_time,
- GNUNET_TIME_relative_subtract (
- duration,
- overlap_duration));
- if (GNUNET_TIME_absolute_cmp (anchor.abs_time,
- <,
- abs))
- anchor = GNUNET_TIME_absolute_to_timestamp (abs);
- }
- key = GNUNET_new (struct Key);
- key->anchor = anchor;
- if (GNUNET_OK !=
- setup_key (key,
- keys_tail))
- {
- GNUNET_break (0);
- GNUNET_free (key);
- GNUNET_SCHEDULER_shutdown ();
- global_ret = EXIT_FAILURE;
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * At what time does the current key set require its next action? Basically,
- * the minimum of the expiration time of the oldest key, and the expiration
- * time of the newest key minus the #lookahead_sign time.
- */
-static struct GNUNET_TIME_Absolute
-key_action_time (void)
-{
- struct Key *nxt;
-
- nxt = keys_head;
- while ( (NULL != nxt) &&
- (nxt->purge) )
- nxt = nxt->next;
- if (NULL == nxt)
- return GNUNET_TIME_UNIT_ZERO_ABS;
- return GNUNET_TIME_absolute_min (
- GNUNET_TIME_absolute_add (nxt->anchor.abs_time,
- duration),
- GNUNET_TIME_absolute_subtract (
- GNUNET_TIME_absolute_subtract (
- GNUNET_TIME_absolute_add (keys_tail->anchor.abs_time,
- duration),
- lookahead_sign),
- overlap_duration));
-}
-
-
-/**
- * Create new keys and expire ancient keys.
- *
- * @param cls NULL
- */
-static void
-update_keys (void *cls)
-{
- bool wake = false;
- struct Key *nxt;
-
- (void) cls;
- keygen_task = NULL;
- GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
- /* create new keys */
- while ( (NULL == keys_tail) ||
- GNUNET_TIME_absolute_is_past (
- GNUNET_TIME_absolute_subtract (
- GNUNET_TIME_absolute_subtract (
- GNUNET_TIME_absolute_add (keys_tail->anchor.abs_time,
- duration),
- lookahead_sign),
- overlap_duration)) )
- {
- if (! wake)
- {
- key_gen++;
- wake = true;
- }
- if (GNUNET_OK !=
- create_key ())
- {
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- GNUNET_break (0);
- global_ret = EXIT_FAILURE;
- GNUNET_SCHEDULER_shutdown ();
- return;
- }
- }
- nxt = keys_head;
- /* purge expired keys */
- while ( (NULL != nxt) &&
- GNUNET_TIME_absolute_is_past (
- GNUNET_TIME_absolute_add (nxt->anchor.abs_time,
- duration)))
- {
- if (! wake)
- {
- key_gen++;
- wake = true;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Purging past key %s (expired %s ago)\n",
- TALER_B2S (&nxt->exchange_pub),
- GNUNET_TIME_relative2s (
- GNUNET_TIME_absolute_get_duration (
- GNUNET_TIME_absolute_add (nxt->anchor.abs_time,
- duration)),
- GNUNET_YES));
- purge_key (nxt);
- nxt = nxt->next;
- }
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- if (wake)
- TES_wake_clients ();
- keygen_task = GNUNET_SCHEDULER_add_at (key_action_time (),
- &update_keys,
- NULL);
-}
-
-
-/**
- * Parse private key from @a filename in @a buf.
- *
- * @param filename name of the file we are parsing, for logging
- * @param buf key material
- * @param buf_size number of bytes in @a buf
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-parse_key (const char *filename,
- const void *buf,
- size_t buf_size)
-{
- struct GNUNET_CRYPTO_EddsaPrivateKey priv;
- char *anchor_s;
- char dummy;
- unsigned long long anchor_ll;
- struct GNUNET_TIME_Timestamp 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 GNUNET_SYSERR;
- }
- 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 GNUNET_SYSERR;
- }
- anchor.abs_time.abs_value_us = anchor_ll
- * GNUNET_TIME_UNIT_SECONDS.rel_value_us;
- if (anchor_ll != anchor.abs_time.abs_value_us
- / GNUNET_TIME_UNIT_SECONDS.rel_value_us)
- {
- /* Integer overflow. Bad, invalid filename. */
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Filename `%s' invalid for key file, skipping\n",
- filename);
- return GNUNET_SYSERR;
- }
- if (buf_size != sizeof (priv))
- {
- /* Parser failure. */
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "File `%s' is malformed, skipping\n",
- filename);
- return GNUNET_SYSERR;
- }
- GNUNET_memcpy (&priv,
- buf,
- buf_size);
-
- {
- struct GNUNET_CRYPTO_EddsaPublicKey pub;
- struct Key *key;
- struct Key *before;
-
- GNUNET_CRYPTO_eddsa_key_get_public (&priv,
- &pub);
- GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
- key = GNUNET_new (struct Key);
- key->exchange_priv.eddsa_priv = priv;
- key->exchange_pub.eddsa_pub = pub;
- key->anchor = anchor;
- key->filename = GNUNET_strdup (filename);
- key->key_gen = key_gen;
- before = NULL;
- for (struct Key *pos = keys_head;
- NULL != pos;
- pos = pos->next)
- {
- if (GNUNET_TIME_timestamp_cmp (pos->anchor, >, anchor))
- break;
- before = pos;
- }
- GNUNET_CONTAINER_DLL_insert_after (keys_head,
- keys_tail,
- before,
- key);
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Imported key from `%s'\n",
- filename);
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Import a private key from @a filename.
- *
- * @param cls NULL
- * @param filename name of a file in the directory
- */
-static enum GNUNET_GenericReturnValue
-import_key (void *cls,
- const char *filename)
-{
- struct GNUNET_DISK_FileHandle *fh;
- struct GNUNET_DISK_MapHandle *map;
- void *ptr;
- int fd;
- struct stat sbuf;
-
- (void) cls;
- {
- struct stat lsbuf;
-
- if (0 != lstat (filename,
- &lsbuf))
- {
- GNUNET_log_strerror_file (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_RDONLY | O_CLOEXEC);
- if (-1 == fd)
- {
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
- "open",
- filename);
- return GNUNET_OK;
- }
- if (0 != fstat (fd,
- &sbuf))
- {
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
- "stat",
- filename);
- GNUNET_break (0 == close (fd));
- 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);
- GNUNET_break (0 == close (fd));
- 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_file (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_file (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_file (GNUNET_ERROR_TYPE_WARNING,
- "mmap",
- filename);
- GNUNET_DISK_file_close (fh);
- return GNUNET_OK;
- }
- (void) parse_key (filename,
- ptr,
- (size_t) sbuf.st_size);
- GNUNET_DISK_file_unmap (map);
- GNUNET_DISK_file_close (fh);
- return GNUNET_OK;
-}
-
-
-/**
- * Load the various duration values from @a kcfg.
- *
- * @param cfg configuration to use
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-load_durations (const struct GNUNET_CONFIGURATION_Handle *cfg)
-{
- char *secname;
-
- GNUNET_asprintf (&secname,
- "%s-secmod-eddsa",
- section);
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_time (cfg,
- secname,
- "OVERLAP_DURATION",
- &overlap_duration))
- {
- GNUNET_free (secname);
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- secname,
- "OVERLAP_DURATION");
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_time (cfg,
- secname,
- "DURATION",
- &duration))
- {
- GNUNET_free (secname);
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- secname,
- "DURATION");
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_time (cfg,
- secname,
- "LOOKAHEAD_SIGN",
- &lookahead_sign))
- {
- GNUNET_free (secname);
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- secname,
- "LOOKAHEAD_SIGN");
- return GNUNET_SYSERR;
- }
- GNUNET_free (secname);
- return GNUNET_OK;
-}
-
-
-/**
- * Function run on shutdown. Stops the various jobs (nicely).
- *
- * @param cls NULL
- */
-static void
-do_shutdown (void *cls)
-{
- (void) cls;
- TES_listen_stop ();
- if (NULL != keygen_task)
- {
- GNUNET_SCHEDULER_cancel (keygen_task);
- keygen_task = NULL;
- }
-}
-
-
-/**
- * Main function that will be run under the GNUnet scheduler.
- *
- * @param cls closure
- * @param args remaining command-line arguments
- * @param cfgfile name of the configuration file used (for saving, can be NULL!)
- * @param cfg configuration
- */
-static void
-run (void *cls,
- char *const *args,
- const char *cfgfile,
- const struct GNUNET_CONFIGURATION_Handle *cfg)
-{
- static struct TES_Callbacks cb = {
- .dispatch = eddsa_work_dispatch,
- .updater = eddsa_update_client_keys,
- .init = eddsa_client_init
- };
- char *secname;
-
- (void) cls;
- (void) args;
- (void) cfgfile;
- if (GNUNET_TIME_timestamp_cmp (now, !=, now_tmp))
- {
- /* The user gave "--now", use it! */
- now = now_tmp;
- }
- else
- {
- /* get current time again, we may be timetraveling! */
- now = GNUNET_TIME_timestamp_get ();
- }
- GNUNET_asprintf (&secname,
- "%s-secmod-eddsa",
- section);
- if (GNUNET_OK !=
- load_durations (cfg))
- {
- global_ret = EXIT_NOTCONFIGURED;
- return;
- }
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_filename (cfg,
- secname,
- "KEY_DIR",
- &keydir))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- secname,
- "KEY_DIR");
- GNUNET_free (secname);
- global_ret = EXIT_NOTCONFIGURED;
- return;
- }
- GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
- NULL);
- global_ret = TES_listen_start (cfg,
- secname,
- &cb);
- if (0 != global_ret)
- return;
- /* Load keys */
- GNUNET_break (GNUNET_OK ==
- GNUNET_DISK_directory_create (keydir));
- GNUNET_DISK_directory_scan (keydir,
- &import_key,
- NULL);
- if ( (NULL != keys_head) &&
- (GNUNET_TIME_absolute_is_future (keys_head->anchor.abs_time)) )
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Existing anchor is in %s the future. Refusing to start\n",
- GNUNET_TIME_relative2s (
- GNUNET_TIME_absolute_get_remaining (
- keys_head->anchor.abs_time),
- GNUNET_YES));
- global_ret = EXIT_FAILURE;
- GNUNET_SCHEDULER_shutdown ();
- return;
- }
- /* start job to keep keys up-to-date; MUST be run before the #listen_task,
- hence with priority. */
- keygen_task = GNUNET_SCHEDULER_add_with_priority (
- GNUNET_SCHEDULER_PRIORITY_URGENT,
- &update_keys,
- NULL);
-}
/**
@@ -1165,38 +47,31 @@ int
main (int argc,
char **argv)
{
+ struct TALER_SECMOD_Options opts = {
+ .max_workers = 16,
+ .section = "taler-exchange"
+ };
struct GNUNET_GETOPT_CommandLineOption options[] = {
- GNUNET_GETOPT_option_string ('s',
- "section",
- "SECTION",
- "name of the configuration section prefix to use, default is 'taler'",
- &section),
- GNUNET_GETOPT_option_timetravel ('T',
- "timetravel"),
- GNUNET_GETOPT_option_timestamp ('t',
- "time",
- "TIMESTAMP",
- "pretend it is a different time for the update",
- &now_tmp),
+ TALER_SECMOD_OPTIONS (&opts),
GNUNET_GETOPT_OPTION_END
};
enum GNUNET_GenericReturnValue ret;
/* Restrict permissions for the key files that we create. */
(void) umask (S_IWGRP | S_IROTH | S_IWOTH | S_IXOTH);
- section = GNUNET_strdup ("taler-exchange");
- now_tmp = now = GNUNET_TIME_timestamp_get ();
+ opts.global_now_tmp
+ = opts.global_now = GNUNET_TIME_timestamp_get ();
ret = GNUNET_PROGRAM_run (TALER_EXCHANGE_project_data (),
argc,
argv,
"taler-exchange-secmod-eddsa",
"Handle private EDDSA key operations for a Taler exchange",
options,
- &run,
- NULL);
+ &TALER_SECMOD_eddsa_run,
+ &opts);
if (GNUNET_NO == ret)
return EXIT_SUCCESS;
if (GNUNET_SYSERR == ret)
return EXIT_INVALIDARGUMENT;
- return global_ret;
+ return opts.global_ret;
}
diff --git a/src/util/taler-exchange-secmod-rsa.c b/src/util/taler-exchange-secmod-rsa.c
index dac04c815..814766238 100644
--- a/src/util/taler-exchange-secmod-rsa.c
+++ b/src/util/taler-exchange-secmod-rsa.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2022 Taler Systems SA
+ Copyright (C) 2014-2024 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
@@ -33,2048 +33,6 @@
*/
#include "platform.h"
#include "taler_util.h"
-#include "taler-exchange-secmod-rsa.h"
-#include <gcrypt.h>
-#include <pthread.h>
-#include "taler_error_codes.h"
-#include "taler_signatures.h"
-#include "secmod_common.h"
-#include <poll.h>
-
-
-/**
- * Information we keep per denomination.
- */
-struct Denomination;
-
-
-/**
- * One particular denomination key.
- */
-struct DenominationKey
-{
-
- /**
- * Kept in a DLL of the respective denomination. Sorted by anchor time.
- */
- struct DenominationKey *next;
-
- /**
- * Kept in a DLL of the respective denomination. Sorted by anchor time.
- */
- struct DenominationKey *prev;
-
- /**
- * Denomination this key belongs to.
- */
- struct Denomination *denom;
-
- /**
- * Name of the file this key is stored under.
- */
- char *filename;
-
- /**
- * The private key of the denomination.
- */
- struct GNUNET_CRYPTO_RsaPrivateKey *denom_priv;
-
- /**
- * The public key of the denomination.
- */
- struct GNUNET_CRYPTO_RsaPublicKey *denom_pub;
-
- /**
- * Message to transmit to clients to introduce this public key.
- */
- struct TALER_CRYPTO_RsaKeyAvailableNotification *an;
-
- /**
- * Hash of this denomination's public key.
- */
- struct TALER_RsaPubHashP h_rsa;
-
- /**
- * Time at which this key is supposed to become valid.
- */
- struct GNUNET_TIME_Timestamp anchor;
-
- /**
- * Generation when this key was created or revoked.
- */
- uint64_t key_gen;
-
- /**
- * 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;
-
-};
-
-
-struct Denomination
-{
-
- /**
- * Kept in a DLL. Sorted by #denomination_action_time().
- */
- struct Denomination *next;
-
- /**
- * Kept in a DLL. Sorted by #denomination_action_time().
- */
- struct Denomination *prev;
-
- /**
- * Head of DLL of actual keys of this denomination.
- */
- struct DenominationKey *keys_head;
-
- /**
- * Tail of DLL of actual keys of this denomination.
- */
- struct DenominationKey *keys_tail;
-
- /**
- * 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
- * anonymity set.
- */
- struct GNUNET_TIME_Relative duration_withdraw;
-
- /**
- * What is the configuration section of this denomination type? Also used
- * for the directory name where the denomination keys are stored.
- */
- char *section;
-
- /**
- * Length of (new) RSA keys (in bits).
- */
- uint32_t rsa_keysize;
-};
-
-
-/**
- * A semaphore.
- */
-struct Semaphore
-{
- /**
- * Mutex for the semaphore.
- */
- pthread_mutex_t mutex;
-
- /**
- * Condition variable for the semaphore.
- */
- pthread_cond_t cv;
-
- /**
- * Counter of the semaphore.
- */
- unsigned int ctr;
-};
-
-
-/**
- * Job in a batch sign request.
- */
-struct BatchJob;
-
-/**
- * Handle for a thread that does work in batch signing.
- */
-struct Worker
-{
- /**
- * Kept in a DLL.
- */
- struct Worker *prev;
-
- /**
- * Kept in a DLL.
- */
- struct Worker *next;
-
- /**
- * Job this worker should do next.
- */
- struct BatchJob *job;
-
- /**
- * Semaphore to signal the worker that a job is available.
- */
- struct Semaphore sem;
-
- /**
- * Handle for this thread.
- */
- pthread_t pt;
-
- /**
- * Set to true if the worker should terminate.
- */
- bool do_shutdown;
-};
-
-
-/**
- * Job in a batch sign request.
- */
-struct BatchJob
-{
- /**
- * Request we are working on.
- */
- const struct TALER_CRYPTO_SignRequest *sr;
-
- /**
- * Thread doing the work.
- */
- struct Worker *worker;
-
- /**
- * Result with the signature.
- */
- struct GNUNET_CRYPTO_RsaSignature *rsa_signature;
-
- /**
- * Semaphore to signal that the job is finished.
- */
- struct Semaphore sem;
-
- /**
- * Computation status.
- */
- enum TALER_ErrorCode ec;
-
-};
-
-
-/**
- * Head of DLL of workers ready for more work.
- */
-static struct Worker *worker_head;
-
-/**
- * Tail of DLL of workers ready for more work.
- */
-static struct Worker *worker_tail;
-
-/**
- * Lock for manipulating the worker DLL.
- */
-static pthread_mutex_t worker_lock;
-
-/**
- * Total number of workers that were started.
- */
-static unsigned int workers;
-
-/**
- * Semaphore used to grab a worker.
- */
-static struct Semaphore worker_sem;
-
-/**
- * Return value from main().
- */
-static int global_ret;
-
-/**
- * Time when the key update is executed.
- * Either the actual current time, or a pretended time.
- */
-static struct GNUNET_TIME_Timestamp global_now;
-
-/**
- * The time for the key update, as passed by the user
- * on the command line.
- */
-static struct GNUNET_TIME_Timestamp global_now_tmp;
-
-/**
- * Where do we store the keys?
- */
-static char *keydir;
-
-/**
- * Name of the configuration section prefix to use. Usually either "taler-exchange" or
- * "donau". The actual configuration section will then be
- * "$SECTION-secmod-rsa".
- */
-static char *section;
-
-/**
- * How much should coin creation (@e duration_withdraw) duration overlap
- * with the next denomination? Basically, the starting time of two
- * denominations is always @e duration_withdraw - #overlap_duration apart.
- */
-static struct GNUNET_TIME_Relative overlap_duration;
-
-/**
- * How long into the future do we pre-generate keys?
- */
-static struct GNUNET_TIME_Relative lookahead_sign;
-
-/**
- * All of our denominations, in a DLL. Sorted?
- */
-static struct Denomination *denom_head;
-
-/**
- * All of our denominations, in a DLL. Sorted?
- */
-static struct Denomination *denom_tail;
-
-/**
- * Map of hashes of public (RSA) keys to `struct DenominationKey *`
- * with the respective private keys.
- */
-static struct GNUNET_CONTAINER_MultiHashMap *keys;
-
-/**
- * Task run to generate new keys.
- */
-static struct GNUNET_SCHEDULER_Task *keygen_task;
-
-/**
- * Lock for the keys queue.
- */
-static pthread_mutex_t keys_lock;
-
-/**
- * Current key generation.
- */
-static uint64_t key_gen;
-
-/**
- * Number of workers to launch. Note that connections to
- * exchanges are NOT workers.
- */
-static unsigned int max_workers = 16;
-
-
-/**
- * Generate the announcement message for @a dk.
- *
- * @param[in,out] dk denomination key to generate the announcement for
- */
-static void
-generate_response (struct DenominationKey *dk)
-{
- struct Denomination *denom = dk->denom;
- size_t nlen = strlen (denom->section) + 1;
- struct TALER_CRYPTO_RsaKeyAvailableNotification *an;
- size_t buf_len;
- void *buf;
- void *p;
- size_t tlen;
-
- buf_len = GNUNET_CRYPTO_rsa_public_key_encode (dk->denom_pub,
- &buf);
- GNUNET_assert (buf_len < UINT16_MAX);
- GNUNET_assert (nlen < UINT16_MAX);
- tlen = buf_len + nlen + sizeof (*an);
- GNUNET_assert (tlen < UINT16_MAX);
- an = GNUNET_malloc (tlen);
- an->header.size = htons ((uint16_t) tlen);
- an->header.type = htons (TALER_HELPER_RSA_MT_AVAIL);
- an->pub_size = htons ((uint16_t) buf_len);
- an->section_name_len = htons ((uint16_t) nlen);
- an->anchor_time = GNUNET_TIME_timestamp_hton (dk->anchor);
- an->duration_withdraw = GNUNET_TIME_relative_hton (denom->duration_withdraw);
- TALER_exchange_secmod_rsa_sign (&dk->h_rsa,
- denom->section,
- dk->anchor,
- denom->duration_withdraw,
- &TES_smpriv,
- &an->secm_sig);
- an->secm_pub = TES_smpub;
- p = (void *) &an[1];
- GNUNET_memcpy (p,
- buf,
- buf_len);
- GNUNET_free (buf);
- GNUNET_memcpy (p + buf_len,
- denom->section,
- nlen);
- dk->an = an;
-}
-
-
-/**
- * Do the actual signing work.
- *
- * @param h_rsa key to sign with
- * @param bm blinded message to sign
- * @param[out] rsa_signaturep set to the RSA signature
- * @return #TALER_EC_NONE on success
- */
-static enum TALER_ErrorCode
-do_sign (const struct TALER_RsaPubHashP *h_rsa,
- const struct GNUNET_CRYPTO_RsaBlindedMessage *bm,
- struct GNUNET_CRYPTO_RsaSignature **rsa_signaturep)
-{
- struct DenominationKey *dk;
- struct GNUNET_CRYPTO_RsaSignature *rsa_signature;
- struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
-
- GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
- dk = GNUNET_CONTAINER_multihashmap_get (keys,
- &h_rsa->hash);
- if (NULL == dk)
- {
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Signing request failed, denomination key %s unknown\n",
- GNUNET_h2s (&h_rsa->hash));
- return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
- }
- if (GNUNET_TIME_absolute_is_future (dk->anchor.abs_time))
- {
- /* it is too early */
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Signing request failed, denomination key %s is not yet valid\n",
- GNUNET_h2s (&h_rsa->hash));
- return TALER_EC_EXCHANGE_DENOMINATION_HELPER_TOO_EARLY;
- }
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Received request to sign over %u bytes with key %s\n",
- (unsigned int) bm->blinded_msg_size,
- GNUNET_h2s (&h_rsa->hash));
- GNUNET_assert (dk->rc < UINT_MAX);
- dk->rc++;
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- rsa_signature
- = GNUNET_CRYPTO_rsa_sign_blinded (dk->denom_priv,
- bm);
- GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
- GNUNET_assert (dk->rc > 0);
- dk->rc--;
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- if (NULL == rsa_signature)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Signing request failed, worker failed to produce signature\n");
- return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
- }
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Sending RSA signature after %s\n",
- GNUNET_TIME_relative2s (
- GNUNET_TIME_absolute_get_duration (now),
- GNUNET_YES));
- *rsa_signaturep = rsa_signature;
- return TALER_EC_NONE;
-}
-
-
-/**
- * Generate error response that signing failed.
- *
- * @param client client to send response to
- * @param ec error code to include
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-fail_sign (struct TES_Client *client,
- enum TALER_ErrorCode ec)
-{
- struct TALER_CRYPTO_SignFailure sf = {
- .header.size = htons (sizeof (sf)),
- .header.type = htons (TALER_HELPER_RSA_MT_RES_SIGN_FAILURE),
- .ec = htonl (ec)
- };
-
- return TES_transmit (client->csock,
- &sf.header);
-}
-
-
-/**
- * Generate signature response.
- *
- * @param client client to send response to
- * @param[in] rsa_signature signature to send, freed by this function
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-send_signature (struct TES_Client *client,
- struct GNUNET_CRYPTO_RsaSignature *rsa_signature)
-{
- struct TALER_CRYPTO_SignResponse *sr;
- void *buf;
- size_t buf_size;
- size_t tsize;
- enum GNUNET_GenericReturnValue ret;
-
- buf_size = GNUNET_CRYPTO_rsa_signature_encode (rsa_signature,
- &buf);
- GNUNET_CRYPTO_rsa_signature_free (rsa_signature);
- tsize = sizeof (*sr) + buf_size;
- GNUNET_assert (tsize < UINT16_MAX);
- sr = GNUNET_malloc (tsize);
- sr->header.size = htons (tsize);
- sr->header.type = htons (TALER_HELPER_RSA_MT_RES_SIGNATURE);
- GNUNET_memcpy (&sr[1],
- buf,
- buf_size);
- GNUNET_free (buf);
- ret = TES_transmit (client->csock,
- &sr->header);
- GNUNET_free (sr);
- return ret;
-}
-
-
-/**
- * Handle @a client request @a sr to create signature. Create the
- * signature using the respective key and return the result to
- * the client.
- *
- * @param client the client making the request
- * @param sr the request details
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-handle_sign_request (struct TES_Client *client,
- const struct TALER_CRYPTO_SignRequest *sr)
-{
- struct GNUNET_CRYPTO_RsaBlindedMessage bm = {
- .blinded_msg = (void *) &sr[1],
- .blinded_msg_size = ntohs (sr->header.size) - sizeof (*sr)
- };
- struct GNUNET_CRYPTO_RsaSignature *rsa_signature;
- enum TALER_ErrorCode ec;
-
- ec = do_sign (&sr->h_rsa,
- &bm,
- &rsa_signature);
- if (TALER_EC_NONE != ec)
- {
- return fail_sign (client,
- ec);
- }
- return send_signature (client,
- rsa_signature);
-}
-
-
-/**
- * Initialize a semaphore @a sem with a value of @a val.
- *
- * @param[out] sem semaphore to initialize
- * @param val initial value of the semaphore
- */
-static void
-sem_init (struct Semaphore *sem,
- unsigned int val)
-{
- GNUNET_assert (0 ==
- pthread_mutex_init (&sem->mutex,
- NULL));
- GNUNET_assert (0 ==
- pthread_cond_init (&sem->cv,
- NULL));
- sem->ctr = val;
-}
-
-
-/**
- * Decrement semaphore, blocks until this is possible.
- *
- * @param[in,out] sem semaphore to decrement
- */
-static void
-sem_down (struct Semaphore *sem)
-{
- GNUNET_assert (0 == pthread_mutex_lock (&sem->mutex));
- while (0 == sem->ctr)
- {
- pthread_cond_wait (&sem->cv,
- &sem->mutex);
- }
- sem->ctr--;
- GNUNET_assert (0 == pthread_mutex_unlock (&sem->mutex));
-}
-
-
-/**
- * Increment semaphore, blocks until this is possible.
- *
- * @param[in,out] sem semaphore to decrement
- */
-static void
-sem_up (struct Semaphore *sem)
-{
- GNUNET_assert (0 == pthread_mutex_lock (&sem->mutex));
- sem->ctr++;
- GNUNET_assert (0 == pthread_mutex_unlock (&sem->mutex));
- pthread_cond_signal (&sem->cv);
-}
-
-
-/**
- * Release resources used by @a sem.
- *
- * @param[in] sem semaphore to release (except the memory itself)
- */
-static void
-sem_done (struct Semaphore *sem)
-{
- GNUNET_break (0 == pthread_cond_destroy (&sem->cv));
- GNUNET_break (0 == pthread_mutex_destroy (&sem->mutex));
-}
-
-
-/**
- * Main logic of a worker thread. Grabs work, does it,
- * grabs more work.
- *
- * @param cls a `struct Worker *`
- * @returns cls
- */
-static void *
-worker (void *cls)
-{
- struct Worker *w = cls;
-
- while (true)
- {
- GNUNET_assert (0 == pthread_mutex_lock (&worker_lock));
- GNUNET_CONTAINER_DLL_insert (worker_head,
- worker_tail,
- w);
- GNUNET_assert (0 == pthread_mutex_unlock (&worker_lock));
- sem_up (&worker_sem);
- sem_down (&w->sem);
- if (w->do_shutdown)
- break;
- {
- struct BatchJob *bj = w->job;
- const struct TALER_CRYPTO_SignRequest *sr = bj->sr;
- struct GNUNET_CRYPTO_RsaBlindedMessage bm = {
- .blinded_msg = (void *) &sr[1],
- .blinded_msg_size = ntohs (sr->header.size) - sizeof (*sr)
- };
-
- bj->ec = do_sign (&sr->h_rsa,
- &bm,
- &bj->rsa_signature);
- sem_up (&bj->sem);
- w->job = NULL;
- }
- }
- return w;
-}
-
-
-/**
- * Start batch job @a bj to sign @a sr.
- *
- * @param sr signature request to answer
- * @param[out] bj job data structure
- */
-static void
-start_job (const struct TALER_CRYPTO_SignRequest *sr,
- struct BatchJob *bj)
-{
- sem_init (&bj->sem,
- 0);
- bj->sr = sr;
- sem_down (&worker_sem);
- GNUNET_assert (0 == pthread_mutex_lock (&worker_lock));
- bj->worker = worker_head;
- GNUNET_CONTAINER_DLL_remove (worker_head,
- worker_tail,
- bj->worker);
- GNUNET_assert (0 == pthread_mutex_unlock (&worker_lock));
- bj->worker->job = bj;
- sem_up (&bj->worker->sem);
-}
-
-
-/**
- * Finish a job @a bj for a @a client.
- *
- * @param client who made the request
- * @param[in,out] bj job to finish
- */
-static void
-finish_job (struct TES_Client *client,
- struct BatchJob *bj)
-{
- sem_down (&bj->sem);
- sem_done (&bj->sem);
- if (TALER_EC_NONE != bj->ec)
- {
- fail_sign (client,
- bj->ec);
- return;
- }
- GNUNET_assert (NULL != bj->rsa_signature);
- send_signature (client,
- bj->rsa_signature);
- bj->rsa_signature = NULL; /* freed in send_signature */
-}
-
-
-/**
- * Handle @a client request @a sr to create a batch of signature. Creates the
- * signatures using the respective key and return the results to the client.
- *
- * @param client the client making the request
- * @param bsr the request details
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-handle_batch_sign_request (struct TES_Client *client,
- const struct TALER_CRYPTO_BatchSignRequest *bsr)
-{
- uint32_t bs = ntohl (bsr->batch_size);
- uint16_t size = ntohs (bsr->header.size) - sizeof (*bsr);
- const void *off = (const void *) &bsr[1];
- unsigned int idx = 0;
- struct BatchJob jobs[bs];
- bool failure = false;
-
- if (bs > TALER_MAX_FRESH_COINS)
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- while ( (bs > 0) &&
- (size > sizeof (struct TALER_CRYPTO_SignRequest)) )
- {
- const struct TALER_CRYPTO_SignRequest *sr = off;
- uint16_t s = ntohs (sr->header.size);
-
- if (s > size)
- {
- failure = true;
- bs = idx;
- break;
- }
- start_job (sr,
- &jobs[idx++]);
- off += s;
- size -= s;
- }
- GNUNET_break_op (0 == size);
- bs = GNUNET_MIN (bs,
- idx);
- for (unsigned int i = 0; i<bs; i++)
- finish_job (client,
- &jobs[i]);
- if (failure)
- {
- struct TALER_CRYPTO_SignFailure sf = {
- .header.size = htons (sizeof (sf)),
- .header.type = htons (TALER_HELPER_RSA_MT_RES_BATCH_FAILURE),
- .ec = htonl (TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE)
- };
-
- GNUNET_break (0);
- return TES_transmit (client->csock,
- &sf.header);
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Start worker thread for batch processing.
- *
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-start_worker (void)
-{
- struct Worker *w;
-
- w = GNUNET_new (struct Worker);
- sem_init (&w->sem,
- 0);
- if (0 != pthread_create (&w->pt,
- NULL,
- &worker,
- w))
- {
- GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
- "pthread_create");
- GNUNET_free (w);
- return GNUNET_SYSERR;
- }
- workers++;
- return GNUNET_OK;
-}
-
-
-/**
- * Stop all worker threads.
- */
-static void
-stop_workers (void)
-{
- while (workers > 0)
- {
- struct Worker *w;
- void *result;
-
- sem_down (&worker_sem);
- GNUNET_assert (0 == pthread_mutex_lock (&worker_lock));
- w = worker_head;
- GNUNET_CONTAINER_DLL_remove (worker_head,
- worker_tail,
- w);
- GNUNET_assert (0 == pthread_mutex_unlock (&worker_lock));
- w->do_shutdown = true;
- sem_up (&w->sem);
- pthread_join (w->pt,
- &result);
- GNUNET_assert (result == w);
- sem_done (&w->sem);
- GNUNET_free (w);
- workers--;
- }
-}
-
-
-/**
- * Initialize key material for denomination key @a dk (also on disk).
- *
- * @param[in,out] dk denomination key to compute key material for
- * @param position where in the DLL will the @a dk go
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-setup_key (struct DenominationKey *dk,
- struct DenominationKey *position)
-{
- struct Denomination *denom = dk->denom;
- struct GNUNET_CRYPTO_RsaPrivateKey *priv;
- struct GNUNET_CRYPTO_RsaPublicKey *pub;
- size_t buf_size;
- void *buf;
-
- priv = GNUNET_CRYPTO_rsa_private_key_create (denom->rsa_keysize);
- if (NULL == priv)
- {
- GNUNET_break (0);
- GNUNET_SCHEDULER_shutdown ();
- global_ret = EXIT_FAILURE;
- 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);
- return GNUNET_SYSERR;
- }
- buf_size = GNUNET_CRYPTO_rsa_private_key_encode (priv,
- &buf);
- GNUNET_CRYPTO_rsa_public_key_hash (pub,
- &dk->h_rsa.hash);
- GNUNET_asprintf (&dk->filename,
- "%s/%s/%llu",
- keydir,
- denom->section,
- (unsigned long long) (dk->anchor.abs_time.abs_value_us
- / GNUNET_TIME_UNIT_SECONDS.rel_value_us
- ));
- if (GNUNET_OK !=
- 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);
- return GNUNET_SYSERR;
- }
- GNUNET_free (buf);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Setup fresh private key %s at %s in `%s' (generation #%llu)\n",
- GNUNET_h2s (&dk->h_rsa.hash),
- GNUNET_TIME_timestamp2s (dk->anchor),
- dk->filename,
- (unsigned long long) key_gen);
- dk->denom_priv = priv;
- dk->denom_pub = pub;
- dk->key_gen = key_gen;
- generate_response (dk);
- if (GNUNET_OK !=
- GNUNET_CONTAINER_multihashmap_put (
- keys,
- &dk->h_rsa.hash,
- dk,
- GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Duplicate private key created! Terminating.\n");
- GNUNET_CRYPTO_rsa_private_key_free (dk->denom_priv);
- GNUNET_CRYPTO_rsa_public_key_free (dk->denom_pub);
- GNUNET_free (dk->filename);
- GNUNET_free (dk->an);
- GNUNET_free (dk);
- return GNUNET_SYSERR;
- }
- GNUNET_CONTAINER_DLL_insert_after (denom->keys_head,
- denom->keys_tail,
- position,
- dk);
- return GNUNET_OK;
-}
-
-
-/**
- * The withdraw period of a key @a dk has expired. Purge it.
- *
- * @param[in] dk expired denomination key to purge
- */
-static void
-purge_key (struct DenominationKey *dk)
-{
- if (dk->purge)
- return;
- if (0 != unlink (dk->filename))
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
- "unlink",
- dk->filename);
- GNUNET_free (dk->filename);
- dk->purge = true;
- dk->key_gen = key_gen;
-}
-
-
-/**
- * A @a client informs us that a key has been revoked.
- * Check if the key is still in use, and if so replace (!)
- * it with a fresh key.
- *
- * @param client the client making the request
- * @param rr the revocation request
- */
-static enum GNUNET_GenericReturnValue
-handle_revoke_request (struct TES_Client *client,
- const struct TALER_CRYPTO_RevokeRequest *rr)
-{
- struct DenominationKey *dk;
- struct DenominationKey *ndk;
- struct Denomination *denom;
-
- (void) client;
- GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
- dk = GNUNET_CONTAINER_multihashmap_get (keys,
- &rr->h_rsa.hash);
- if (NULL == dk)
- {
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Revocation request ignored, denomination key %s unknown\n",
- GNUNET_h2s (&rr->h_rsa.hash));
- return GNUNET_OK;
- }
- if (dk->purge)
- {
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Revocation request ignored, denomination key %s already revoked\n",
- GNUNET_h2s (&rr->h_rsa.hash));
- return GNUNET_OK;
- }
-
- key_gen++;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Revoking key %s, bumping generation to %llu\n",
- GNUNET_h2s (&rr->h_rsa.hash),
- (unsigned long long) key_gen);
- purge_key (dk);
-
- /* Setup replacement key */
- denom = dk->denom;
- ndk = GNUNET_new (struct DenominationKey);
- ndk->denom = denom;
- ndk->anchor = dk->anchor;
- if (GNUNET_OK !=
- setup_key (ndk,
- dk))
- {
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- GNUNET_break (0);
- GNUNET_SCHEDULER_shutdown ();
- global_ret = EXIT_FAILURE;
- return GNUNET_SYSERR;
- }
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- TES_wake_clients ();
- return GNUNET_OK;
-}
-
-
-/**
- * Handle @a hdr message received from @a client.
- *
- * @param client the client that received the message
- * @param hdr message that was received
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-rsa_work_dispatch (struct TES_Client *client,
- const struct GNUNET_MessageHeader *hdr)
-{
- uint16_t msize = ntohs (hdr->size);
-
- switch (ntohs (hdr->type))
- {
- case TALER_HELPER_RSA_MT_REQ_SIGN:
- if (msize <= sizeof (struct TALER_CRYPTO_SignRequest))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return handle_sign_request (
- client,
- (const struct TALER_CRYPTO_SignRequest *) hdr);
- case TALER_HELPER_RSA_MT_REQ_REVOKE:
- if (msize != sizeof (struct TALER_CRYPTO_RevokeRequest))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return handle_revoke_request (
- client,
- (const struct TALER_CRYPTO_RevokeRequest *) hdr);
- case TALER_HELPER_RSA_MT_REQ_BATCH_SIGN:
- if (msize <= sizeof (struct TALER_CRYPTO_BatchSignRequest))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return handle_batch_sign_request (
- client,
- (const struct TALER_CRYPTO_BatchSignRequest *) hdr);
- default:
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-}
-
-
-/**
- * Send our initial key set to @a client together with the
- * "sync" terminator.
- *
- * @param client the client to inform
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-rsa_client_init (struct TES_Client *client)
-{
- size_t obs = 0;
- char *buf;
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Initializing new client %p\n",
- client);
- GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
- for (struct Denomination *denom = denom_head;
- NULL != denom;
- denom = denom->next)
- {
- for (struct DenominationKey *dk = denom->keys_head;
- NULL != dk;
- dk = dk->next)
- {
- GNUNET_assert (obs + ntohs (dk->an->header.size)
- > obs);
- obs += ntohs (dk->an->header.size);
- }
- }
- buf = GNUNET_malloc (obs);
- obs = 0;
- for (struct Denomination *denom = denom_head;
- NULL != denom;
- denom = denom->next)
- {
- for (struct DenominationKey *dk = denom->keys_head;
- NULL != dk;
- dk = dk->next)
- {
- GNUNET_memcpy (&buf[obs],
- dk->an,
- ntohs (dk->an->header.size));
- GNUNET_assert (obs + ntohs (dk->an->header.size)
- > obs);
- obs += ntohs (dk->an->header.size);
- }
- }
- client->key_gen = key_gen;
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- if (GNUNET_OK !=
- TES_transmit_raw (client->csock,
- obs,
- buf))
- {
- GNUNET_free (buf);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Client %p must have disconnected\n",
- client);
- return GNUNET_SYSERR;
- }
- GNUNET_free (buf);
- {
- struct GNUNET_MessageHeader synced = {
- .type = htons (TALER_HELPER_RSA_SYNCED),
- .size = htons (sizeof (synced))
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Sending RSA SYNCED message to %p\n",
- client);
- if (GNUNET_OK !=
- TES_transmit (client->csock,
- &synced))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Notify @a client about all changes to the keys since
- * the last generation known to the @a client.
- *
- * @param client the client to notify
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-rsa_update_client_keys (struct TES_Client *client)
-{
- size_t obs = 0;
- char *buf;
- enum GNUNET_GenericReturnValue ret;
-
- GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
- for (struct Denomination *denom = denom_head;
- NULL != denom;
- denom = denom->next)
- {
- for (struct DenominationKey *key = denom->keys_head;
- NULL != key;
- key = key->next)
- {
- if (key->key_gen <= client->key_gen)
- continue;
- if (key->purge)
- obs += sizeof (struct TALER_CRYPTO_RsaKeyPurgeNotification);
- else
- obs += ntohs (key->an->header.size);
- }
- }
- if (0 == obs)
- {
- /* nothing to do */
- client->key_gen = key_gen;
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- return GNUNET_OK;
- }
- buf = GNUNET_malloc (obs);
- obs = 0;
- for (struct Denomination *denom = denom_head;
- NULL != denom;
- denom = denom->next)
- {
- for (struct DenominationKey *key = denom->keys_head;
- NULL != key;
- key = key->next)
- {
- if (key->key_gen <= client->key_gen)
- continue;
- if (key->purge)
- {
- struct TALER_CRYPTO_RsaKeyPurgeNotification pn = {
- .header.type = htons (TALER_HELPER_RSA_MT_PURGE),
- .header.size = htons (sizeof (pn)),
- .h_rsa = key->h_rsa
- };
-
- GNUNET_memcpy (&buf[obs],
- &pn,
- sizeof (pn));
- GNUNET_assert (obs + sizeof (pn)
- > obs);
- obs += sizeof (pn);
- }
- else
- {
- GNUNET_memcpy (&buf[obs],
- key->an,
- ntohs (key->an->header.size));
- GNUNET_assert (obs + ntohs (key->an->header.size)
- > obs);
- obs += ntohs (key->an->header.size);
- }
- }
- }
- client->key_gen = key_gen;
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- ret = TES_transmit_raw (client->csock,
- obs,
- buf);
- GNUNET_free (buf);
- return ret;
-}
-
-
-/**
- * Create a new denomination key (we do not have enough).
- *
- * @param denom denomination key to create
- * @param now current time to use (to get many keys to use the exact same time)
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-create_key (struct Denomination *denom,
- struct GNUNET_TIME_Timestamp now)
-{
- struct DenominationKey *dk;
- struct GNUNET_TIME_Timestamp anchor;
-
- anchor = now;
- // FIXME: round down to multiple of 'anchor_round' value from configuration
- if (NULL != denom->keys_tail)
- {
- struct GNUNET_TIME_Absolute abs;
-
- abs = GNUNET_TIME_absolute_add (denom->keys_tail->anchor.abs_time,
- GNUNET_TIME_relative_subtract (
- denom->duration_withdraw,
- overlap_duration));
- if (GNUNET_TIME_absolute_cmp (now.abs_time, <, abs))
- anchor = GNUNET_TIME_absolute_to_timestamp (abs);
- }
- dk = GNUNET_new (struct DenominationKey);
- dk->denom = denom;
- dk->anchor = anchor;
- if (GNUNET_OK !=
- setup_key (dk,
- denom->keys_tail))
- {
- GNUNET_break (0);
- GNUNET_free (dk);
- GNUNET_SCHEDULER_shutdown ();
- global_ret = EXIT_FAILURE;
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * 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 denom denomination to compute action time for
- */
-static struct GNUNET_TIME_Absolute
-denomination_action_time (const struct Denomination *denom)
-{
- struct DenominationKey *head = denom->keys_head;
- struct DenominationKey *tail = denom->keys_tail;
- struct GNUNET_TIME_Absolute tt;
-
- if (NULL == head)
- return GNUNET_TIME_UNIT_ZERO_ABS;
- tt = GNUNET_TIME_absolute_subtract (
- GNUNET_TIME_absolute_subtract (
- GNUNET_TIME_absolute_add (tail->anchor.abs_time,
- denom->duration_withdraw),
- lookahead_sign),
- overlap_duration);
- if (head->rc > 0)
- return tt; /* head expiration does not count due to rc > 0 */
- return GNUNET_TIME_absolute_min (
- GNUNET_TIME_absolute_add (head->anchor.abs_time,
- denom->duration_withdraw),
- tt);
-}
-
-
-/**
- * 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
- * @param now current time to use (to get many keys to use the exact same time)
- * @param[in,out] wake set to true if we should wake the clients
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-update_keys (struct Denomination *denom,
- struct GNUNET_TIME_Timestamp now,
- bool *wake)
-{
- /* create new denomination keys */
- if (NULL != denom->keys_tail)
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Updating keys of denomination `%s', last key %s valid for another %s\n",
- denom->section,
- GNUNET_h2s (&denom->keys_tail->h_rsa.hash),
- GNUNET_TIME_relative2s (
- GNUNET_TIME_absolute_get_remaining (
- GNUNET_TIME_absolute_subtract (
- GNUNET_TIME_absolute_add (
- denom->keys_tail->anchor.abs_time,
- denom->duration_withdraw),
- overlap_duration)),
- GNUNET_YES));
- while ( (NULL == denom->keys_tail) ||
- GNUNET_TIME_absolute_is_past (
- GNUNET_TIME_absolute_subtract (
- GNUNET_TIME_absolute_subtract (
- GNUNET_TIME_absolute_add (denom->keys_tail->anchor.abs_time,
- denom->duration_withdraw),
- lookahead_sign),
- overlap_duration)) )
- {
- if (! *wake)
- {
- key_gen++;
- *wake = true;
- }
- if (GNUNET_OK !=
- create_key (denom,
- now))
- {
- GNUNET_break (0);
- global_ret = EXIT_FAILURE;
- GNUNET_SCHEDULER_shutdown ();
- return GNUNET_SYSERR;
- }
- }
- /* remove expired denomination keys */
- while ( (NULL != denom->keys_head) &&
- GNUNET_TIME_absolute_is_past
- (GNUNET_TIME_absolute_add (denom->keys_head->anchor.abs_time,
- denom->duration_withdraw)) )
- {
- struct DenominationKey *key = denom->keys_head;
- struct DenominationKey *nxt = key->next;
-
- if (0 != key->rc)
- break; /* later */
- GNUNET_CONTAINER_DLL_remove (denom->keys_head,
- denom->keys_tail,
- key);
- GNUNET_assert (GNUNET_OK ==
- GNUNET_CONTAINER_multihashmap_remove (
- keys,
- &key->h_rsa.hash,
- key));
- if ( (! key->purge) &&
- (0 != unlink (key->filename)) )
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
- "unlink",
- key->filename);
- GNUNET_free (key->filename);
- GNUNET_CRYPTO_rsa_private_key_free (key->denom_priv);
- GNUNET_CRYPTO_rsa_public_key_free (key->denom_pub);
- GNUNET_free (key->an);
- GNUNET_free (key);
- key = nxt;
- }
-
- /* 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);
- GNUNET_CONTAINER_DLL_remove (denom_head,
- denom_tail,
- denom);
- before = NULL;
- for (struct Denomination *pos = denom_head;
- NULL != pos;
- pos = pos->next)
- {
- if (GNUNET_TIME_absolute_cmp (denomination_action_time (pos), >=, at))
- break;
- before = pos;
- }
- GNUNET_CONTAINER_DLL_insert_after (denom_head,
- denom_tail,
- before,
- denom);
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Task run periodically to expire keys and/or generate fresh ones.
- *
- * @param cls NULL
- */
-static void
-update_denominations (void *cls)
-{
- struct Denomination *denom;
- struct GNUNET_TIME_Absolute now;
- struct GNUNET_TIME_Timestamp t;
- bool wake = false;
-
- (void) cls;
- keygen_task = NULL;
- now = GNUNET_TIME_absolute_get ();
- t = GNUNET_TIME_absolute_to_timestamp (now);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Updating denominations ...\n");
- GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
- do {
- denom = denom_head;
- if (GNUNET_OK !=
- update_keys (denom,
- t,
- &wake))
- return;
- } while (denom != denom_head);
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Updating denominations finished ...\n");
- if (wake)
- TES_wake_clients ();
- keygen_task = GNUNET_SCHEDULER_add_at (denomination_action_time (denom),
- &update_denominations,
- NULL);
-}
-
-
-/**
- * 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_Timestamp 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.abs_value_us
- = anchor_ll * GNUNET_TIME_UNIT_SECONDS.rel_value_us;
- if (anchor_ll != anchor.abs_time.abs_value_us
- / GNUNET_TIME_UNIT_SECONDS.rel_value_us)
- {
- /* 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 = priv;
- dk->denom = denom;
- dk->anchor = anchor;
- dk->filename = GNUNET_strdup (filename);
- GNUNET_CRYPTO_rsa_public_key_hash (pub,
- &dk->h_rsa.hash);
- dk->denom_pub = pub;
- generate_response (dk);
- if (GNUNET_OK !=
- GNUNET_CONTAINER_multihashmap_put (
- keys,
- &dk->h_rsa.hash,
- dk,
- GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Duplicate private key %s detected in file `%s'. Skipping.\n",
- GNUNET_h2s (&dk->h_rsa.hash),
- filename);
- GNUNET_CRYPTO_rsa_private_key_free (priv);
- GNUNET_CRYPTO_rsa_public_key_free (pub);
- GNUNET_free (dk->an);
- GNUNET_free (dk);
- return;
- }
- before = NULL;
- for (struct DenominationKey *pos = denom->keys_head;
- NULL != pos;
- pos = pos->next)
- {
- if (GNUNET_TIME_timestamp_cmp (pos->anchor,
- >,
- anchor))
- break;
- before = pos;
- }
- GNUNET_CONTAINER_DLL_insert_after (denom->keys_head,
- denom->keys_tail,
- before,
- dk);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Imported key %s from `%s'\n",
- GNUNET_h2s (&dk->h_rsa.hash),
- filename);
- }
-}
-
-
-/**
- * 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
- * @return #GNUNET_OK (always, continue to iterate)
- */
-static enum GNUNET_GenericReturnValue
-import_key (void *cls,
- const char *filename)
-{
- struct Denomination *denom = cls;
- struct GNUNET_DISK_FileHandle *fh;
- struct GNUNET_DISK_MapHandle *map;
- void *ptr;
- int fd;
- struct stat sbuf;
-
- {
- struct stat lsbuf;
-
- if (0 != lstat (filename,
- &lsbuf))
- {
- GNUNET_log_strerror_file (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_RDONLY | O_CLOEXEC);
- if (-1 == fd)
- {
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
- "open",
- filename);
- return GNUNET_OK;
- }
- if (0 != fstat (fd,
- &sbuf))
- {
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
- "stat",
- filename);
- GNUNET_break (0 == close (fd));
- 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);
- GNUNET_break (0 == close (fd));
- 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_file (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_file (GNUNET_ERROR_TYPE_WARNING,
- "open",
- filename);
- GNUNET_break (0 == close (fd));
- return GNUNET_OK;
- }
- if (sbuf.st_size > 16 * 1024)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "File `%s' too 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_file (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;
-}
-
-
-/**
- * Parse configuration for denomination type parameters. Also determines
- * our anchor by looking at the existing denominations of the same type.
- *
- * @param cfg configuration to use
- * @param ct section in the configuration file giving the denomination type parameters
- * @param[out] denom set to the denomination parameters from the configuration
- * @return #GNUNET_OK on success, #GNUNET_SYSERR if the configuration is invalid
- */
-static enum GNUNET_GenericReturnValue
-parse_denomination_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg,
- const char *ct,
- struct Denomination *denom)
-{
- unsigned long long rsa_keysize;
- char *secname;
-
- GNUNET_asprintf (&secname,
- "%s-secmod-rsa",
- section);
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_time (cfg,
- ct,
- "DURATION_WITHDRAW",
- &denom->duration_withdraw))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- ct,
- "DURATION_WITHDRAW");
- GNUNET_free (secname);
- return GNUNET_SYSERR;
- }
- if (GNUNET_TIME_relative_cmp (overlap_duration,
- >=,
- denom->duration_withdraw))
- {
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- section,
- "OVERLAP_DURATION",
- "Value given must be smaller than value for DURATION_WITHDRAW!");
- GNUNET_free (secname);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_number (cfg,
- ct,
- "RSA_KEYSIZE",
- &rsa_keysize))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- ct,
- "RSA_KEYSIZE");
- GNUNET_free (secname);
- return GNUNET_SYSERR;
- }
- if ( (rsa_keysize > 4 * 2048) ||
- (rsa_keysize < 1024) )
- {
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- ct,
- "RSA_KEYSIZE",
- "Given RSA keysize outside of permitted range [1024,8192]\n");
- GNUNET_free (secname);
- return GNUNET_SYSERR;
- }
- GNUNET_free (secname);
- denom->rsa_keysize = (unsigned int) rsa_keysize;
- denom->section = GNUNET_strdup (ct);
- return GNUNET_OK;
-}
-
-
-/**
- * Closure for #load_denominations.
- */
-struct LoadContext
-{
-
- /**
- * Configuration to use.
- */
- const struct GNUNET_CONFIGURATION_Handle *cfg;
-
- /**
- * Current time to use.
- */
- struct GNUNET_TIME_Timestamp t;
-
- /**
- * Status, to be set to #GNUNET_SYSERR on failure
- */
- enum GNUNET_GenericReturnValue ret;
-};
-
-
-/**
- * Generate new denomination signing keys for the denomination type of the given @a
- * denomination_alias.
- *
- * @param cls a `struct LoadContext`, with 'ret' 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)
-{
- struct LoadContext *ctx = cls;
- struct Denomination *denom;
- bool wake = true;
- char *cipher;
-
- if ( (0 != strncasecmp (denomination_alias,
- "coin_",
- strlen ("coin_"))) &&
- (0 != strncasecmp (denomination_alias,
- "coin-",
- strlen ("coin-"))) )
- return; /* not a denomination type definition */
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (ctx->cfg,
- denomination_alias,
- "CIPHER",
- &cipher))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- denomination_alias,
- "CIPHER");
- return;
- }
- if (0 != strcmp (cipher, "RSA"))
- {
- GNUNET_free (cipher);
- return; /* Ignore denominations of other types than CS */
- }
- GNUNET_free (cipher);
- denom = GNUNET_new (struct Denomination);
- if (GNUNET_OK !=
- parse_denomination_cfg (ctx->cfg,
- denomination_alias,
- denom))
- {
- ctx->ret = GNUNET_SYSERR;
- GNUNET_free (denom);
- return;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Loading keys for denomination %s\n",
- denom->section);
- {
- char *dname;
-
- GNUNET_asprintf (&dname,
- "%s/%s",
- keydir,
- denom->section);
- GNUNET_break (GNUNET_OK ==
- GNUNET_DISK_directory_create (dname));
- GNUNET_DISK_directory_scan (dname,
- &import_key,
- denom);
- GNUNET_free (dname);
- }
- GNUNET_CONTAINER_DLL_insert (denom_head,
- denom_tail,
- denom);
- update_keys (denom,
- ctx->t,
- &wake);
-}
-
-
-/**
- * Load the various duration values from @a cfg
- *
- * @param cfg configuration to use
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-load_durations (const struct GNUNET_CONFIGURATION_Handle *cfg)
-{
- char *secname;
-
- GNUNET_asprintf (&secname,
- "%s-secmod-rsa",
- section);
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_time (cfg,
- secname,
- "OVERLAP_DURATION",
- &overlap_duration))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- secname,
- "OVERLAP_DURATION");
- GNUNET_free (secname);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_time (cfg,
- secname,
- "LOOKAHEAD_SIGN",
- &lookahead_sign))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- secname,
- "LOOKAHEAD_SIGN");
- GNUNET_free (secname);
- return GNUNET_SYSERR;
- }
- GNUNET_free (secname);
- return GNUNET_OK;
-}
-
-
-/**
- * Function run on shutdown. Stops the various jobs (nicely).
- *
- * @param cls NULL
- */
-static void
-do_shutdown (void *cls)
-{
- (void) cls;
- TES_listen_stop ();
- if (NULL != keygen_task)
- {
- GNUNET_SCHEDULER_cancel (keygen_task);
- keygen_task = NULL;
- }
- stop_workers ();
- sem_done (&worker_sem);
-}
-
-
-/**
- * Main function that will be run under the GNUnet scheduler.
- *
- * @param cls closure
- * @param args remaining command-line arguments
- * @param cfgfile name of the configuration file used (for saving, can be NULL!)
- * @param cfg configuration
- */
-static void
-run (void *cls,
- char *const *args,
- const char *cfgfile,
- const struct GNUNET_CONFIGURATION_Handle *cfg)
-{
- static struct TES_Callbacks cb = {
- .dispatch = rsa_work_dispatch,
- .updater = rsa_update_client_keys,
- .init = rsa_client_init
- };
- char *secname;
-
- (void) cls;
- (void) args;
- (void) cfgfile;
- if (GNUNET_TIME_timestamp_cmp (global_now,
- !=,
- global_now_tmp))
- {
- /* The user gave "--now", use it! */
- global_now = global_now_tmp;
- }
- else
- {
- /* get current time again, we may be timetraveling! */
- global_now = GNUNET_TIME_timestamp_get ();
- }
- GNUNET_asprintf (&secname,
- "%s-secmod-rsa",
- section);
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_filename (cfg,
- secname,
- "KEY_DIR",
- &keydir))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- secname,
- "KEY_DIR");
- GNUNET_free (secname);
- global_ret = EXIT_NOTCONFIGURED;
- return;
- }
- if (GNUNET_OK !=
- load_durations (cfg))
- {
- global_ret = EXIT_NOTCONFIGURED;
- GNUNET_free (secname);
- return;
- }
- GNUNET_asprintf (&secname,
- "%s-secmod-rsa",
- section);
- global_ret = TES_listen_start (cfg,
- secname,
- &cb);
- GNUNET_free (secname);
- if (0 != global_ret)
- return;
- sem_init (&worker_sem,
- 0);
- GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
- NULL);
- if (0 == max_workers)
- {
- long lret;
-
- lret = sysconf (_SC_NPROCESSORS_CONF);
- if (lret <= 0)
- lret = 1;
- max_workers = (unsigned int) lret;
- }
-
- for (unsigned int i = 0; i<max_workers; i++)
- if (GNUNET_OK !=
- start_worker ())
- {
- GNUNET_SCHEDULER_shutdown ();
- return;
- }
- /* Load denominations */
- keys = GNUNET_CONTAINER_multihashmap_create (65536,
- GNUNET_YES);
- {
- struct LoadContext lc = {
- .cfg = cfg,
- .ret = GNUNET_OK,
- .t = global_now
- };
-
- GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
- GNUNET_CONFIGURATION_iterate_sections (cfg,
- &load_denominations,
- &lc);
- GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
- if (GNUNET_OK != lc.ret)
- {
- global_ret = EXIT_FAILURE;
- GNUNET_SCHEDULER_shutdown ();
- return;
- }
- }
- if (NULL == denom_head)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "No RSA denominations configured\n");
- TES_wake_clients ();
- return;
- }
- /* start job to keep keys up-to-date; MUST be run before the #listen_task,
- hence with priority. */
- keygen_task = GNUNET_SCHEDULER_add_with_priority (
- GNUNET_SCHEDULER_PRIORITY_URGENT,
- &update_denominations,
- NULL);
-}
/**
@@ -2088,44 +46,31 @@ int
main (int argc,
char **argv)
{
+ struct TALER_SECMOD_Options opts = {
+ .max_workers = 16,
+ .section = "taler-exchange"
+ };
struct GNUNET_GETOPT_CommandLineOption options[] = {
- GNUNET_GETOPT_option_string ('s',
- "section",
- "SECTION",
- "name of the configuration section prefix to use, default is 'taler'",
- &section),
- GNUNET_GETOPT_option_timetravel ('T',
- "timetravel"),
- GNUNET_GETOPT_option_timestamp ('t',
- "time",
- "TIMESTAMP",
- "pretend it is a different time for the update",
- &global_now_tmp),
- GNUNET_GETOPT_option_uint ('w',
- "workers",
- "COUNT",
- "use COUNT workers for parallel processing of batch requests",
- &max_workers),
+ TALER_SECMOD_OPTIONS (&opts),
GNUNET_GETOPT_OPTION_END
};
enum GNUNET_GenericReturnValue ret;
/* Restrict permissions for the key files that we create. */
(void) umask (S_IWGRP | S_IROTH | S_IWOTH | S_IXOTH);
- section = GNUNET_strdup ("taler-exchange");
- global_now_tmp
- = global_now
+ opts.global_now_tmp
+ = opts.global_now
= GNUNET_TIME_timestamp_get ();
ret = GNUNET_PROGRAM_run (TALER_EXCHANGE_project_data (),
argc, argv,
"taler-exchange-secmod-rsa",
"Handle private RSA key operations for a Taler exchange",
options,
- &run,
- NULL);
+ &TALER_SECMOD_rsa_run,
+ &opts);
if (GNUNET_NO == ret)
return EXIT_SUCCESS;
if (GNUNET_SYSERR == ret)
return EXIT_INVALIDARGUMENT;
- return global_ret;
+ return opts.global_ret;
}