gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-exchange] 14/37: initial cs_secmod implementation


From: gnunet
Subject: [taler-exchange] 14/37: initial cs_secmod implementation
Date: Fri, 04 Feb 2022 16:53:44 +0100

This is an automated email from the git hooks/post-receive script.

gian-demarmels pushed a commit to branch master
in repository exchange.

commit 18db69be2d2bbacc6b9f4de2e9e8f8db2df4febe
Author: Gian Demarmels <gian@demarmels.org>
AuthorDate: Mon Jan 3 14:38:59 2022 +0100

    initial cs_secmod implementation
---
 src/include/taler_crypto_lib.h         |  130 +++
 src/testing/.gitignore                 |    2 +
 src/util/.gitignore                    |    3 +
 src/util/Makefile.am                   |   27 +-
 src/util/crypto_helper_cs.c            |  643 +++++++++++++
 src/util/denom.c                       |   16 +
 src/util/taler-exchange-secmod-cs.c    | 1580 ++++++++++++++++++++++++++++++++
 src/util/taler-exchange-secmod-cs.conf |   23 +
 src/util/taler-exchange-secmod-cs.h    |  258 ++++++
 src/util/test_helper_cs.c              |  692 ++++++++++++++
 src/util/test_helper_cs.conf           |   11 +
 11 files changed, 3382 insertions(+), 3 deletions(-)

diff --git a/src/include/taler_crypto_lib.h b/src/include/taler_crypto_lib.h
index ff145cc4..bd889b35 100644
--- a/src/include/taler_crypto_lib.h
+++ b/src/include/taler_crypto_lib.h
@@ -446,6 +446,15 @@ void
 TALER_rsa_pub_hash (const struct GNUNET_CRYPTO_RsaPublicKey *rsa,
                     struct TALER_RsaPubHashP *h_rsa);
 
