diff options
| author | Gian Demarmels <gian@demarmels.org> | 2022-01-03 14:38:59 +0100 | 
|---|---|---|
| committer | Gian Demarmels <gian@demarmels.org> | 2022-02-04 15:33:11 +0100 | 
| commit | 18db69be2d2bbacc6b9f4de2e9e8f8db2df4febe (patch) | |
| tree | c7630208dee223cdaef32097c2b9f66cc9b1bfa7 /src | |
| parent | f239b01be196f5ce64fdd9f0a6f42a11077c33c6 (diff) | |
initial cs_secmod implementation
Diffstat (limited to 'src')
| -rw-r--r-- | src/include/taler_crypto_lib.h | 130 | ||||
| -rw-r--r-- | src/testing/.gitignore | 2 | ||||
| -rw-r--r-- | src/util/.gitignore | 3 | ||||
| -rw-r--r-- | src/util/Makefile.am | 27 | ||||
| -rw-r--r-- | src/util/crypto_helper_cs.c | 643 | ||||
| -rw-r--r-- | src/util/denom.c | 16 | ||||
| -rw-r--r-- | src/util/taler-exchange-secmod-cs.c | 1580 | ||||
| -rw-r--r-- | src/util/taler-exchange-secmod-cs.conf | 23 | ||||
| -rw-r--r-- | src/util/taler-exchange-secmod-cs.h | 258 | ||||
| -rw-r--r-- | src/util/test_helper_cs.c | 692 | ||||
| -rw-r--r-- | 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 | 
