From 18db69be2d2bbacc6b9f4de2e9e8f8db2df4febe Mon Sep 17 00:00:00 2001 From: Gian Demarmels Date: Mon, 3 Jan 2022 14:38:59 +0100 Subject: initial cs_secmod implementation --- src/include/taler_crypto_lib.h | 130 +++ src/testing/.gitignore | 2 + src/util/.gitignore | 3 + src/util/Makefile.am | 27 +- src/util/crypto_helper_cs.c | 643 +++++++++++++ src/util/denom.c | 16 + src/util/taler-exchange-secmod-cs.c | 1580 ++++++++++++++++++++++++++++++++ src/util/taler-exchange-secmod-cs.conf | 23 + src/util/taler-exchange-secmod-cs.h | 258 ++++++ src/util/test_helper_cs.c | 692 ++++++++++++++ src/util/test_helper_cs.conf | 11 + 11 files changed, 3382 insertions(+), 3 deletions(-) create mode 100644 src/util/crypto_helper_cs.c create mode 100644 src/util/taler-exchange-secmod-cs.c create mode 100644 src/util/taler-exchange-secmod-cs.conf create mode 100644 src/util/taler-exchange-secmod-cs.h create mode 100644 src/util/test_helper_cs.c create mode 100644 src/util/test_helper_cs.conf diff --git a/src/include/taler_crypto_lib.h b/src/include/taler_crypto_lib.h index ff145cc41..bd889b354 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 8c19b94c8..f721009e6 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 f25567f32..c5f8c76dd 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 35e580347..997b49f29 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 000000000..94d98f13c --- /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 +*/ +/** + * @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 +#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 9d8acfcae..908302600 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 000000000..12a1fbbd0 --- /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 +*/ +/** + * @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 +#include +#include +#include "taler_error_codes.h" +#include "taler_signatures.h" +#include "secmod_common.h" +#include + + +/** + * 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 000000000..5085eab79 --- /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 000000000..c8e348b2a --- /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 +*/ +/** + * @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 000000000..bc2287ce4 --- /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 +*/ +/** + * @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 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 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, + 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, + 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