+/**
+ * Hash @a cs.
+ *
+ * @param cs key to hash
+ * @param[out] h_cs where to write the result
+ */
+void
+TALER_cs_pub_hash (const struct GNUNET_CRYPTO_CsPublicKey *cs,
+                   struct TALER_CsPubHashP *h_cs);
 
 /**
  * Hash used to represent a denomination public key
@@ -1698,6 +1707,127 @@ void
 TALER_CRYPTO_helper_rsa_disconnect (
   struct TALER_CRYPTO_RsaDenominationHelper *dh);
 
+/* **************** Helper-based CS operations **************** */
+
+/**
+ * Handle for talking to an Denomination key signing helper.
+ */
+struct TALER_CRYPTO_CsDenominationHelper;
+
+/**
+ * Function called with information about available keys for signing.  Usually
+ * only called once per key upon connect. Also called again in case a key is
+ * being revoked, in that case with an @a end_time of zero.
+ *
+ * @param cls closure
+ * @param section_name name of the denomination type in the configuration;
+ *                 NULL if the key has been revoked or purged
+ * @param start_time when does the key become available for signing;
+ *                 zero if the key has been revoked or purged
+ * @param validity_duration how long does the key remain available for signing;
+ *                 zero if the key has been revoked or purged
+ * @param h_cs hash of the CS @a denom_pub that is available (or was purged)
+ * @param denom_pub the public key itself, NULL if the key was revoked or 
purged
+ * @param sm_pub public key of the security module, NULL if the key was 
revoked or purged
+ * @param sm_sig signature from the security module, NULL if the key was 
revoked or purged
+ *               The signature was already verified against @a sm_pub.
+ */
+typedef void
+(*TALER_CRYPTO_CsDenominationKeyStatusCallback)(
+  void *cls,
+  const char *section_name,
+  struct GNUNET_TIME_Timestamp start_time,
+  struct GNUNET_TIME_Relative validity_duration,
+  const struct TALER_CsPubHashP *h_cs,
+  const struct TALER_DenominationPublicKey *denom_pub,
+  const struct TALER_SecurityModulePublicKeyP *sm_pub,
+  const struct TALER_SecurityModuleSignatureP *sm_sig);
+
+
+/**
+ * Initiate connection to an denomination key helper.
+ *
+ * @param cfg configuration to use
+ * @param dkc function to call with key information
+ * @param dkc_cls closure for @a dkc
+ * @return NULL on error (such as bad @a cfg).
+ */
+struct TALER_CRYPTO_CsDenominationHelper *
+TALER_CRYPTO_helper_cs_connect (
+  const struct GNUNET_CONFIGURATION_Handle *cfg,
+  TALER_CRYPTO_CsDenominationKeyStatusCallback dkc,
+  void *dkc_cls);
+
+
+/**
+ * Function to call to 'poll' for updates to the available key material.
+ * Should be called whenever it is important that the key material status is
+ * current, like when handling a "/keys" request.  This function basically
+ * briefly checks if there are messages from the helper announcing changes to
+ * denomination keys.
+ *
+ * @param dh helper process connection
+ */
+void
+TALER_CRYPTO_helper_cs_poll (struct TALER_CRYPTO_CsDenominationHelper *dh);
+
+
+/**
+ * Request helper @a dh to sign @a msg using the public key corresponding to
+ * @a h_denom_pub.
+ *
+ * This operation will block until the signature has been obtained.  Should
+ * this process receive a signal (that is not ignored) while the operation is
+ * pending, the operation will fail.  Note that the helper may still believe
+ * that it created the signature. Thus, signals may result in a small
+ * differences in the signature counters.  Retrying in this case may work.
+ *
+ * @param dh helper process connection
+ * @param h_rsa hash of the RSA public key to use to sign
+ * @param msg message to sign
+ * @param msg_size number of bytes in @a msg
+ * @param[out] ec set to the error code (or #TALER_EC_NONE on success)
+ * @return signature, the value inside the structure will be NULL on failure,
+ *         see @a ec for details about the failure
+ */
+struct TALER_BlindedDenominationSignature
+TALER_CRYPTO_helper_cs_sign (
+  struct TALER_CRYPTO_CsDenominationHelper *dh,
+  const struct TALER_CsPubHashP *h_cs,
+  const void *msg,
+  size_t msg_size,
+  enum TALER_ErrorCode *ec);
+
+
+/**
+ * Ask the helper to revoke the public key associated with @param h_denom_pub .
+ * Will cause the helper to tell all clients that the key is now unavailable,
+ * and to create a replacement key.
+ *
+ * This operation will block until the revocation request has been
+ * transmitted.  Should this process receive a signal (that is not ignored)
+ * while the operation is pending, the operation may fail. If the key is
+ * unknown, this function will also appear to have succeeded. To be sure that
+ * the revocation worked, clients must watch the denomination key status
+ * callback.
+ *
+ * @param dh helper to process connection
+ * @param h_rsa hash of the RSA public key to revoke
+ */
+void
+TALER_CRYPTO_helper_cs_revoke (
+  struct TALER_CRYPTO_CsDenominationHelper *dh,
+  const struct TALER_CsPubHashP *h_cs);
+
+
+/**
+ * Close connection to @a dh.
+ *
+ * @param[in] dh connection to close
+ */
+void
+TALER_CRYPTO_helper_cs_disconnect (
+  struct TALER_CRYPTO_CsDenominationHelper *dh);
 
 /**
  * Handle for talking to an online key signing helper.
diff --git a/src/testing/.gitignore b/src/testing/.gitignore
index 8c19b94c..f721009e 100644
--- a/src/testing/.gitignore
+++ b/src/testing/.gitignore
@@ -33,3 +33,5 @@ 
test_taler_exchange_httpd_home/.local/share/taler/exchange-offline/secm_tofus.pu
 test_taler_exchange_httpd_home/.local/share/taler/exchange-secmod-eddsa/
 test_taler_exchange_httpd_home/.local/share/taler/exchange-secmod-rsa/
 test_kyc_api
+test_helper_cs_home/
+test_helper_rsa_home/
\ No newline at end of file
diff --git a/src/util/.gitignore b/src/util/.gitignore
index f25567f3..c5f8c76d 100644
--- a/src/util/.gitignore
+++ b/src/util/.gitignore
@@ -1,8 +1,11 @@
 taler-config
 test_payto
 taler-exchange-secmod-rsa
+taler-exchange-secmod-cs
 taler-exchange-secmod-eddsa
 test_helper_rsa
 test_helper_rsa_home/
+test_helper_cs
+test_helper_cs_home/
 test_helper_eddsa
 test_helper_eddsa_home/
diff --git a/src/util/Makefile.am b/src/util/Makefile.am
index 35e58034..997b49f2 100644
--- a/src/util/Makefile.am
+++ b/src/util/Makefile.am
@@ -12,17 +12,20 @@ pkgcfgdir = $(prefix)/share/taler/config.d/
 pkgcfg_DATA = \
   paths.conf \
   taler-exchange-secmod-eddsa.conf \
-  taler-exchange-secmod-rsa.conf
+  taler-exchange-secmod-rsa.conf \
+  taler-exchange-secmod-cs.conf
 
 EXTRA_DIST = \
   $(pkgcfg_DATA) \
   taler-config.in \
   test_helper_eddsa.conf \
-  test_helper_rsa.conf
+  test_helper_rsa.conf \
+  test_helper_cs.conf
 
 bin_PROGRAMS = \
   taler-exchange-secmod-eddsa \
-  taler-exchange-secmod-rsa
+  taler-exchange-secmod-rsa \
+  taler-exchange-secmod-cs
 
 bin_SCRIPTS = \
   taler-config
@@ -48,6 +51,16 @@ taler_exchange_secmod_rsa_LDADD = \
   $(LIBGCRYPT_LIBS) \
   $(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_LDADD = \
+  libtalerutil.la \
+  -lgnunetutil \
+  -lpthread \
+  $(LIBGCRYPT_LIBS) \
+  $(XLIB)
+
 taler_exchange_secmod_eddsa_SOURCES = \
   taler-exchange-secmod-eddsa.c taler-exchange-secmod-eddsa.h \
   secmod_common.c secmod_common.h
@@ -68,6 +81,7 @@ libtalerutil_la_SOURCES = \
   crypto.c \
   crypto_helper_common.c crypto_helper_common.h \
   crypto_helper_rsa.c \
+  crypto_helper_cs.c \
   crypto_helper_esign.c \
   crypto_wire.c \
   denom.c \
@@ -105,6 +119,7 @@ check_PROGRAMS = \
  test_crypto \
  test_helper_eddsa \
  test_helper_rsa \
+ test_helper_cs \
  test_payto \
  test_url
 
@@ -142,6 +157,12 @@ test_helper_rsa_LDADD = \
   -lgnunetutil \
   libtalerutil.la
 
+test_helper_cs_SOURCES = \
+  test_helper_cs.c
+test_helper_cs_LDADD = \
+  -lgnunetutil \
+  libtalerutil.la
+
 test_url_SOURCES = \
   test_url.c
 test_url_LDADD = \
diff --git a/src/util/crypto_helper_cs.c b/src/util/crypto_helper_cs.c
new file mode 100644
index 00000000..94d98f13
--- /dev/null
+++ b/src/util/crypto_helper_cs.c
@@ -0,0 +1,643 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020, 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/crypto_helper_cs.c
+ * @brief utility functions for running out-of-process private key operations
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_signatures.h"
+#include "taler-exchange-secmod-cs.h"
+#include <poll.h>
+#include "crypto_helper_common.h"
+
+
+struct TALER_CRYPTO_CsDenominationHelper
+{
+  /**
+   * Function to call with updates to available key material.
+   */
+  TALER_CRYPTO_CsDenominationKeyStatusCallback dkc;
+
+  /**
+   * Closure for @e dkc
+   */
+  void *dkc_cls;
+
+  /**
+   * Socket address of the denomination helper process.
+   * Used to reconnect if the connection breaks.
+   */
+  struct sockaddr_un sa;
+
+  /**
+   * The UNIX domain socket, -1 if we are currently not connected.
+   */
+  int sock;
+
+  /**
+   * Have we ever been sync'ed?
+   */
+  bool synced;
+};
+
+
+/**
+ * Disconnect from the helper process.  Updates
+ * @e sock field in @a dh.
+ *
+ * @param[in,out] dh handle to tear down connection of
+ */
+static void
+do_disconnect (struct TALER_CRYPTO_CsDenominationHelper *dh)
+{
+  GNUNET_break (0 == close (dh->sock));
+  dh->sock = -1;
+  dh->synced = false;
+}
+
+
+/**
+ * Try to connect to the helper process.  Updates
+ * @e sock field in @a dh.
+ *
+ * @param[in,out] dh handle to establish connection for
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+try_connect (struct TALER_CRYPTO_CsDenominationHelper *dh)
+{
+  if (-1 != dh->sock)
+    return GNUNET_OK;
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Establishing connection!\n");
+  dh->sock = socket (AF_UNIX,
+                     SOCK_STREAM,
+                     0);
+  if (-1 == dh->sock)
+  {
+    GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+                         "socket");
+    return GNUNET_SYSERR;
+  }
+  if (0 !=
+      connect (dh->sock,
+               (const struct sockaddr *) &dh->sa,
+               sizeof (dh->sa)))
+  {
+    GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+                              "connect",
+                              dh->sa.sun_path);
+    do_disconnect (dh);
+    return GNUNET_SYSERR;
+  }
+  TALER_CRYPTO_helper_cs_poll (dh);
+  return GNUNET_OK;
+}
+
+
+struct TALER_CRYPTO_CsDenominationHelper *
+TALER_CRYPTO_helper_cs_connect (
+  const struct GNUNET_CONFIGURATION_Handle *cfg,
+  TALER_CRYPTO_CsDenominationKeyStatusCallback dkc,
+  void *dkc_cls)
+{
+  struct TALER_CRYPTO_CsDenominationHelper *dh;
+  char *unixpath;
+
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_filename (cfg,
+                                               "taler-exchange-secmod-cs",
+                                               "UNIXPATH",
+                                               &unixpath))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "taler-exchange-secmod-cs",
+                               "UNIXPATH");
+    return NULL;
+  }
+  /* we use >= here because we want the sun_path to always
+     be 0-terminated */
+  if (strlen (unixpath) >= sizeof (dh->sa.sun_path))
+  {
+    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+                               "taler-exchange-secmod-cs",
+                               "UNIXPATH",
+                               "path too long");
+    GNUNET_free (unixpath);
+    return NULL;
+  }
+  dh = GNUNET_new (struct TALER_CRYPTO_CsDenominationHelper);
+  dh->dkc = dkc;
+  dh->dkc_cls = dkc_cls;
+  dh->sa.sun_family = AF_UNIX;
+  strncpy (dh->sa.sun_path,
+           unixpath,
+           sizeof (dh->sa.sun_path) - 1);
+  GNUNET_free (unixpath);
+  dh->sock = -1;
+  if (GNUNET_OK !=
+      try_connect (dh))
+  {
+    TALER_CRYPTO_helper_cs_disconnect (dh);
+    return NULL;
+  }
+  return dh;
+}
+
+
+/**
+ * Handle a #TALER_HELPER_CS_MT_AVAIL message from the helper.
+ *
+ * @param dh helper context
+ * @param hdr message that we received
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_mt_avail (struct TALER_CRYPTO_CsDenominationHelper *dh,
+                 const struct GNUNET_MessageHeader *hdr)
+{
+  const struct TALER_CRYPTO_CsKeyAvailableNotification *kan
+    = (const struct TALER_CRYPTO_CsKeyAvailableNotification *) hdr;
+  const char *buf = (const char *) &kan[1];
+  const char *section_name;
+  uint16_t ps;
+  uint16_t snl;
+
+  if (sizeof (*kan) > ntohs (hdr->size))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  ps = ntohs (kan->pub_size);
+  snl = ntohs (kan->section_name_len);
+  if (ntohs (hdr->size) != sizeof (*kan) + ps + snl)
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (0 == snl)
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  section_name = &buf[ps];
+  if ('\0' != section_name[snl - 1])
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+
+  {
+    struct TALER_DenominationPublicKey denom_pub;
+    struct TALER_CsPubHashP h_cs;
+
+    denom_pub.cipher = TALER_DENOMINATION_RSA;
+    denom_pub.details.rsa_public_key
+      = GNUNET_CRYPTO_rsa_public_key_decode (buf,
+                                             ntohs (kan->pub_size));
+    if (NULL == denom_pub.details.rsa_public_key)
+    {
+      GNUNET_break_op (0);
+      return GNUNET_SYSERR;
+    }
+    GNUNET_CRYPTO_rsa_public_key_hash (denom_pub.details.rsa_public_key,
+                                       &h_cs.hash);
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Received CS key %s (%s)\n",
+                GNUNET_h2s (&h_cs.hash),
+                section_name);
+    if (GNUNET_OK !=
+        TALER_exchange_secmod_cs_verify (
+          &h_cs,
+          section_name,
+          GNUNET_TIME_timestamp_ntoh (kan->anchor_time),
+          GNUNET_TIME_relative_ntoh (kan->duration_withdraw),
+          &kan->secm_pub,
+          &kan->secm_sig))
+    {
+      GNUNET_break_op (0);
+      TALER_denom_pub_free (&denom_pub);
+      return GNUNET_SYSERR;
+    }
+    dh->dkc (dh->dkc_cls,
+             section_name,
+             GNUNET_TIME_timestamp_ntoh (kan->anchor_time),
+             GNUNET_TIME_relative_ntoh (kan->duration_withdraw),
+             &h_cs,
+             &denom_pub,
+             &kan->secm_pub,
+             &kan->secm_sig);
+    TALER_denom_pub_free (&denom_pub);
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Handle a #TALER_HELPER_CS_MT_PURGE message from the helper.
+ *
+ * @param dh helper context
+ * @param hdr message that we received
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_mt_purge (struct TALER_CRYPTO_CsDenominationHelper *dh,
+                 const struct GNUNET_MessageHeader *hdr)
+{
+  const struct TALER_CRYPTO_CsKeyPurgeNotification *pn
+    = (const struct TALER_CRYPTO_CsKeyPurgeNotification *) hdr;
+
+  if (sizeof (*pn) != ntohs (hdr->size))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Received revocation of denomination key %s\n",
+              GNUNET_h2s (&pn->h_cs.hash));
+  dh->dkc (dh->dkc_cls,
+           NULL,
+           GNUNET_TIME_UNIT_ZERO_TS,
+           GNUNET_TIME_UNIT_ZERO,
+           &pn->h_cs,
+           NULL,
+           NULL,
+           NULL);
+  return GNUNET_OK;
+}
+
+
+void
+TALER_CRYPTO_helper_cs_poll (struct TALER_CRYPTO_CsDenominationHelper *dh)
+{
+  char buf[UINT16_MAX];
+  size_t off = 0;
+  unsigned int retry_limit = 3;
+  const struct GNUNET_MessageHeader *hdr
+    = (const struct GNUNET_MessageHeader *) buf;
+
+  if (GNUNET_OK !=
+      try_connect (dh))
+    return; /* give up */
+  while (1)
+  {
+    uint16_t msize;
+    ssize_t ret;
+
+    ret = recv (dh->sock,
+                buf + off,
+                sizeof (buf) - off,
+                (dh->synced && (0 == off))
+                ? MSG_DONTWAIT
+                : 0);
+    if (ret < 0)
+    {
+      if (EINTR == errno)
+        continue;
+      if (EAGAIN == errno)
+      {
+        GNUNET_assert (dh->synced);
+        GNUNET_assert (0 == off);
+        break;
+      }
+      GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+                           "recv");
+      do_disconnect (dh);
+      if (0 == retry_limit)
+        return; /* give up */
+      if (GNUNET_OK !=
+          try_connect (dh))
+        return; /* give up */
+      retry_limit--;
+      continue;
+    }
+    if (0 == ret)
+    {
+      GNUNET_break (0 == off);
+      return;
+    }
+    off += ret;
+more:
+    if (off < sizeof (struct GNUNET_MessageHeader))
+      continue;
+    msize = ntohs (hdr->size);
+    if (off < msize)
+      continue;
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Received message of type %u and length %u\n",
+                (unsigned int) ntohs (hdr->type),
+                (unsigned int) msize);
+    switch (ntohs (hdr->type))
+    {
+    case TALER_HELPER_CS_MT_AVAIL:
+      if (GNUNET_OK !=
+          handle_mt_avail (dh,
+                           hdr))
+      {
+        GNUNET_break_op (0);
+        do_disconnect (dh);
+        return;
+      }
+      break;
+    case TALER_HELPER_CS_MT_PURGE:
+      if (GNUNET_OK !=
+          handle_mt_purge (dh,
+                           hdr))
+      {
+        GNUNET_break_op (0);
+        do_disconnect (dh);
+        return;
+      }
+      break;
+    case TALER_HELPER_CS_SYNCED:
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Now synchronized with CS helper\n");
+      dh->synced = true;
+      break;
+    default:
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Received unexpected message of type %d (len: %u)\n",
+                  (unsigned int) ntohs (hdr->type),
+                  (unsigned int) msize);
+      GNUNET_break_op (0);
+      do_disconnect (dh);
+      return;
+    }
+    memmove (buf,
+             &buf[msize],
+             off - msize);
+    off -= msize;
+    goto more;
+  }
+}
+
+
+struct TALER_BlindedDenominationSignature
+TALER_CRYPTO_helper_cs_sign (
+  struct TALER_CRYPTO_CsDenominationHelper *dh,
+  const struct TALER_CsPubHashP *h_cs,
+  const void *msg,
+  size_t msg_size,
+  enum TALER_ErrorCode *ec)
+{
+  struct TALER_BlindedDenominationSignature ds = {
+    .cipher = TALER_DENOMINATION_INVALID
+  };
+
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Starting signature process\n");
+  if (GNUNET_OK !=
+      try_connect (dh))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Failed to connect to helper\n");
+    *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE;
+    return ds;
+  }
+
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Requesting signature\n");
+  {
+    char buf[sizeof (struct TALER_CRYPTO_CsSignRequest) + msg_size];
+    struct TALER_CRYPTO_CsSignRequest *sr
+      = (struct TALER_CRYPTO_CsSignRequest *) buf;
+
+    sr->header.size = htons (sizeof (buf));
+    sr->header.type = htons (TALER_HELPER_CS_MT_REQ_SIGN);
+    sr->reserved = htonl (0);
+    sr->h_cs = *h_cs;
+    memcpy (&sr[1],
+            msg,
+            msg_size);
+    if (GNUNET_OK !=
+        TALER_crypto_helper_send_all (dh->sock,
+                                      buf,
+                                      sizeof (buf)))
+    {
+      GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+                           "send");
+      do_disconnect (dh);
+      *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE;
+      return ds;
+    }
+  }
+
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Awaiting reply\n");
+  {
+    char buf[UINT16_MAX];
+    size_t off = 0;
+    const struct GNUNET_MessageHeader *hdr
+      = (const struct GNUNET_MessageHeader *) buf;
+    bool finished = false;
+
+    *ec = TALER_EC_INVALID;
+    while (1)
+    {
+      uint16_t msize;
+      ssize_t ret;
+
+      ret = recv (dh->sock,
+                  &buf[off],
+                  sizeof (buf) - off,
+                  (finished && (0 == off))
+                  ? MSG_DONTWAIT
+                  : 0);
+      if (ret < 0)
+      {
+        if (EINTR == errno)
+          continue;
+        if (EAGAIN == errno)
+        {
+          GNUNET_assert (finished);
+          GNUNET_assert (0 == off);
+          return ds;
+        }
+        GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+                             "recv");
+        do_disconnect (dh);
+        *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE;
+        break;
+      }
+      if (0 == ret)
+      {
+        GNUNET_break (0 == off);
+        if (! finished)
+          *ec = TALER_EC_EXCHANGE_SIGNKEY_HELPER_BUG;
+        return ds;
+      }
+      off += ret;
+more:
+      if (off < sizeof (struct GNUNET_MessageHeader))
+        continue;
+      msize = ntohs (hdr->size);
+      if (off < msize)
+        continue;
+      switch (ntohs (hdr->type))
+      {
+      case TALER_HELPER_CS_MT_RES_SIGNATURE:
+        if (msize < sizeof (struct TALER_CRYPTO_SignResponse))
+        {
+          GNUNET_break_op (0);
+          do_disconnect (dh);
+          *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+          goto end;
+        }
+        if (finished)
+        {
+          GNUNET_break_op (0);
+          do_disconnect (dh);
+          *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+          goto end;
+        }
+        {
+          const struct TALER_CRYPTO_SignResponse *sr =
+            (const struct TALER_CRYPTO_SignResponse *) buf;
+          struct GNUNET_CRYPTO_RsaSignature *rsa_signature;
+
+          rsa_signature = GNUNET_CRYPTO_rsa_signature_decode (
+            &sr[1],
+            msize - sizeof (*sr));
+          if (NULL == rsa_signature)
+          {
+            GNUNET_break_op (0);
+            do_disconnect (dh);
+            *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+            goto end;
+          }
+          GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                      "Received signature\n");
+          *ec = TALER_EC_NONE;
+          finished = true;
+          ds.cipher = TALER_DENOMINATION_RSA;
+          ds.details.blinded_rsa_signature = rsa_signature;
+          break;
+        }
+      case TALER_HELPER_CS_MT_RES_SIGN_FAILURE:
+        if (msize != sizeof (struct TALER_CRYPTO_SignFailure))
+        {
+          GNUNET_break_op (0);
+          do_disconnect (dh);
+          *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+          goto end;
+        }
+        {
+          const struct TALER_CRYPTO_SignFailure *sf =
+            (const struct TALER_CRYPTO_SignFailure *) buf;
+
+          *ec = (enum TALER_ErrorCode) ntohl (sf->ec);
+          GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                      "Signing failed!\n");
+          finished = true;
+          break;
+        }
+      case TALER_HELPER_CS_MT_AVAIL:
+        GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                    "Received new key!\n");
+        if (GNUNET_OK !=
+            handle_mt_avail (dh,
+                             hdr))
+        {
+          GNUNET_break_op (0);
+          do_disconnect (dh);
+          *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+          goto end;
+        }
+        break; /* while(1) loop ensures we recvfrom() again */
+      case TALER_HELPER_CS_MT_PURGE:
+        GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                    "Received revocation!\n");
+        if (GNUNET_OK !=
+            handle_mt_purge (dh,
+                             hdr))
+        {
+          GNUNET_break_op (0);
+          do_disconnect (dh);
+          *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+          goto end;
+        }
+        break; /* while(1) loop ensures we recvfrom() again */
+      case TALER_HELPER_CS_SYNCED:
+        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                    "Synchronized add odd time with CS helper!\n");
+        dh->synced = true;
+        break;
+      default:
+        GNUNET_break_op (0);
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Received unexpected message of type %u\n",
+                    ntohs (hdr->type));
+        do_disconnect (dh);
+        *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+        goto end;
+      }
+      memmove (buf,
+               &buf[msize],
+               off - msize);
+      off -= msize;
+      goto more;
+    } /* while(1) */
+end:
+    if (finished)
+      TALER_blinded_denom_sig_free (&ds);
+    return ds;
+  }
+}
+
+
+void
+TALER_CRYPTO_helper_cs_revoke (
+  struct TALER_CRYPTO_CsDenominationHelper *dh,
+  const struct TALER_CsPubHashP *h_cs)
+{
+  struct TALER_CRYPTO_CsRevokeRequest rr = {
+    .header.size = htons (sizeof (rr)),
+    .header.type = htons (TALER_HELPER_CS_MT_REQ_REVOKE),
+    .h_cs = *h_cs
+  };
+
+  if (GNUNET_OK !=
+      try_connect (dh))
+    return; /* give up */
+  if (GNUNET_OK !=
+      TALER_crypto_helper_send_all (dh->sock,
+                                    &rr,
+                                    sizeof (rr)))
+  {
+    GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+                         "send");
+    do_disconnect (dh);
+    return;
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Requested revocation of denomination key %s\n",
+              GNUNET_h2s (&h_cs->hash));
+}
+
+
+void
+TALER_CRYPTO_helper_cs_disconnect (
+  struct TALER_CRYPTO_CsDenominationHelper *dh)
+{
+  if (-1 != dh->sock)
+    do_disconnect (dh);
+  GNUNET_free (dh);
+}
+
+
+/* end of crypto_helper_cs.c */
diff --git a/src/util/denom.c b/src/util/denom.c
index 9d8acfca..90830260 100644
--- a/src/util/denom.c
+++ b/src/util/denom.c
@@ -235,6 +235,22 @@ TALER_rsa_pub_hash (const struct 
GNUNET_CRYPTO_RsaPublicKey *rsa,
 }
 
 
