diff options
-rw-r--r-- | src/util/.gitignore | 1 | ||||
-rw-r--r-- | src/util/Makefile.am | 10 | ||||
-rw-r--r-- | src/util/taler-helper-crypto-eddsa.c | 1563 | ||||
-rw-r--r-- | src/util/taler-helper-crypto-eddsa.h | 188 |
4 files changed, 1762 insertions, 0 deletions
diff --git a/src/util/.gitignore b/src/util/.gitignore index 8a8cc0524..656403c57 100644 --- a/src/util/.gitignore +++ b/src/util/.gitignore @@ -1,4 +1,5 @@ taler-config test_payto taler-helper-crypto-rsa +taler-helper-crypto-eddsa test_helper_rsa diff --git a/src/util/Makefile.am b/src/util/Makefile.am index 73edce2cd..5663c6236 100644 --- a/src/util/Makefile.am +++ b/src/util/Makefile.am @@ -21,6 +21,7 @@ EXTRA_DIST = \ test_helper_rsa.conf libexec_PROGRAMS = \ + taler-helper-crypto-eddsa \ taler-helper-crypto-rsa bin_SCRIPTS = \ @@ -44,6 +45,15 @@ taler_helper_crypto_rsa_LDADD = \ $(LIBGCRYPT_LIBS) \ $(XLIB) +taler_helper_crypto_eddsa_SOURCES = \ + taler-helper-crypto-eddsa.c taler-helper-crypto-eddsa.h +taler_helper_crypto_eddsa_LDADD = \ + libtalerutil.la \ + -lgnunetutil \ + -lpthread \ + $(LIBGCRYPT_LIBS) \ + $(XLIB) + lib_LTLIBRARIES = \ libtalerutil.la diff --git a/src/util/taler-helper-crypto-eddsa.c b/src/util/taler-helper-crypto-eddsa.c new file mode 100644 index 000000000..2f6a6cc9e --- /dev/null +++ b/src/util/taler-helper-crypto-eddsa.c @@ -0,0 +1,1563 @@ +/* + This file is part of TALER + Copyright (C) 2014-2020 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file util/taler-helper-crypto-eddsa.c + * @brief Standalone process to perform private key EDDSA operations + * @author Christian Grothoff + * + * INTEGRATION NOTES: + * - Option 'DURATION_OVERLAP' renamed to 'OVERLAP_DURATION' for consistency; + * => need to update in deployment scripts and default configuration! + * - option 'KEY_DIR' moved from section 'exchange' to 'taler-helper-crypto-eddsa'! + * + * Key design points: + * - EVERY thread of the exchange will have its own pair of connections to the + * crypto helpers. This way, every threat will also have its own /keys state + * and avoid the need to synchronize on those. + * - auditor signatures and master signatures are to be kept in the exchange DB, + * and merged with the public keys of the helper by the exchange HTTPD! + * - the main loop of the helper is SINGLE-THREADED, but there are + * threads for crypto-workers which (only) do the signing in parallel, + * working of a work-queue. + * - thread-safety: signing happens in parallel, thus when REMOVING private keys, + * we must ensure that all signers are done before we fully free() the + * private key. This is done by reference counting (as work is always + * assigned and collected by the main thread). + */ +#include "platform.h" +#include "taler_util.h" +#include "taler-helper-crypto-eddsa.h" +#include <gcrypt.h> +#include <pthread.h> +#include <sys/eventfd.h> +#include "taler_error_codes.h" + +/** + * One particular key. + */ +struct Key +{ + + /** + * Kept in a DLL. Sorted by anchor time. + */ + struct Key *next; + + /** + * Kept in a DLL. Sorted by anchor time. + */ + struct Key *prev; + + /** + * Name of the file this key is stored under. + */ + char *filename; + + /** + * The private key. + */ + struct TALER_ExchangePrivateKeyP exchange_priv; + + /** + * The public key. + */ + struct TALER_ExchangePublicKeyP exchange_pub; + + /** + * Time at which this key is supposed to become valid. + */ + struct GNUNET_TIME_Absolute anchor; + + /** + * Reference counter. Counts the number of threads that are + * using this key at this time. + */ + unsigned int rc; + + /** + * Flag set to true if this key has been purged and the memory + * must be freed as soon as @e rc hits zero. + */ + bool purge; + +}; + + +/** + * Information we keep for a client connected to us. + */ +struct Client +{ + + /** + * Kept in a DLL. + */ + struct Client *next; + + /** + * Kept in a DLL. + */ + struct Client *prev; + + /** + * Client address. + */ + struct sockaddr_un addr; + + /** + * Number of bytes used in @e addr. + */ + socklen_t addr_size; + +}; + + +struct WorkItem +{ + + /** + * Kept in a DLL. + */ + struct WorkItem *next; + + /** + * Kept in a DLL. + */ + struct WorkItem *prev; + + /** + * Key to be used for this operation. + */ + struct Key *key; + + /** + * EDDSA signature over @e msg using @e key. Result of doing the work. + */ + struct TALER_ExchangeSignatureP signature; + + /** + * Message to sign. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose *purpose; + + /** + * Client address. + */ + struct sockaddr_un addr; + + /** + * Number of bytes used in @e addr. + */ + socklen_t addr_size; + + /** + * Operation status code. + */ + enum TALER_ErrorCode ec; + +}; + + +/** + * Head of DLL of actual keys, sorted by anchor. + */ +static struct Key *keys_head; + +/** + * Tail of DLL of actual keys. + */ +static struct Key *keys_tail; + +/** + * How long can a key be used? + */ +static struct GNUNET_TIME_Relative duration; + +/** + * Return value from main(). + */ +static int global_ret; + +/** + * Number of worker threads to use. Default (0) is to use one per CPU core + * available. + * Length of the #workers array. + */ +static unsigned int num_workers; + +/** + * Time when the key update is executed. + * Either the actual current time, or a pretended time. + */ +static struct GNUNET_TIME_Absolute now; + +/** + * The time for the key update, as passed by the user + * on the command line. + */ +static struct GNUNET_TIME_Absolute now_tmp; + +/** + * Handle to the exchange's configuration + */ +static const struct GNUNET_CONFIGURATION_Handle *kcfg; + +/** + * Where do we store the keys? + */ +static char *keydir; + +/** + * How much should coin creation duration overlap + * with the next key? Basically, the starting time of two + * keys is always #duration - #duration_overlap 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; + +/** + * Our listen socket. + */ +static struct GNUNET_NETWORK_Handle *unix_sock; + +/** + * Path where we are listening. + */ +static char *unixpath; + +/** + * Task run to accept new inbound connections. + */ +static struct GNUNET_SCHEDULER_Task *read_task; + +/** + * Task run to generate new keys. + */ +static struct GNUNET_SCHEDULER_Task *keygen_task; + +/** + * Head of DLL of clients connected to us. + */ +static struct Client *clients_head; + +/** + * Tail of DLL of clients connected to us. + */ +static struct Client *clients_tail; + +/** + * Head of DLL with pending signing operations. + */ +static struct WorkItem *work_head; + +/** + * Tail of DLL with pending signing operations. + */ +static struct WorkItem *work_tail; + +/** + * Lock for the work queue. + */ +static pthread_mutex_t work_lock; + +/** + * Condition variable for the semaphore of the work queue. + */ +static pthread_cond_t work_cond = PTHREAD_COND_INITIALIZER; + +/** + * Number of items in the work queue. Also used as the semaphore counter. + */ +static unsigned long long work_counter; + +/** + * Head of DLL with completed signing operations. + */ +static struct WorkItem *done_head; + +/** + * Tail of DLL with completed signing operations. + */ +static struct WorkItem *done_tail; + +/** + * Lock for the done queue. + */ +static pthread_mutex_t done_lock; + +/** + * Task waiting for work to be done. + */ +static struct GNUNET_SCHEDULER_Task *done_task; + +/** + * Signal used by threads to notify the #done_task that they + * completed work that is now in the done queue. + */ +static struct GNUNET_NETWORK_Handle *done_signal; + +/** + * Set once we are in shutdown and workers should terminate. + */ +static volatile bool in_shutdown; + +/** + * Array of #num_worker sign_worker() threads. + */ +static pthread_t *workers; + + +/** + * Main function of a worker thread that signs. + * + * @param cls NULL + * @return NULL + */ +static void * +sign_worker (void *cls) +{ + (void) cls; + GNUNET_assert (0 == pthread_mutex_lock (&work_lock)); + while (! in_shutdown) + { + struct WorkItem *wi; + + while (NULL != (wi = work_head)) + { + /* take work from queue */ + GNUNET_CONTAINER_DLL_remove (work_head, + work_tail, + wi); + work_counter--; + GNUNET_assert (0 == pthread_mutex_unlock (&work_lock)); + { + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_sign_ (&wi->key->exchange_priv.eddsa_priv, + wi->purpose, + &wi->signature.eddsa_signature)) + wi->ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; + else + wi->ec = TALER_EC_NONE; + } + /* put completed work into done queue */ + GNUNET_assert (0 == pthread_mutex_lock (&done_lock)); + GNUNET_CONTAINER_DLL_insert (done_head, + done_tail, + wi); + GNUNET_assert (0 == pthread_mutex_unlock (&done_lock)); + { + uint64_t val = GNUNET_htonll (1); + + /* raise #done_signal */ + if (sizeof(val) != + write (GNUNET_NETWORK_get_fd (done_signal), + &val, + sizeof (val))) + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "write(eventfd)"); + } + GNUNET_assert (0 == pthread_mutex_lock (&work_lock)); + } + /* queue is empty, wait for work */ + GNUNET_assert (0 == + pthread_cond_wait (&work_cond, + &work_lock)); + } + GNUNET_assert (0 == + pthread_mutex_unlock (&work_lock)); + return NULL; +} + + +/** + * Free @a client, releasing all (remaining) state. + * + * @param[in] client data to free + */ +static void +free_client (struct Client *client) +{ + GNUNET_CONTAINER_DLL_remove (clients_head, + clients_tail, + client); + GNUNET_free (client); +} + + +/** + * Function run to read incoming requests from a client. + * + * @param cls the `struct Client` + */ +static void +read_job (void *cls); + + +/** + * Free @a key. It must already have been removed from the DLL. + * + * @param[in] key the key to free + */ +static void +free_key (struct Key *key) +{ + GNUNET_free (key->filename); + GNUNET_free (key); +} + + +/** + * Send a message starting with @a hdr to @a client. + * + * @param addr address where to send the message + * @param addr_size number of bytes in @a addr + * @param hdr beginning of the message, length indicated in size field + * @return #GNUNET_OK on success + */ +static int +transmit (const struct sockaddr_un *addr, + socklen_t addr_size, + const struct GNUNET_MessageHeader *hdr) +{ + ssize_t ret; + + ret = GNUNET_NETWORK_socket_sendto (unix_sock, + hdr, + ntohs (hdr->size), + (const struct sockaddr *) addr, + addr_size); + if (ret != ntohs (hdr->size)) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_INFO, + "sendto"); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Process completed tasks that are in the #done_head queue, sending + * the result back to the client (and resuming the client). + * + * @param cls NULL + */ +static void +handle_done (void *cls) +{ + uint64_t data; + (void) cls; + + /* consume #done_signal */ + if (sizeof (data) != + read (GNUNET_NETWORK_get_fd (done_signal), + &data, + sizeof (data))) + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "read(eventfd)"); + done_task = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL, + done_signal, + &handle_done, + NULL); + GNUNET_assert (0 == pthread_mutex_lock (&done_lock)); + while (NULL != done_head) + { + struct WorkItem *wi = done_head; + + GNUNET_CONTAINER_DLL_remove (done_head, + done_tail, + wi); + GNUNET_assert (0 == pthread_mutex_unlock (&done_lock)); + if (TALER_EC_NONE != wi->ec) + { + struct TALER_CRYPTO_SignFailure sf = { + .header.size = htons (sizeof (sf)), + .header.type = htons (TALER_HELPER_EDDSA_MT_RES_SIGN_FAILURE), + .ec = htonl (wi->ec) + }; + + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Signing request failed, worker failed to produce signature\n"); + (void) transmit (&wi->addr, + wi->addr_size, + &sf.header); + } + else + { + struct TALER_CRYPTO_SignResponse sr = { + .header.size = htons (sizeof (sr)), + .header.type = htons (TALER_HELPER_EDDSA_MT_RES_SIGNATURE), + .exchange_pub = wi->key->exchange_pub, + .exchange_sig = wi->signature + }; + + (void) transmit (&wi->addr, + wi->addr_size, + &sr.header); + } + { + struct Key *key = wi->key; + + key->rc--; + if ( (0 == key->rc) && + (key->purge) ) + free_key (key); + } + GNUNET_free (wi); + GNUNET_assert (0 == pthread_mutex_lock (&done_lock)); + } + GNUNET_assert (0 == pthread_mutex_unlock (&done_lock)); + +} + + +/** + * Handle @a client request @a sr to create signature. Create the + * signature using the respective key and return the result to + * the client. + * + * @param addr address of the client making the request + * @param addr_size number of bytes in @a addr + * @param sr the request details + */ +static void +handle_sign_request (const struct sockaddr_un *addr, + socklen_t addr_size, + const struct TALER_CRYPTO_SignRequest *sr) +{ + const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose = &sr->purpose; + struct WorkItem *wi; + size_t purpose_size = ntohs (sr->header.size) - sizeof (*sr) + + sizeof (*purpose); + + if (purpose_size != htonl (purpose->size)) + { + struct TALER_CRYPTO_SignFailure sf = { + .header.size = htons (sizeof (sr)), + .header.type = htons (TALER_HELPER_EDDSA_MT_RES_SIGN_FAILURE), + .ec = htonl (TALER_EC_GENERIC_PARAMETER_MALFORMED) + }; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Signing request failed, request malformed\n"); + (void) transmit (addr, + addr_size, + &sf.header); + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Received request to sign over %u bytes\n", + (unsigned int) purpose_size); + wi = GNUNET_new (struct WorkItem); + wi->addr = *addr; + wi->addr_size = addr_size; + wi->key = keys_head; + keys_head->rc++; + wi->purpose = GNUNET_memdup (purpose, + purpose_size); + GNUNET_assert (0 == pthread_mutex_lock (&work_lock)); + work_counter++; + GNUNET_CONTAINER_DLL_insert (work_head, + work_tail, + wi); + GNUNET_assert (0 == pthread_mutex_unlock (&work_lock)); + GNUNET_assert (0 == pthread_cond_signal (&work_cond)); +} + + +/** + * Notify @a client about @a key becoming available. + * + * @param[in,out] client the client to notify; possible freed if transmission fails + * @param key the key to notify @a client about + * @return #GNUNET_OK on success + */ +static int +notify_client_key_add (struct Client *client, + const struct Key *key) +{ + struct TALER_CRYPTO_EddsaKeyAvailableNotification an = { + .header.size = htons (sizeof (an)), + .header.type = htons (TALER_HELPER_EDDSA_MT_AVAIL), + .anchor_time = GNUNET_TIME_absolute_hton (key->anchor), + .duration = GNUNET_TIME_relative_hton (duration), + .exchange_pub = key->exchange_pub + }; + + if (GNUNET_OK != + transmit (&client->addr, + client->addr_size, + &an.header)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Client %s must have disconnected\n", + client->addr.sun_path); + free_client (client); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Notify @a client about @a key being purged. + * + * @param[in,out] client the client to notify; possible freed if transmission fails + * @param key the key to notify @a client about + * @return #GNUNET_OK on success + */ +static int +notify_client_key_del (struct Client *client, + const struct Key *key) +{ + struct TALER_CRYPTO_EddsaKeyPurgeNotification pn = { + .header.type = htons (TALER_HELPER_EDDSA_MT_PURGE), + .header.size = htons (sizeof (pn)), + .exchange_pub = key->exchange_pub + }; + + if (GNUNET_OK != + transmit (&client->addr, + client->addr_size, + &pn.header)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Client %s must have disconnected\n", + client->addr.sun_path); + free_client (client); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Initialize key material for key @a key (also on disk). + * + * @param[in,out] key to compute key material for + * @param position where in the DLL will the @a key go + * @return #GNUNET_OK on success + */ +static int +setup_key (struct Key *key, + struct Key *position) +{ + struct GNUNET_CRYPTO_EddsaPrivateKey priv; + struct GNUNET_CRYPTO_EddsaPublicKey pub; + + GNUNET_CRYPTO_eddsa_key_create (&priv); + GNUNET_CRYPTO_eddsa_key_get_public (&priv, + &pub); + GNUNET_asprintf (&key->filename, + "%s/%llu", + keydir, + (unsigned long long) (key->anchor.abs_value_us + / GNUNET_TIME_UNIT_SECONDS.rel_value_us)); + if (sizeof (priv) != + GNUNET_DISK_fn_write (key->filename, + &priv, + sizeof (priv), + GNUNET_DISK_PERM_USER_READ)) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "write", + key->filename); + return GNUNET_SYSERR; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Setup fresh private key in `%s'\n", + key->filename); + key->exchange_priv.eddsa_priv = priv; + key->exchange_pub.eddsa_pub = pub; + GNUNET_CONTAINER_DLL_insert_after (keys_head, + keys_tail, + position, + key); + + /* tell clients about new key */ + { + struct Client *nxt; + + for (struct Client *client = clients_head; + NULL != client; + client = nxt) + { + nxt = client->next; + if (GNUNET_OK != + notify_client_key_add (client, + key)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Failed to notify client about new key, client dropped\n"); + } + } + } + return GNUNET_OK; +} + + +/** + * 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 addr address of the client making the request + * @param addr_size number of bytes in @a addr + * @param rr the revocation request + */ +static void +handle_revoke_request (const struct sockaddr_un *addr, + socklen_t addr_size, + const struct TALER_CRYPTO_RevokeRequest *rr) +{ + struct Key *key; + struct Key *nkey; + + nkey = NULL; + for (struct Key *pos = keys_head; NULL != pos; pos = pos->next) + if (0 == GNUNET_memcmp (&pos->exchange_pub, + &rr->exchange_pub)) + { + key = pos; + break; + } + if (NULL == key) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Revocation request ignored, key unknown\n"); + return; + } + + /* kill existing key, done first to ensure this always happens */ + if (0 != unlink (key->filename)) + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "unlink", + key->filename); + + /* Setup replacement key */ + nkey = GNUNET_new (struct Key); + nkey->anchor = key->anchor; + if (GNUNET_OK != + setup_key (nkey, + key)) + { + GNUNET_break (0); + GNUNET_SCHEDULER_shutdown (); + global_ret = 44; + return; + } + + /* get rid of the old key */ + key->purge = true; + GNUNET_CONTAINER_DLL_remove (keys_head, + keys_tail, + key); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Revocation complete\n"); + + /* Tell clients this key is gone */ + { + struct Client *nxt; + + for (struct Client *client = clients_head; + NULL != client; + client = nxt) + { + nxt = client->next; + if (GNUNET_OK != + notify_client_key_del (client, + key)) + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Failed to notify client about revoked key, client dropped\n"); + } + } + if (0 == key->rc) + free_key (key); +} + + +static void +read_job (void *cls) +{ + struct Client *client = cls; + char buf[65536]; + ssize_t buf_size; + const struct GNUNET_MessageHeader *hdr; + struct sockaddr_un addr; + socklen_t addr_size = sizeof (addr); + + read_task = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL, + unix_sock, + &read_job, + NULL); + buf_size = GNUNET_NETWORK_socket_recvfrom (unix_sock, + buf, + sizeof (buf), + (struct sockaddr *) &addr, + &addr_size); + if (-1 == buf_size) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "recv"); + return; + } + if (0 == buf_size) + { + return; + } + if (buf_size < sizeof (struct GNUNET_MessageHeader)) + { + GNUNET_break_op (0); + return; + } + hdr = (const struct GNUNET_MessageHeader *) buf; + if (ntohs (hdr->size) != buf_size) + { + GNUNET_break_op (0); + free_client (client); + return; + } + switch (ntohs (hdr->type)) + { + case TALER_HELPER_EDDSA_MT_REQ_INIT: + if (ntohs (hdr->size) != sizeof (struct GNUNET_MessageHeader)) + { + GNUNET_break_op (0); + return; + } + { + struct Client *client; + + client = GNUNET_new (struct Client); + client->addr = addr; + client->addr_size = addr_size; + GNUNET_CONTAINER_DLL_insert (clients_head, + clients_tail, + client); + for (struct Key *key = keys_head; + NULL != key; + key = key->next) + { + if (GNUNET_OK != + notify_client_key_add (client, + key)) + { + /* client died, skip the rest */ + break; + } + } + } + break; + case TALER_HELPER_EDDSA_MT_REQ_SIGN: + if (ntohs (hdr->size) <= sizeof (struct TALER_CRYPTO_SignRequest)) + { + GNUNET_break_op (0); + return; + } + handle_sign_request (&addr, + addr_size, + (const struct TALER_CRYPTO_SignRequest *) buf); + break; + case TALER_HELPER_EDDSA_MT_REQ_REVOKE: + if (ntohs (hdr->size) != sizeof (struct TALER_CRYPTO_RevokeRequest)) + { + GNUNET_break_op (0); + return; + } + handle_revoke_request (&addr, + addr_size, + (const struct TALER_CRYPTO_RevokeRequest *) buf); + break; + default: + GNUNET_break_op (0); + return; + } +} + + +/** + * Create a new key (we do not have enough). + * + * @return #GNUNET_OK on success + */ +static int +create_key (void) +{ + struct Key *key; + struct GNUNET_TIME_Absolute anchor; + struct GNUNET_TIME_Absolute now; + + now = GNUNET_TIME_absolute_get (); + (void) GNUNET_TIME_round_abs (&now); + if (NULL == keys_tail) + { + anchor = now; + } + else + { + anchor = GNUNET_TIME_absolute_add (keys_tail->anchor, + GNUNET_TIME_relative_subtract ( + duration, + overlap_duration)); + if (now.abs_value_us > anchor.abs_value_us) + anchor = now; + } + key = GNUNET_new (struct Key); + key->anchor = anchor; + if (GNUNET_OK != + setup_key (key, + keys_tail)) + { + GNUNET_free (key); + GNUNET_SCHEDULER_shutdown (); + global_ret = 42; + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * At what time does the current key set require its next action? Basically, + * the minimum of the expiration time of the oldest key, and the expiration + * time of the newest key minus the #lookahead_sign time. + */ +static struct GNUNET_TIME_Absolute +key_action_time (void) +{ + return GNUNET_TIME_absolute_min ( + GNUNET_TIME_absolute_add (keys_head->anchor, + duration), + GNUNET_TIME_absolute_subtract ( + GNUNET_TIME_absolute_subtract ( + GNUNET_TIME_absolute_add (keys_tail->anchor, + duration), + lookahead_sign), + overlap_duration)); +} + + +/** + * The validity period of a key @a key has expired. Purge it. + * + * @param[in] key expired key to purge and free + */ +static void +purge_key (struct Key *key) +{ + struct Client *nxt; + + for (struct Client *client = clients_head; + NULL != client; + client = nxt) + { + nxt = client->next; + if (GNUNET_OK != + notify_client_key_del (client, + key)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Failed to notify client about purged key, client dropped\n"); + } + } + GNUNET_CONTAINER_DLL_remove (keys_head, + keys_tail, + key); + if (0 != unlink (key->filename)) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "unlink", + key->filename); + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Purged expired private key `%s'\n", + key->filename); + } + GNUNET_free (key->filename); + if (0 != key->rc) + { + /* delay until all signing threads are done with this key */ + key->purge = true; + return; + } + GNUNET_free (key); +} + + +/** + * Create new keys and expire ancient keys. + * + * @param cls NULL + */ +static void +update_keys (void *cls) +{ + (void) cls; + + keygen_task = NULL; + /* create new keys */ + while ( (NULL == keys_tail) || + (0 == + GNUNET_TIME_absolute_get_remaining ( + GNUNET_TIME_absolute_subtract ( + GNUNET_TIME_absolute_subtract ( + GNUNET_TIME_absolute_add (keys_tail->anchor, + duration), + lookahead_sign), + overlap_duration)).rel_value_us) ) + GNUNET_assert (GNUNET_OK == + create_key ()); + /* remove expired keys */ + while ( (NULL != keys_head) && + (0 == + GNUNET_TIME_absolute_get_remaining + (GNUNET_TIME_absolute_add (keys_head->anchor, + duration)).rel_value_us) ) + purge_key (keys_head); + keygen_task = GNUNET_SCHEDULER_add_at (key_action_time (), + &update_keys, + NULL); +} + + +/** + * Parse private key from @a filename in @a buf. + * + * @param filename name of the file we are parsing, for logging + * @param buf key material + * @param buf_size number of bytes in @a buf + */ +static void +parse_key (const char *filename, + const void *buf, + size_t buf_size) +{ + struct GNUNET_CRYPTO_EddsaPrivateKey priv; + char *anchor_s; + char dummy; + unsigned long long anchor_ll; + struct GNUNET_TIME_Absolute anchor; + + anchor_s = strrchr (filename, + '/'); + if (NULL == anchor_s) + { + /* File in a directory without '/' in the name, this makes no sense. */ + GNUNET_break (0); + return; + } + anchor_s++; + if (1 != sscanf (anchor_s, + "%llu%c", + &anchor_ll, + &dummy)) + { + /* Filenames in KEYDIR must ONLY be the anchor time in seconds! */ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Filename `%s' invalid for key file, skipping\n", + filename); + return; + } + anchor.abs_value_us = anchor_ll * GNUNET_TIME_UNIT_SECONDS.rel_value_us; + if (anchor_ll != anchor.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; + } + if (buf_size != sizeof (priv)) + { + /* Parser failure. */ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "File `%s' is malformed, skipping\n", + filename); + return; + } + memcpy (&priv, + buf, + buf_size); + + { + struct GNUNET_CRYPTO_EddsaPublicKey pub; + struct Key *key; + struct Key *before; + + GNUNET_CRYPTO_eddsa_key_get_public (&priv, + &pub); + key = GNUNET_new (struct Key); + key->exchange_priv.eddsa_priv = priv; + key->exchange_pub.eddsa_pub = pub; + key->anchor = anchor; + key->filename = GNUNET_strdup (filename); + before = NULL; + for (struct Key *pos = keys_head; + NULL != pos; + pos = pos->next) + { + if (pos->anchor.abs_value_us > anchor.abs_value_us) + break; + before = pos; + } + GNUNET_CONTAINER_DLL_insert_after (keys_head, + keys_tail, + before, + key); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Imported key from `%s'\n", + filename); + } +} + + +/** + * Import a private key from @a filename. + * + * @param cls NULL + * @param filename name of a file in the directory + */ +static int +import_key (void *cls, + const char *filename) +{ + 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); + 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); + return GNUNET_OK; + } + if (0 != (sbuf.st_mode & (S_IWUSR | S_IRWXG | S_IRWXO))) + { + /* permission are NOT tight, try to patch them up! */ + if (0 != + fchmod (fd, + S_IRUSR)) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, + "fchmod", + filename); + /* refuse to use key if file has wrong permissions */ + GNUNET_break (0 == close (fd)); + return GNUNET_OK; + } + } + fh = GNUNET_DISK_get_handle_from_int_fd (fd); + if (NULL == fh) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, + "open", + filename); + GNUNET_break (0 == close (fd)); + return GNUNET_OK; + } + if (sbuf.st_size > 2048) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "File `%s' to big to be a private key\n", + filename); + GNUNET_DISK_file_close (fh); + return GNUNET_OK; + } + ptr = GNUNET_DISK_file_map (fh, + &map, + GNUNET_DISK_MAP_TYPE_READ, + (size_t) sbuf.st_size); + if (NULL == ptr) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, + "mmap", + filename); + GNUNET_DISK_file_close (fh); + return GNUNET_OK; + } + parse_key (filename, + ptr, + (size_t) sbuf.st_size); + GNUNET_DISK_file_unmap (map); + GNUNET_DISK_file_close (fh); + return GNUNET_OK; +} + + +/** + * Load the various duration values from #kcfg. + * + * @return #GNUNET_OK on success + */ +static int +load_durations (void) +{ + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_time (kcfg, + "taler-helper-crypto-eddsa", + "OVERLAP_DURATION", + &overlap_duration)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "taler-helper-crypto-eddsa", + "OVERLAP_DURATION"); + return GNUNET_SYSERR; + } + GNUNET_TIME_round_rel (&overlap_duration); + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_time (kcfg, + "taler-helper-crypto-eddsa", + "LOOKAHEAD_SIGN", + &lookahead_sign)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "taler-helper-crypto-eddsa", + "LOOKAHEAD_SIGN"); + return GNUNET_SYSERR; + } + GNUNET_TIME_round_rel (&lookahead_sign); + return GNUNET_OK; +} + + +/** + * Function run on shutdown. Stops the various jobs (nicely). + * + * @param cls NULL + */ +static void +do_shutdown (void *cls) +{ + (void) cls; + if (NULL != read_task) + { + GNUNET_SCHEDULER_cancel (read_task); + read_task = NULL; + } + if (NULL != unix_sock) + { + GNUNET_break (GNUNET_OK == + GNUNET_NETWORK_socket_close (unix_sock)); + unix_sock = NULL; + } + if (0 != unlink (unixpath)) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, + "unlink", + unixpath); + } + GNUNET_free (unixpath); + if (NULL != keygen_task) + { + GNUNET_SCHEDULER_cancel (keygen_task); + keygen_task = NULL; + } + if (NULL != done_task) + { + GNUNET_SCHEDULER_cancel (done_task); + done_task = NULL; + } + /* shut down worker threads */ + GNUNET_assert (0 == pthread_mutex_lock (&work_lock)); + in_shutdown = true; + GNUNET_assert (0 == pthread_cond_broadcast (&work_cond)); + GNUNET_assert (0 == pthread_mutex_unlock (&work_lock)); + for (unsigned int i = 0; i<num_workers; i++) + GNUNET_assert (0 == pthread_join (workers[i], + NULL)); + if (NULL != done_signal) + { + GNUNET_break (GNUNET_OK == + GNUNET_NETWORK_socket_close (done_signal)); + done_signal = 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) +{ + (void) cls; + (void) args; + (void) cfgfile; + kcfg = cfg; + if (now.abs_value_us != now_tmp.abs_value_us) + { + /* The user gave "--now", use it! */ + now = now_tmp; + } + else + { + /* get current time again, we may be timetraveling! */ + now = GNUNET_TIME_absolute_get (); + } + GNUNET_TIME_round_abs (&now); + if (GNUNET_OK != + load_durations ()) + { + global_ret = 1; + return; + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_filename (kcfg, + "taler-helper-crypto-eddsa", + "KEY_DIR", + &keydir)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "taler-helper-crypto-eddsa", + "KEY_DIR"); + global_ret = 1; + return; + } + + /* open socket */ + { + int sock; + + sock = socket (PF_UNIX, + SOCK_DGRAM, + 0); + if (-1 == sock) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "socket"); + global_ret = 2; + return; + } + { + struct sockaddr_un un; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_filename (kcfg, + "taler-helper-crypto-eddsa", + "UNIXPATH", + &unixpath)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "taler-helper-crypto-eddsa", + "UNIXPATH"); + global_ret = 3; + return; + } + if (GNUNET_OK != + GNUNET_DISK_directory_create_for_file (unixpath)) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, + "mkdir(dirname)", + unixpath); + } + if (0 != unlink (unixpath)) + { + if (ENOENT != errno) + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, + "unlink", + unixpath); + } + memset (&un, + 0, + sizeof (un)); + un.sun_family = AF_UNIX; + strncpy (un.sun_path, + unixpath, + sizeof (un.sun_path)); + if (0 != bind (sock, + (const struct sockaddr *) &un, + sizeof (un))) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "bind", + unixpath); + global_ret = 3; + GNUNET_break (0 == close (sock)); + return; + } + } + unix_sock = GNUNET_NETWORK_socket_box_native (sock); + } + + GNUNET_SCHEDULER_add_shutdown (&do_shutdown, + NULL); + + /* Load keys */ + GNUNET_break (GNUNET_OK == + GNUNET_DISK_directory_create (keydir)); + GNUNET_DISK_directory_scan (keydir, + &import_key, + NULL); + update_keys (NULL); + if (NULL == keys_head) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "No keys could be created. Strange.\n"); + global_ret = 5; + GNUNET_SCHEDULER_shutdown (); + return; + } + + /* start job to accept incoming requests on 'sock' */ + read_task = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL, + unix_sock, + &read_job, + NULL); + + /* start job to keep keys up-to-date */ + keygen_task = GNUNET_SCHEDULER_add_now (&update_keys, + NULL); + + /* start job to handle completed work */ + { + int fd; + + fd = eventfd (0, + EFD_NONBLOCK | EFD_CLOEXEC); + if (-1 == fd) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "eventfd"); + global_ret = 6; + GNUNET_SCHEDULER_shutdown (); + return; + } + done_signal = GNUNET_NETWORK_socket_box_native (fd); + } + done_task = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL, + done_signal, + &handle_done, + NULL); + + /* start crypto workers */ + if (0 == num_workers) + num_workers = sysconf (_SC_NPROCESSORS_CONF); + workers = GNUNET_new_array (num_workers, + pthread_t); + for (unsigned int i = 0; i<num_workers; i++) + GNUNET_assert (0 == + pthread_create (&workers[i], + NULL, + &sign_worker, + 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_uint ('p', + "parallelism", + "NUM_WORKERS", + "number of worker threads to use", + &num_workers), + GNUNET_GETOPT_option_absolute_time ('t', + "time", + "TIMESTAMP", + "pretend it is a different time for the update", + &now_tmp), + GNUNET_GETOPT_OPTION_END + }; + int ret; + + (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 */ + GNUNET_OS_init (TALER_project_data_default ()); + GNUNET_assert (GNUNET_OK == + GNUNET_log_setup ("taler-helper-crypto-eddsa", + "WARNING", + NULL)); + now = now_tmp = GNUNET_TIME_absolute_get (); + ret = GNUNET_PROGRAM_run (argc, argv, + "taler-helper-crypto-eddsa", + "Handle private EDDSA key operations for a Taler exchange", + options, + &run, + NULL); + if (GNUNET_NO == ret) + return 0; + if (GNUNET_SYSERR == ret) + return 1; + return global_ret; +} diff --git a/src/util/taler-helper-crypto-eddsa.h b/src/util/taler-helper-crypto-eddsa.h new file mode 100644 index 000000000..215af566c --- /dev/null +++ b/src/util/taler-helper-crypto-eddsa.h @@ -0,0 +1,188 @@ +/* + This file is part of TALER + Copyright (C) 2020 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file util/taler-helper-crypto-eddsa.h + * @brief IPC messages for the EDDSA crypto helper. + * @author Christian Grothoff + */ +#ifndef TALER_HELPER_CRYPTO_EDDSA_H +#define TALER_HELPER_CRYPTO_EDDSA_H + +#define TALER_HELPER_EDDSA_MT_PURGE 11 +#define TALER_HELPER_EDDSA_MT_AVAIL 12 + +#define TALER_HELPER_EDDSA_MT_REQ_INIT 14 +#define TALER_HELPER_EDDSA_MT_REQ_SIGN 15 +#define TALER_HELPER_EDDSA_MT_REQ_REVOKE 16 + +#define TALER_HELPER_EDDSA_MT_RES_SIGNATURE 17 +#define TALER_HELPER_EDDSA_MT_RES_SIGN_FAILURE 18 + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Message sent if a key is available. + */ +struct TALER_CRYPTO_EddsaKeyAvailableNotification +{ + /** + * Type is #TALER_HELPER_EDDSA_MT_AVAIL + */ + struct GNUNET_MessageHeader header; + + /** + * For now, always zero. + */ + uint32_t reserved; + + /** + * When does the key become available? + */ + struct GNUNET_TIME_AbsoluteNBO anchor_time; + + /** + * How long is the key available after @e anchor_time? + */ + struct GNUNET_TIME_RelativeNBO duration; + + /** + * The public key. + */ + struct TALER_ExchangePublicKeyP exchange_pub; + +}; + + +/** + * Message sent if a key was purged. + */ +struct TALER_CRYPTO_EddsaKeyPurgeNotification +{ + /** + * Type is #TALER_HELPER_EDDSA_MT_PURGE. + */ + struct GNUNET_MessageHeader header; + + /** + * For now, always zero. + */ + uint32_t reserved; + + /** + * The public key. + */ + struct TALER_ExchangePublicKeyP exchange_pub; + +}; + + +/** + * Message sent if a signature is requested. + */ +struct TALER_CRYPTO_SignRequest +{ + /** + * Type is #TALER_HELPER_EDDSA_MT_REQ_SIGN. + */ + struct GNUNET_MessageHeader header; + + /** + * For now, always zero. + */ + uint32_t reserved; + + /** + * What should be signed over. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /* followed by rest of data to sign */ +}; + + +/** + * Message sent if a key was revoked. + */ +struct TALER_CRYPTO_RevokeRequest +{ + /** + * Type is #TALER_HELPER_EDDSA_MT_REQ_REVOKE. + */ + struct GNUNET_MessageHeader header; + + /** + * For now, always zero. + */ + uint32_t reserved; + + /** + * The public key to revoke. + */ + struct TALER_ExchangePublicKeyP exchange_pub; + +}; + + +/** + * Message sent if a signature was successfully computed. + */ +struct TALER_CRYPTO_SignResponse +{ + /** + * Type is #TALER_HELPER_EDDSA_MT_RES_SIGNATURE. + */ + struct GNUNET_MessageHeader header; + + /** + * For now, always zero. + */ + uint32_t reserved; + + /** + * The public key used for the signature. + */ + struct TALER_ExchangePublicKeyP exchange_pub; + + /** + * The public key to use for the signature. + */ + struct TALER_ExchangeSignatureP exchange_sig; + +}; + + +/** + * Message sent if signing failed. + */ +struct TALER_CRYPTO_SignFailure +{ + /** + * Type is #TALER_HELPER_EDDSA_MT_RES_SIGN_FAILURE. + */ + struct GNUNET_MessageHeader header; + + /** + * If available, Taler error code. In NBO. + */ + uint32_t ec; + +}; + + +GNUNET_NETWORK_STRUCT_END + + +#endif |