+/**
+ * Hash @a cs. key
+ *
+ * @param cs key to hash
+ * @param[out] h_cs where to write the result
+ */
+void
+TALER_cs_pub_hash (const struct GNUNET_CRYPTO_CsPublicKey *cs,
+                   struct TALER_CsPubHashP *h_cs)
+{
+  GNUNET_CRYPTO_hash (cs,
+                      sizeof(*cs),
+                      &h_cs->hash);
+}
+
+
 void
 TALER_denom_pub_hash (const struct TALER_DenominationPublicKey *denom_pub,
                       struct TALER_DenominationHash *denom_hash)
diff --git a/src/util/taler-exchange-secmod-cs.c 
b/src/util/taler-exchange-secmod-cs.c
new file mode 100644
index 00000000..12a1fbbd
--- /dev/null
+++ b/src/util/taler-exchange-secmod-cs.c
@@ -0,0 +1,1580 @@
+/*
+  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/taler-exchange-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_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_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;
+
+  /**
+   * Length of (new) CS keys (in bits).
+   */
+  uint32_t rsa_keysize;
+};
+
+
+/**
+ * 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;
+
+/**
+ * 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] 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;
+  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_CS_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_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];
+  memcpy (p,
+          buf,
+          buf_len);
+  GNUNET_free (buf);
+  memcpy (p + buf_len,
+          denom->section,
+          nlen);
+  dk->an = an;
+}
+
+
+/**
+ * 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_CsSignRequest *sr)
+{
+  struct DenominationKey *dk;
+  const void *blinded_msg = &sr[1];
+  size_t blinded_msg_size = ntohs (sr->header.size) - sizeof (*sr);
+  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,
+                                          &sr->h_cs.hash);
+  if (NULL == dk)
+  {
+    struct TALER_CRYPTO_SignFailure sf = {
+      .header.size = htons (sizeof (sr)),
+      .header.type = htons (TALER_HELPER_CS_MT_RES_SIGN_FAILURE),
+      .ec = htonl (TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN)
+    };
+
+    GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Signing request failed, denomination key %s unknown\n",
+                GNUNET_h2s (&sr->h_cs.hash));
+    return TES_transmit (client->csock,
+                         &sf.header);
+  }
+  if (GNUNET_TIME_absolute_is_future (dk->anchor.abs_time))
+  {
+    /* it is too early */
+    struct TALER_CRYPTO_SignFailure sf = {
+      .header.size = htons (sizeof (sr)),
+      .header.type = htons (TALER_HELPER_CS_MT_RES_SIGN_FAILURE),
+      .ec = htonl (TALER_EC_EXCHANGE_DENOMINATION_HELPER_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 (&sr->h_cs.hash));
+    return TES_transmit (client->csock,
+                         &sf.header);
+  }
+
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Received request to sign over %u bytes with key %s\n",
+              (unsigned int) blinded_msg_size,
+              GNUNET_h2s (&sr->h_cs.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,
+                                      blinded_msg,
+                                      blinded_msg_size);
+  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)
+  {
+    struct TALER_CRYPTO_SignFailure sf = {
+      .header.size = htons (sizeof (sf)),
+      .header.type = htons (TALER_HELPER_CS_MT_RES_SIGN_FAILURE),
+      .ec = htonl (TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE)
+    };
+
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Signing request failed, worker failed to produce 
signature\n");
+    return TES_transmit (client->csock,
+                         &sf.header);
+  }
+
+  {
+    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_CS_MT_RES_SIGNATURE);
+    memcpy (&sr[1],
+            buf,
+            buf_size);
+    GNUNET_free (buf);
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Sending CS signature after %s\n",
+                GNUNET_TIME_relative2s (
+                  GNUNET_TIME_absolute_get_duration (now),
+                  GNUNET_YES));
+    ret = TES_transmit (client->csock,
+                        &sr->header);
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Sent CS signature after %s\n",
+                GNUNET_TIME_relative2s (
+                  GNUNET_TIME_absolute_get_duration (now),
+                  GNUNET_YES));
+    GNUNET_free (sr);
+    return ret;
+  }
+}
+
+
+/**
+ * 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);
+  TALER_rsa_pub_hash (pub,
+                      &dk->h_cs);
+  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_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_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_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 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_CsSignRequest))
+    {
+      GNUNET_break_op (0);
+      return GNUNET_SYSERR;
+    }
+    return handle_sign_request (
+      client,
+      (const struct TALER_CRYPTO_CsSignRequest *) 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);
+  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)
+    {
+      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
+        };
+
+        memcpy (&buf[obs],
+                &pn,
+                sizeof (pn));
+        obs += sizeof (pn);
+      }
+      else
+      {
+        memcpy (&buf[obs],
+                key->an,
+                ntohs (key->an->header.size));
+        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_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);
+    TALER_rsa_pub_hash (pub,
+                        &dk->h_cs);
+    dk->denom_pub = pub;
+    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_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_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_CLOEXEC);
+  if (-1 == fd)
+  {
+    GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+                              "open",
+                              filename);
+    GNUNET_break (0 == close (fd));
+    return GNUNET_OK;
+  }
+  if (0 != fstat (fd,
+                  &sbuf))
+  {
+    GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+                              "stat",
+                              filename);
+    return GNUNET_OK;
+  }
+  if (! S_ISREG (sbuf.st_mode))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "File `%s' is not a regular file, which is not allowed for 
private keys!\n",
+                filename);
+    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;
+
+  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");
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_TIME_relative_cmp (overlap_duration,
+                                >=,
+                                denom->duration_withdraw))
+  {
+    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+                               "taler-exchange-secmod-cs",
+                               "OVERLAP_DURATION",
+                               "Value given must be smaller than value for 
DURATION_WITHDRAW!");
+    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");
+    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");
+    return GNUNET_SYSERR;
+  }
+  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;
+
+  if ( (0 != strncasecmp (denomination_alias,
+                          "coin_",
+                          strlen ("coin_"))) &&
+       (0 != strncasecmp (denomination_alias,
+                          "coin-",
+                          strlen ("coin-"))) )
+    return; /* not a denomination type definition */
+  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)
+{
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_time (cfg,
+                                           "taler-exchange-secmod-cs",
+                                           "OVERLAP_DURATION",
+                                           &overlap_duration))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "taler-exchange-secmod-cs",
+                               "OVERLAP_DURATION");
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_time (cfg,
+                                           "taler-exchange-secmod-cs",
+                                           "LOOKAHEAD_SIGN",
+                                           &lookahead_sign))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "taler-exchange-secmod-cs",
+                               "LOOKAHEAD_SIGN");
+    return GNUNET_SYSERR;
+  }
+  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 = cs_work_dispatch,
+    .updater = cs_update_client_keys,
+    .init = cs_client_init
+  };
+
+  (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 ();
+  }
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_filename (cfg,
+                                               "taler-exchange-secmod-cs",
+                                               "KEY_DIR",
+                                               &keydir))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "taler-exchange-secmod-cs",
+                               "KEY_DIR");
+    global_ret = EXIT_NOTCONFIGURED;
+    return;
+  }
+  if (GNUNET_OK !=
+      load_durations (cfg))
+  {
+    global_ret = EXIT_NOTCONFIGURED;
+    return;
+  }
+  global_ret = TES_listen_start (cfg,
+                                 "taler-exchange-secmod-cs",
+                                 &cb);
+  if (0 != global_ret)
+    return;
+  GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
+                                 NULL);
+  /* Load denominations */
+  keys = GNUNET_CONTAINER_multihashmap_create (65536,
+                                               GNUNET_YES);
+  {
+    struct LoadContext lc = {
+      .cfg = cfg,
+      .ret = GNUNET_OK,
+      .t = 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_ERROR,
+                "No denominations configured\n");
+    global_ret = EXIT_NOTCONFIGURED;
+    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_denominations,
+    NULL);
+}
+
+
+/**
+ * The entry point.
+ *
+ * @param argc number of arguments in @a argv
+ * @param argv command-line arguments
+ * @return 0 on normal termination
+ */
+int
+main (int argc,
+      char **argv)
+{
+  struct GNUNET_GETOPT_CommandLineOption options[] = {
+    GNUNET_GETOPT_option_timetravel ('T',
+                                     "timetravel"),
+    GNUNET_GETOPT_option_timestamp ('t',
+                                    "time",
+                                    "TIMESTAMP",
+                                    "pretend it is a different time for the 
update",
+                                    &now_tmp),
+    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);
+
+  /* force linker to link against libtalerutil; if we do
+   not do this, the linker may "optimize" libtalerutil
+   away and skip #TALER_OS_init(), which we do need */
+  TALER_OS_init ();
+  now_tmp = now = GNUNET_TIME_timestamp_get ();
+  ret = GNUNET_PROGRAM_run (argc, argv,
+                            "taler-exchange-secmod-cs",
+                            "Handle private CS key operations for a Taler 
exchange",
+                            options,
+                            &run,
+                            NULL);
+  if (GNUNET_NO == ret)
+    return EXIT_SUCCESS;
+  if (GNUNET_SYSERR == ret)
+    return EXIT_INVALIDARGUMENT;
+  return global_ret;
+}
diff --git a/src/util/taler-exchange-secmod-cs.conf 
b/src/util/taler-exchange-secmod-cs.conf
new file mode 100644
index 00000000..5085eab7
--- /dev/null
+++ b/src/util/taler-exchange-secmod-cs.conf
@@ -0,0 +1,23 @@
+[taler-exchange-secmod-cs]
+
+# How long should generated coins overlap in their validity
+# periods. Should be long enough to avoid problems with
+# wallets picking one key and then due to network latency
+# another key being valid.  The DURATION_WITHDRAW period
+# must be longer than this value.
+OVERLAP_DURATION = 5 m
+
+# Where do we store the generated private keys.
+KEY_DIR = ${TALER_DATA_HOME}/exchange-secmod-cs/keys
+
+# Where does the helper listen for requests?
+UNIXPATH = $TALER_RUNTIME_DIR/exchange-secmod-cs/server.sock
+
+# Directory for clients.
+CLIENT_DIR = $TALER_RUNTIME_DIR/exchange-secmod-cs/clients
+
+# Where should the security module store its own private key?
+SM_PRIV_KEY = ${TALER_DATA_HOME}/exchange-secmod-cs/secmod-private-key
+
+# For how long into the future do we pre-generate keys?
+LOOKAHEAD_SIGN = 1 year
diff --git a/src/util/taler-exchange-secmod-cs.h 
b/src/util/taler-exchange-secmod-cs.h
new file mode 100644
index 00000000..c8e348b2
--- /dev/null
+++ b/src/util/taler-exchange-secmod-cs.h
@@ -0,0 +1,258 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file util/taler-exchange-secmod-cs.h
+ * @brief IPC messages for the CS crypto helper.
+ * @author Christian Grothoff
+ * @author Gian Demarmels
+ * @author Lucien Heuzeveldt
+ */
+#ifndef TALER_EXCHANGE_SECMOD_CS_H
+#define TALER_EXCHANGE_SECMOD_CS_H
+
+#define TALER_HELPER_CS_MT_PURGE 1
+#define TALER_HELPER_CS_MT_AVAIL 2
+
+#define TALER_HELPER_CS_MT_REQ_INIT 4
+#define TALER_HELPER_CS_MT_REQ_SIGN 5
+#define TALER_HELPER_CS_MT_REQ_REVOKE 6
+#define TALER_HELPER_CS_MT_REQ_RDERIVE 7
+
+#define TALER_HELPER_CS_MT_RES_SIGNATURE 8
+#define TALER_HELPER_CS_MT_RES_SIGN_FAILURE 9
+#define TALER_HELPER_CS_MT_RES_RDERIVE 10
+#define TALER_HELPER_CS_MT_RES_RDERIVE_FAILURE 11
+
+#define TALER_HELPER_CS_SYNCED 12
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+
+/**
+ * Message sent if a key is available.
+ */
+struct TALER_CRYPTO_CsKeyAvailableNotification
+{
+  /**
+   * Type is #TALER_HELPER_CS_MT_AVAIL
+   */
+  struct GNUNET_MessageHeader header;
+
+  /**
+   * Number of bytes of the public key.
+   */
+  uint16_t pub_size;
+
+  /**
+   * Number of bytes of the section name.
+   */
+  uint16_t section_name_len;
+
+  /**
+   * When does the key become available?
+   */
+  struct GNUNET_TIME_TimestampNBO anchor_time;
+
+  /**
+   * How long is the key available after @e anchor_time?
+   */
+  struct GNUNET_TIME_RelativeNBO duration_withdraw;
+
+  /**
+   * Public key used to generate the @e sicm_sig.
+   */
+  struct TALER_SecurityModulePublicKeyP secm_pub;
+
+  /**
+   * Signature affirming the announcement, of
+   * purpose #TALER_SIGNATURE_SM_DENOMINATION_KEY.
+   */
+  struct TALER_SecurityModuleSignatureP secm_sig;
+
+  /* followed by @e pub_size bytes of the CS public key */
+
+  /* followed by @e section_name bytes of the configuration section name
+     of the denomination of this key */
+
+};
+
+
+/**
+ * Message sent if a key was purged.
+ */
+struct TALER_CRYPTO_CsKeyPurgeNotification
+{
+  /**
+   * Type is #TALER_HELPER_CS_MT_PURGE.
+   */
+  struct GNUNET_MessageHeader header;
+
+  /**
+   * For now, always zero.
+   */
+  uint32_t reserved;
+
+  /**
+   * Hash of the public key of the purged CS key.
+   */
+  struct TALER_CsPubHashP h_cs;
+
+};
+
+
+/**
+ * Message sent if a signature is requested.
+ */
+struct TALER_CRYPTO_CsSignRequest
+{
+  /**
+   * Type is #TALER_HELPER_CS_MT_REQ_SIGN.
+   */
+  struct GNUNET_MessageHeader header;
+
+  /**
+   * For now, always zero.
+   */
+  uint32_t reserved;
+
+  /**
+   * Hash of the public key of the CS key to use for the signature.
+   */
+  struct TALER_CsPubHashP h_cs;
+
+  /* followed by message to sign */
+};
+
+/**
+ * Message sent if a signature is requested.
+ */
+struct TALER_CRYPTO_CsRDeriveRequest
+{
+  /**
+   * Type is #TALER_HELPER_CS_MT_REQ_RDERIVE.
+   */
+  struct GNUNET_MessageHeader header;
+
+  /**
+   * For now, always zero.
+   */
+  uint32_t reserved;
+
+  /**
+   * Hash of the public key of the CS key to use for the derivation.
+   */
+  struct TALER_CsPubHashP h_cs;
+
+  /* followed by Withdraw nonce to derive R  */
+};
+
+/**
+ * Message sent if a key was revoked.
+ */
+struct TALER_CRYPTO_CsRevokeRequest
+{
+  /**
+   * Type is #TALER_HELPER_CS_MT_REQ_REVOKE.
+   */
+  struct GNUNET_MessageHeader header;
+
+  /**
+   * For now, always zero.
+   */
+  uint32_t reserved;
+
+  /**
+   * Hash of the public key of the revoked CS key.
+   */
+  struct TALER_CsPubHashP h_cs;
+
+};
+
+
+/**
+ * Message sent if a signature was successfully computed.
+ */
+struct TALER_CRYPTO_SignResponse
+{
+  /**
+   * Type is #TALER_HELPER_CS_MT_RES_SIGNATURE.
+   */
+  struct GNUNET_MessageHeader header;
+
+  /**
+   * For now, always zero.
+   */
+  uint32_t reserved;
+
+  /* followed by CS signature */
+};
+
+/**
+ * Message sent if a R is successfully derived
+ */
+struct TALER_CRYPTO_RDeriveResponse
+{
+  /**
+   * Type is #TALER_HELPER_CS_MT_RES_RDERIVE.
+   */
+  struct GNUNET_MessageHeader header;
+
+  /**
+   * For now, always zero.
+   */
+  uint32_t reserved;
+
+  /* followed by derived R */
+};
+
+
+/**
+ * Message sent if signing failed.
+ */
+struct TALER_CRYPTO_SignFailure
+{
+  /**
+   * Type is #TALER_HELPER_CS_MT_RES_SIGN_FAILURE.
+   */
+  struct GNUNET_MessageHeader header;
+
+  /**
+   * If available, Taler error code. In NBO.
+   */
+  uint32_t ec;
+
+};
+
+/**
+ * Message sent if derivation failed.
+ */
+struct TALER_CRYPTO_RDeriveFailure
+{
+  /**
+   * Type is #TALER_HELPER_CS_MT_RES_RDERIVE_FAILURE.
+   */
+  struct GNUNET_MessageHeader header;
+
+  /**
+   * If available, Taler error code. In NBO.
+   */
+  uint32_t ec;
+
+};
+GNUNET_NETWORK_STRUCT_END
+
+
+#endif
diff --git a/src/util/test_helper_cs.c b/src/util/test_helper_cs.c
new file mode 100644
index 00000000..bc2287ce
--- /dev/null
+++ b/src/util/test_helper_cs.c
@@ -0,0 +1,692 @@
+/*
+  This file is part of TALER
+  (C) 2020, 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/test_helper_cs.c
+ * @brief Tests for CS crypto helper
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_util.h"
+
+/**
+ * Configuration has 1 minute duration and 5 minutes lookahead, but
+ * we do not get 'revocations' for expired keys. So this must be
+ * large enough to deal with key rotation during the runtime of
+ * the benchmark.
+ */
+#define MAX_KEYS 1024
+
+/**
+ * How many random key revocations should we test?
+ */
+#define NUM_REVOKES 3
+
+/**
+ * How many iterations of the successful signing test should we run?
+ */
+#define NUM_SIGN_TESTS 5
+
+/**
+ * How many iterations of the successful signing test should we run
+ * during the benchmark phase?
+ */
+#define NUM_SIGN_PERFS 100
+
+/**
+ * How many parallel clients should we use for the parallel
+ * benchmark? (> 500 may cause problems with the max open FD number limit).
+ */
+#define NUM_CORES 8
+
+/**
+ * Number of keys currently in #keys.
+ */
+static unsigned int num_keys;
+
+/**
+ * Keys currently managed by the helper.
+ */
+struct KeyData
+{
+  /**
+   * Validity start point.
+   */
+  struct GNUNET_TIME_Timestamp start_time;
+
+  /**
+   * Key expires for signing at @e start_time plus this value.
+   */
+  struct GNUNET_TIME_Relative validity_duration;
+
+  /**
+   * Hash of the public key.
+   */
+  struct TALER_CsPubHashP h_cs;
+
+  /**
+   * Full public key.
+   */
+  struct TALER_DenominationPublicKey denom_pub;
+
+  /**
+   * Is this key currently valid?
+   */
+  bool valid;
+
+  /**
+   * Did the test driver revoke this key?
+   */
+  bool revoked;
+};
+
+/**
+ * Array of all the keys we got from the helper.
+ */
+static struct KeyData keys[MAX_KEYS];
+
+
+/**
+ * Release memory occupied by #keys.
+ */
+static void
+free_keys (void)
+{
+  for (unsigned int i = 0; i<MAX_KEYS; i++)
+    if (keys[i].valid)
+    {
+      TALER_denom_pub_free (&keys[i].denom_pub);
+      keys[i].valid = false;
+      GNUNET_assert (num_keys > 0);
+      num_keys--;
+    }
+}
+
+
+/**
+ * Function called with information about available keys for signing.  Usually
+ * only called once per key upon connect. Also called again in case a key is
+ * being revoked, in that case with an @a end_time of zero.  Stores the keys
+ * status in #keys.
+ *
+ * @param cls closure, NULL
+ * @param section_name name of the denomination type in the configuration;
+ *                 NULL if the key has been revoked or purged
+ * @param start_time when does the key become available for signing;
+ *                 zero if the key has been revoked or purged
+ * @param validity_duration how long does the key remain available for signing;
+ *                 zero if the key has been revoked or purged
+ * @param h_cs hash of the @a denom_pub that is available (or was purged)
+ * @param denom_pub the public key itself, NULL if the key was revoked or 
purged
+ * @param sm_pub public key of the security module, NULL if the key was 
revoked or purged
+ * @param sm_sig signature from the security module, NULL if the key was 
revoked or purged
+ *               The signature was already verified against @a sm_pub.
+ */
+static void
+key_cb (void *cls,
+        const char *section_name,
+        struct GNUNET_TIME_Timestamp start_time,
+        struct GNUNET_TIME_Relative validity_duration,
+        const struct TALER_CsPubHashP *h_cs,
+        const struct TALER_DenominationPublicKey *denom_pub,
+        const struct TALER_SecurityModulePublicKeyP *sm_pub,
+        const struct TALER_SecurityModuleSignatureP *sm_sig)
+{
+  (void) cls;
+  (void) sm_pub;
+  (void) sm_sig;
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Key notification about key %s in `%s'\n",
+              GNUNET_h2s (&h_cs->hash),
+              section_name);
+  if (0 == validity_duration.rel_value_us)
+  {
+    bool found = false;
+
+    GNUNET_break (NULL == denom_pub);
+    GNUNET_break (NULL == section_name);
+    for (unsigned int i = 0; i<MAX_KEYS; i++)
+      if (0 == GNUNET_memcmp (h_cs,
+                              &keys[i].h_cs))
+      {
+        keys[i].valid = false;
+        keys[i].revoked = false;
+        TALER_denom_pub_free (&keys[i].denom_pub);
+        GNUNET_assert (num_keys > 0);
+        num_keys--;
+        found = true;
+        break;
+      }
+    if (! found)
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Error: helper announced expiration of unknown key!\n");
+
+    return;
+  }
+
+  GNUNET_break (NULL != denom_pub);
+  for (unsigned int i = 0; i<MAX_KEYS; i++)
+    if (! keys[i].valid)
+    {
+      keys[i].valid = true;
+      keys[i].h_cs = *h_cs;
+      keys[i].start_time = start_time;
+      keys[i].validity_duration = validity_duration;
+      TALER_denom_pub_deep_copy (&keys[i].denom_pub,
+                                 denom_pub);
+      num_keys++;
+      return;
+    }
+  /* too many keys! */
+  GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+              "Error: received %d live keys from the service!\n",
+              MAX_KEYS + 1);
+}
+
+
+/**
+ * Test key revocation logic.
+ *
+ * @param dh handle to the helper
+ * @return 0 on success
+ */
+static int
+test_revocation (struct TALER_CRYPTO_CsDenominationHelper *dh)
+{
+  struct timespec req = {
+    .tv_nsec = 250000000
+  };
+
+  for (unsigned int i = 0; i<NUM_REVOKES; i++)
+  {
+    uint32_t off;
+
+    off = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK,
+                                    num_keys);
+    /* find index of key to revoke */
+    for (unsigned int j = 0; j < MAX_KEYS; j++)
+    {
+      if (! keys[j].valid)
+        continue;
+      if (0 != off)
+      {
+        off--;
+        continue;
+      }
+      keys[j].revoked = true;
+      fprintf (stderr,
+               "Revoking key %s ...",
+               GNUNET_h2s (&keys[j].h_cs.hash));
+      TALER_CRYPTO_helper_cs_revoke (dh,
+                                     &keys[j].h_cs);
+      for (unsigned int k = 0; k<1000; k++)
+      {
+        TALER_CRYPTO_helper_cs_poll (dh);
+        if (! keys[j].revoked)
+          break;
+        nanosleep (&req, NULL);
+        fprintf (stderr, ".");
+      }
+      if (keys[j].revoked)
+      {
+        fprintf (stderr,
+                 "\nFAILED: timeout trying to revoke key %u\n",
+                 j);
+        TALER_CRYPTO_helper_cs_disconnect (dh);
+        return 2;
+      }
+      fprintf (stderr, "\n");
+      break;
+    }
+  }
+  return 0;
+}
+
+
+/**
+ * Test signing logic.
+ *
+ * @param dh handle to the helper
+ * @return 0 on success
+ */
+static int
+test_signing (struct TALER_CRYPTO_CsDenominationHelper *dh)
+{
+  struct TALER_BlindedDenominationSignature ds;
+  enum TALER_ErrorCode ec;
+  bool success = false;
+  struct TALER_PlanchetSecretsP ps;
+  struct TALER_CoinPubHash c_hash;
+
+  TALER_planchet_setup_random (&ps, TALER_DENOMINATION_RSA);
+  for (unsigned int i = 0; i<MAX_KEYS; i++)
+  {
+    if (! keys[i].valid)
+      continue;
+    {
+      struct TALER_PlanchetDetail pd;
+      pd.blinded_planchet.cipher = TALER_DENOMINATION_RSA;
+      // keys[i].denom_pub.cipher = TALER_DENOMINATION_CS;
+
+      GNUNET_assert (GNUNET_YES ==
+                     TALER_planchet_prepare (&keys[i].denom_pub,
+                                             &ps,
+                                             &c_hash,
+                                             &pd));
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Requesting signature over %u bytes with key %s\n",
+                  (unsigned
+                   int) pd.blinded_planchet.details.rsa_blinded_planchet.
+                  blinded_msg_size,
+                  GNUNET_h2s (&keys[i].h_cs.hash));
+      ds = TALER_CRYPTO_helper_cs_sign (dh,
+                                        &keys[i].h_cs,
+                                        pd.blinded_planchet.details.
+                                        rsa_blinded_planchet.blinded_msg,
+                                        pd.blinded_planchet.details.
+                                        rsa_blinded_planchet.blinded_msg_size,
+                                        &ec);
+      GNUNET_free (
+        pd.blinded_planchet.details.rsa_blinded_planchet.blinded_msg);
+    }
+    switch (ec)
+    {
+    case TALER_EC_NONE:
+      if (GNUNET_TIME_relative_cmp (GNUNET_TIME_absolute_get_remaining (
+                                      keys[i].start_time.abs_time),
+                                    >,
+                                    GNUNET_TIME_UNIT_SECONDS))
+      {
+        /* key worked too early */
+        GNUNET_break (0);
+        return 4;
+      }
+      if (GNUNET_TIME_relative_cmp (GNUNET_TIME_absolute_get_duration (
+                                      keys[i].start_time.abs_time),
+                                    >,
+                                    keys[i].validity_duration))
+      {
+        /* key worked too later */
+        GNUNET_break (0);
+        return 5;
+      }
+      {
+        struct TALER_DenominationSignature rs;
+
+        if (GNUNET_OK !=
+            TALER_denom_sig_unblind (&rs,
+                                     &ds,
+                                     &ps.blinding_key,
+                                     &keys[i].denom_pub))
+        {
+          GNUNET_break (0);
+          return 6;
+        }
+        TALER_blinded_denom_sig_free (&ds);
+        if (GNUNET_OK !=
+            TALER_denom_pub_verify (&keys[i].denom_pub,
+                                    &rs,
+                                    &c_hash))
+        {
+          /* signature invalid */
+          GNUNET_break (0);
+          TALER_denom_sig_free (&rs);
+          return 7;
+        }
+        TALER_denom_sig_free (&rs);
+      }
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Received valid signature for key %s\n",
+                  GNUNET_h2s (&keys[i].h_cs.hash));
+      success = true;
+      break;
+    case TALER_EC_EXCHANGE_DENOMINATION_HELPER_TOO_EARLY:
+      /* This 'failure' is expected, we're testing also for the
+         error handling! */
+      if ( (GNUNET_TIME_relative_is_zero (
+              GNUNET_TIME_absolute_get_remaining (
+                keys[i].start_time.abs_time))) &&
+           (GNUNET_TIME_relative_cmp (
+              GNUNET_TIME_absolute_get_duration (
+                keys[i].start_time.abs_time),
+              <,
+              keys[i].validity_duration)) )
+      {
+        /* key should have worked! */
+        GNUNET_break (0);
+        return 6;
+      }
+      break;
+    default:
+      /* unexpected error */
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Unexpected error %d\n",
+                  ec);
+      return 7;
+    }
+  }
+  if (! success)
+  {
+    /* no valid key for signing found, also bad */
+    GNUNET_break (0);
+    return 16;
+  }
+
+  /* check signing does not work if the key is unknown */
+  {
+    struct TALER_CsPubHashP rnd;
+
+    GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+                                &rnd,
+                                sizeof (rnd));
+    ds = TALER_CRYPTO_helper_cs_sign (dh,
+                                      &rnd,
+                                      "Hello",
+                                      strlen ("Hello"),
+                                      &ec);
+    if (TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN != ec)
+    {
+      if (TALER_EC_NONE == ec)
+        TALER_blinded_denom_sig_free (&ds);
+      GNUNET_break (0);
+      return 17;
+    }
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Signing with invalid key %s failed as desired\n",
+                GNUNET_h2s (&rnd.hash));
+  }
+  return 0;
+}
+
+
+/**
+ * Benchmark signing logic.
+ *
+ * @param dh handle to the helper
+ * @return 0 on success
+ */
+static int
+perf_signing (struct TALER_CRYPTO_CsDenominationHelper *dh,
+              const char *type)
+{
+  struct TALER_BlindedDenominationSignature ds;
+  enum TALER_ErrorCode ec;
+  struct GNUNET_TIME_Relative duration;
+  struct TALER_PlanchetSecretsP ps;
+
+  TALER_planchet_setup_random (&ps, TALER_DENOMINATION_RSA);
+  duration = GNUNET_TIME_UNIT_ZERO;
+  TALER_CRYPTO_helper_cs_poll (dh);
+  for (unsigned int j = 0; j<NUM_SIGN_PERFS;)
+  {
+    for (unsigned int i = 0; i<MAX_KEYS; i++)
+    {
+      if (! keys[i].valid)
+        continue;
+      if (GNUNET_TIME_relative_cmp (GNUNET_TIME_absolute_get_remaining (
+                                      keys[i].start_time.abs_time),
+                                    >,
+                                    GNUNET_TIME_UNIT_SECONDS))
+        continue;
+      if (GNUNET_TIME_relative_cmp (GNUNET_TIME_absolute_get_duration (
+                                      keys[i].start_time.abs_time),
+                                    >,
+                                    keys[i].validity_duration))
+        continue;
+      {
+        struct TALER_CoinPubHash c_hash;
+        struct TALER_PlanchetDetail pd;
+
+        GNUNET_assert (GNUNET_YES ==
+                       TALER_planchet_prepare (&keys[i].denom_pub,
+                                               &ps,
+                                               &c_hash,
+                                               &pd));
+        /* use this key as long as it works */
+        while (1)
+        {
+          struct GNUNET_TIME_Absolute start = GNUNET_TIME_absolute_get ();
+          struct GNUNET_TIME_Relative delay;
+
+          ds = TALER_CRYPTO_helper_cs_sign (dh,
+                                            &keys[i].h_cs,
+                                            pd.blinded_planchet.details.
+                                            rsa_blinded_planchet.blinded_msg,
+                                            pd.blinded_planchet.details.
+                                            rsa_blinded_planchet.
+                                            blinded_msg_size,
+                                            &ec);
+          if (TALER_EC_NONE != ec)
+            break;
+          delay = GNUNET_TIME_absolute_get_duration (start);
+          duration = GNUNET_TIME_relative_add (duration,
+                                               delay);
+          TALER_blinded_denom_sig_free (&ds);
+          j++;
+          if (NUM_SIGN_PERFS <= j)
+            break;
+        }
+        GNUNET_free (
+          pd.blinded_planchet.details.rsa_blinded_planchet.blinded_msg);
+      }
+    } /* for i */
+  } /* for j */
+  fprintf (stderr,
+           "%u (%s) signature operations took %s\n",
+           (unsigned int) NUM_SIGN_PERFS,
+           type,
+           GNUNET_STRINGS_relative_time_to_string (duration,
+                                                   GNUNET_YES));
+  return 0;
+}
+
+
+/**
+ * Parallel signing logic.
+ *
+ * @param esh handle to the helper
+ * @return 0 on success
+ */
+static int
+par_signing (struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+  struct GNUNET_TIME_Absolute start;
+  struct GNUNET_TIME_Relative duration;
+  pid_t pids[NUM_CORES];
+  struct TALER_CRYPTO_CsDenominationHelper *dh;
+
+  start = GNUNET_TIME_absolute_get ();
+  for (unsigned int i = 0; i<NUM_CORES; i++)
+  {
+    pids[i] = fork ();
+    num_keys = 0;
+    GNUNET_assert (-1 != pids[i]);
+    if (0 == pids[i])
+    {
+      int ret;
+
+      dh = TALER_CRYPTO_helper_cs_connect (cfg,
+                                           &key_cb,
+                                           NULL);
+      GNUNET_assert (NULL != dh);
+      ret = perf_signing (dh,
+                          "parallel");
+      TALER_CRYPTO_helper_cs_disconnect (dh);
+      free_keys ();
+      exit (ret);
+    }
+  }
+  for (unsigned int i = 0; i<NUM_CORES; i++)
+  {
+    int wstatus;
+
+    GNUNET_assert (pids[i] ==
+                   waitpid (pids[i],
+                            &wstatus,
+                            0));
+  }
+  duration = GNUNET_TIME_absolute_get_duration (start);
+  fprintf (stderr,
+           "%u (parallel) signature operations took %s (total real time)\n",
+           (unsigned int) NUM_SIGN_PERFS * NUM_CORES,
+           GNUNET_STRINGS_relative_time_to_string (duration,
+                                                   GNUNET_YES));
+  return 0;
+}
+
+
+/**
+ * Main entry point into the test logic with the helper already running.
+ */
+static int
+run_test (void)
+{
+  struct GNUNET_CONFIGURATION_Handle *cfg;
+  struct TALER_CRYPTO_CsDenominationHelper *dh;
+  struct timespec req = {
+    .tv_nsec = 250000000
+  };
+  int ret;
+
+  cfg = GNUNET_CONFIGURATION_create ();
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_load (cfg,
+                                 "test_helper_cs.conf"))
+  {
+    GNUNET_break (0);
+    return 77;
+  }
+
+  fprintf (stderr, "Waiting for helper to start ... ");
+  for (unsigned int i = 0; i<100; i++)
+  {
+    nanosleep (&req,
+               NULL);
+    dh = TALER_CRYPTO_helper_cs_connect (cfg,
+                                         &key_cb,
+                                         NULL);
+    if (NULL != dh)
+      break;
+    fprintf (stderr, ".");
+  }
+  if (NULL == dh)
+  {
+    fprintf (stderr,
+             "\nFAILED: timeout trying to connect to helper\n");
+    GNUNET_CONFIGURATION_destroy (cfg);
+    return 1;
+  }
+  if (0 == num_keys)
+  {
+    fprintf (stderr,
+             "\nFAILED: timeout trying to connect to helper\n");
+    TALER_CRYPTO_helper_cs_disconnect (dh);
+    GNUNET_CONFIGURATION_destroy (cfg);
+    return 1;
+  }
+  fprintf (stderr,
+           " Done (%u keys)\n",
+           num_keys);
+  ret = 0;
+  if (0 == ret)
+    ret = test_revocation (dh);
+  if (0 == ret)
+    ret = test_signing (dh);
+  if (0 == ret)
+    ret = perf_signing (dh,
+                        "sequential");
+  TALER_CRYPTO_helper_cs_disconnect (dh);
+  free_keys ();
+  if (0 == ret)
+    ret = par_signing (cfg);
+  /* clean up our state */
+  GNUNET_CONFIGURATION_destroy (cfg);
+  return ret;
+}
+
+
+int
+main (int argc,
+      const char *const argv[])
+{
+  struct GNUNET_OS_Process *helper;
+  char *libexec_dir;
+  char *binary_name;
+  int ret;
+  enum GNUNET_OS_ProcessStatusType type;
+  unsigned long code;
+
+  (void) argc;
+  (void) argv;
+  unsetenv ("XDG_DATA_HOME");
+  unsetenv ("XDG_CONFIG_HOME");
+  GNUNET_log_setup ("test-helper-cs",
+                    "WARNING",
+                    NULL);
+  GNUNET_OS_init (TALER_project_data_default ());
+  libexec_dir = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_BINDIR);
+  GNUNET_asprintf (&binary_name,
+                   "%s/%s",
+                   libexec_dir,
+                   "taler-exchange-secmod-cs");
+  GNUNET_free (libexec_dir);
+  helper = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ERR,
+                                    NULL, NULL, NULL,
+                                    binary_name,
+                                    binary_name,
+                                    "-c",
+                                    "test_helper_cs.conf",
+                                    "-L",
+                                    "WARNING",
+                                    NULL);
+  if (NULL == helper)
+  {
+    GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+                              "exec",
+                              binary_name);
+    GNUNET_free (binary_name);
+    return 77;
+  }
+  GNUNET_free (binary_name);
+  ret = run_test ();
+
+  GNUNET_OS_process_kill (helper,
+                          SIGTERM);
+  if (GNUNET_OK !=
+      GNUNET_OS_process_wait_status (helper,
+                                     &type,
+                                     &code))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Helper process did not die voluntarily, killing hard\n");
+    GNUNET_OS_process_kill (helper,
+                            SIGKILL);
+    ret = 4;
+  }
+  else if ( (GNUNET_OS_PROCESS_EXITED != type) ||
+            (0 != code) )
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Helper died with unexpected status %d/%d\n",
+                (int) type,
+                (int) code);
+    ret = 5;
+  }
+  GNUNET_OS_process_destroy (helper);
+  return ret;
+}
+
+
+/* end of test_helper_cs.c */
diff --git a/src/util/test_helper_cs.conf b/src/util/test_helper_cs.conf
new file mode 100644
index 00000000..a5d1211a
--- /dev/null
+++ b/src/util/test_helper_cs.conf
@@ -0,0 +1,11 @@
+[PATHS]
+# Persistent data storage for the testcase
+TALER_TEST_HOME = test_helper_cs_home/
+
+[coin_1]
+DURATION_WITHDRAW = 1 minute
+RSA_KEYSIZE = 2048
+
+[taler-exchange-secmod-cs]
+LOOKAHEAD_SIGN = 5 minutes
+OVERLAP_DURATION = 1 s

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]