diff options
Diffstat (limited to 'src/util/secmod_eddsa.c')
-rw-r--r-- | src/util/secmod_eddsa.c | 1130 |
1 files changed, 1130 insertions, 0 deletions
diff --git a/src/util/secmod_eddsa.c b/src/util/secmod_eddsa.c new file mode 100644 index 000000000..9661534a8 --- /dev/null +++ b/src/util/secmod_eddsa.c @@ -0,0 +1,1130 @@ +/* + This file is part of TALER + Copyright (C) 2014-2021 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file util/secmod_eddsa.c + * @brief Standalone process to perform private key EDDSA operations + * @author Christian Grothoff + * + * Key design points: + * - EVERY thread of the exchange will have its own pair of connections to the + * crypto helpers. This way, every threat will also have its own /keys state + * and avoid the need to synchronize on those. + * - auditor signatures and master signatures are to be kept in the exchange DB, + * and merged with the public keys of the helper by the exchange HTTPD! + * - the main loop of the helper is SINGLE-THREADED, but there are + * threads for crypto-workers which (only) do the signing in parallel, + * one per client. + * - thread-safety: signing happens in parallel, thus when REMOVING private keys, + * we must ensure that all signers are done before we fully free() the + * private key. This is done by reference counting (as work is always + * assigned and collected by the main thread). + */ +#include "platform.h" +#include "taler_util.h" +#include "taler-exchange-secmod-eddsa.h" +#include <gcrypt.h> +#include <pthread.h> +#include "taler_error_codes.h" +#include "taler_signatures.h" +#include "secmod_common.h" +#include <poll.h> + + +/** + * One particular key. + */ +struct Key +{ + + /** + * Kept in a DLL. Sorted by anchor time. + */ + struct Key *next; + + /** + * Kept in a DLL. Sorted by anchor time. + */ + struct Key *prev; + + /** + * Name of the file this key is stored under. + */ + char *filename; + + /** + * The private key. + */ + struct TALER_ExchangePrivateKeyP exchange_priv; + + /** + * The public key. + */ + struct TALER_ExchangePublicKeyP exchange_pub; + + /** + * Time at which this key is supposed to become valid. + */ + struct GNUNET_TIME_Timestamp anchor; + + /** + * Generation when this key was created or revoked. + */ + uint64_t key_gen; + + /** + * Reference counter. Counts the number of threads that are + * using this key at this time. + */ + unsigned int rc; + + /** + * Flag set to true if this key has been purged and the memory + * must be freed as soon as @e rc hits zero. + */ + bool purge; + +}; + + +/** + * Head of DLL of actual keys, sorted by anchor. + */ +static struct Key *keys_head; + +/** + * Tail of DLL of actual keys. + */ +static struct Key *keys_tail; + +/** + * How long can a key be used? + */ +static struct GNUNET_TIME_Relative duration; + +/** + * Command-line options for various TALER_SECMOD_XXX_run() functions. + */ +static struct TALER_SECMOD_Options *globals; + +/** + * Where do we store the keys? + */ +static char *keydir; + +/** + * How much should coin creation duration overlap + * with the next key? Basically, the starting time of two + * keys is always #duration - #overlap_duration apart. + */ +static struct GNUNET_TIME_Relative overlap_duration; + +/** + * How long into the future do we pre-generate keys? + */ +static struct GNUNET_TIME_Relative lookahead_sign; + +/** + * Task run to generate new keys. + */ +static struct GNUNET_SCHEDULER_Task *keygen_task; + +/** + * Lock for the keys queue. + */ +static pthread_mutex_t keys_lock; + +/** + * Current key generation. + */ +static uint64_t key_gen; + + +/** + * Notify @a client about @a key becoming available. + * + * @param[in,out] client the client to notify; possible freed if transmission fails + * @param key the key to notify @a client about + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +notify_client_key_add (struct TES_Client *client, + const struct Key *key) +{ + struct TALER_CRYPTO_EddsaKeyAvailableNotification an = { + .header.size = htons (sizeof (an)), + .header.type = htons (TALER_HELPER_EDDSA_MT_AVAIL), + .anchor_time = GNUNET_TIME_timestamp_hton (key->anchor), + .duration = GNUNET_TIME_relative_hton (duration), + .exchange_pub = key->exchange_pub, + .secm_pub = TES_smpub + }; + + TALER_exchange_secmod_eddsa_sign (&key->exchange_pub, + key->anchor, + duration, + &TES_smpriv, + &an.secm_sig); + if (GNUNET_OK != + TES_transmit (client->csock, + &an.header)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Client %p must have disconnected\n", + client); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Notify @a client about @a key being purged. + * + * @param[in,out] client the client to notify; possible freed if transmission fails + * @param key the key to notify @a client about + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +notify_client_key_del (struct TES_Client *client, + const struct Key *key) +{ + struct TALER_CRYPTO_EddsaKeyPurgeNotification pn = { + .header.type = htons (TALER_HELPER_EDDSA_MT_PURGE), + .header.size = htons (sizeof (pn)), + .exchange_pub = key->exchange_pub + }; + + if (GNUNET_OK != + TES_transmit (client->csock, + &pn.header)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Client %p must have disconnected\n", + client); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Handle @a client request @a sr to create signature. Create the + * signature using the respective key and return the result to + * the client. + * + * @param client the client making the request + * @param sr the request details + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +handle_sign_request (struct TES_Client *client, + const struct TALER_CRYPTO_EddsaSignRequest *sr) +{ + const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose = &sr->purpose; + size_t purpose_size = ntohs (sr->header.size) - sizeof (*sr) + + sizeof (*purpose); + struct Key *key; + struct TALER_CRYPTO_EddsaSignResponse sres = { + .header.size = htons (sizeof (sres)), + .header.type = htons (TALER_HELPER_EDDSA_MT_RES_SIGNATURE) + }; + enum TALER_ErrorCode ec; + + if (purpose_size != htonl (purpose->size)) + { + struct TALER_CRYPTO_EddsaSignFailure sf = { + .header.size = htons (sizeof (sr)), + .header.type = htons (TALER_HELPER_EDDSA_MT_RES_SIGN_FAILURE), + .ec = htonl (TALER_EC_GENERIC_PARAMETER_MALFORMED) + }; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Signing request failed, request malformed\n"); + return TES_transmit (client->csock, + &sf.header); + } + + GNUNET_assert (0 == pthread_mutex_lock (&keys_lock)); + key = keys_head; + while ( (NULL != key) && + (GNUNET_TIME_absolute_is_past ( + GNUNET_TIME_absolute_add (key->anchor.abs_time, + duration))) ) + { + struct Key *nxt = key->next; + + if (0 != key->rc) + break; /* do later */ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Deleting past key %s (expired %s ago)\n", + TALER_B2S (&nxt->exchange_pub), + GNUNET_TIME_relative2s ( + GNUNET_TIME_absolute_get_duration ( + GNUNET_TIME_absolute_add (key->anchor.abs_time, + duration)), + GNUNET_YES)); + GNUNET_CONTAINER_DLL_remove (keys_head, + keys_tail, + key); + if ( (! key->purge) && + (0 != unlink (key->filename)) ) + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "unlink", + key->filename); + GNUNET_free (key->filename); + GNUNET_free (key); + key = nxt; + } + if (NULL == key) + { + GNUNET_break (0); + ec = TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING; + } + else + { + GNUNET_assert (key->rc < UINT_MAX); + key->rc++; + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_sign_ (&key->exchange_priv.eddsa_priv, + purpose, + &sres.exchange_sig.eddsa_signature)) + ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; + else + ec = TALER_EC_NONE; + sres.exchange_pub = key->exchange_pub; + GNUNET_assert (0 == pthread_mutex_lock (&keys_lock)); + GNUNET_assert (key->rc > 0); + key->rc--; + } + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + if (TALER_EC_NONE != ec) + { + struct TALER_CRYPTO_EddsaSignFailure sf = { + .header.size = htons (sizeof (sf)), + .header.type = htons (TALER_HELPER_EDDSA_MT_RES_SIGN_FAILURE), + .ec = htonl ((uint32_t) ec) + }; + + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Signing request %p failed, worker failed to produce signature\n", + client); + return TES_transmit (client->csock, + &sf.header); + } + return TES_transmit (client->csock, + &sres.header); +} + + +/** + * Initialize key material for key @a key (also on disk). + * + * @param[in,out] key to compute key material for + * @param position where in the DLL will the @a key go + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +setup_key (struct Key *key, + struct Key *position) +{ + struct GNUNET_CRYPTO_EddsaPrivateKey priv; + struct GNUNET_CRYPTO_EddsaPublicKey pub; + + GNUNET_CRYPTO_eddsa_key_create (&priv); + GNUNET_CRYPTO_eddsa_key_get_public (&priv, + &pub); + GNUNET_asprintf (&key->filename, + "%s/%llu", + keydir, + (unsigned long long) (key->anchor.abs_time.abs_value_us + / GNUNET_TIME_UNIT_SECONDS.rel_value_us + )); + if (GNUNET_OK != + GNUNET_DISK_fn_write (key->filename, + &priv, + sizeof (priv), + GNUNET_DISK_PERM_USER_READ)) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "write", + key->filename); + return GNUNET_SYSERR; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Setup fresh private key in `%s'\n", + key->filename); + key->key_gen = key_gen; + key->exchange_priv.eddsa_priv = priv; + key->exchange_pub.eddsa_pub = pub; + GNUNET_CONTAINER_DLL_insert_after (keys_head, + keys_tail, + position, + key); + return GNUNET_OK; +} + + +/** + * The validity period of a key @a key has expired. Purge it. + * + * @param[in] key expired or revoked key to purge + */ +static void +purge_key (struct Key *key) +{ + if (key->purge) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Key %s already purged, skipping\n", + TALER_B2S (&key->exchange_pub)); + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Purging key %s\n", + TALER_B2S (&key->exchange_pub)); + if (0 != unlink (key->filename)) + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "unlink", + key->filename); + key->purge = true; + key->key_gen = key_gen; + GNUNET_free (key->filename); +} + + +/** + * A @a client informs us that a key has been revoked. + * Check if the key is still in use, and if so replace (!) + * it with a fresh key. + * + * @param client the client making the request + * @param rr the revocation request + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +handle_revoke_request (struct TES_Client *client, + const struct TALER_CRYPTO_EddsaRevokeRequest *rr) +{ + struct Key *key; + struct Key *nkey; + + (void) client; + key = NULL; + GNUNET_assert (0 == pthread_mutex_lock (&keys_lock)); + for (struct Key *pos = keys_head; + NULL != pos; + pos = pos->next) + if (0 == GNUNET_memcmp (&pos->exchange_pub, + &rr->exchange_pub)) + { + key = pos; + break; + } + if (NULL == key) + { + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Revocation request ignored, key unknown\n"); + return GNUNET_OK; + } + if (key->purge) + { + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Revocation request ignored, key %s already revoked\n", + TALER_B2S (&key->exchange_pub)); + return GNUNET_OK; + } + key_gen++; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Revoking key %s, bumping generation to %llu\n", + TALER_B2S (&key->exchange_pub), + (unsigned long long) key_gen); + purge_key (key); + + /* Setup replacement key */ + nkey = GNUNET_new (struct Key); + nkey->anchor = key->anchor; + if (GNUNET_OK != + setup_key (nkey, + key)) + { + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + GNUNET_break (0); + GNUNET_SCHEDULER_shutdown (); + globals->global_ret = EXIT_FAILURE; + return GNUNET_SYSERR; + } + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + TES_wake_clients (); + return GNUNET_OK; +} + + +/** + * Handle @a hdr message received from @a client. + * + * @param client the client that received the message + * @param hdr message that was received + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +eddsa_work_dispatch (struct TES_Client *client, + const struct GNUNET_MessageHeader *hdr) +{ + uint16_t msize = ntohs (hdr->size); + + switch (ntohs (hdr->type)) + { + case TALER_HELPER_EDDSA_MT_REQ_SIGN: + if (msize < sizeof (struct TALER_CRYPTO_EddsaSignRequest)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return handle_sign_request ( + client, + (const struct TALER_CRYPTO_EddsaSignRequest *) hdr); + case TALER_HELPER_EDDSA_MT_REQ_REVOKE: + if (msize != sizeof (struct TALER_CRYPTO_EddsaRevokeRequest)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return handle_revoke_request ( + client, + (const struct TALER_CRYPTO_EddsaRevokeRequest *) hdr); + default: + GNUNET_break_op (0); + return GNUNET_SYSERR; + } +} + + +/** + * Send our initial key set to @a client together with the + * "sync" terminator. + * + * @param client the client to inform + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +eddsa_client_init (struct TES_Client *client) +{ + GNUNET_assert (0 == pthread_mutex_lock (&keys_lock)); + for (struct Key *key = keys_head; + NULL != key; + key = key->next) + { + if (GNUNET_OK != + notify_client_key_add (client, + key)) + { + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + GNUNET_break (0); + return GNUNET_SYSERR; + } + } + client->key_gen = key_gen; + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + { + struct GNUNET_MessageHeader synced = { + .type = htons (TALER_HELPER_EDDSA_SYNCED), + .size = htons (sizeof (synced)) + }; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Client %p synced\n", + client); + if (GNUNET_OK != + TES_transmit (client->csock, + &synced)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + } + return GNUNET_OK; +} + + +/** + * Notify @a client about all changes to the keys since + * the last generation known to the @a client. + * + * @param client the client to notify + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +eddsa_update_client_keys (struct TES_Client *client) +{ + GNUNET_assert (0 == pthread_mutex_lock (&keys_lock)); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Updating client %p to generation %llu\n", + client, + (unsigned long long) key_gen); + for (struct Key *key = keys_head; + NULL != key; + key = key->next) + { + if (key->key_gen <= client->key_gen) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Skipping key %s, no change since generation %llu\n", + TALER_B2S (&key->exchange_pub), + (unsigned long long) client->key_gen); + continue; + } + if (key->purge) + { + if (GNUNET_OK != + notify_client_key_del (client, + key)) + { + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + return GNUNET_SYSERR; + } + } + else + { + if (GNUNET_OK != + notify_client_key_add (client, + key)) + { + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + return GNUNET_SYSERR; + } + } + } + client->key_gen = key_gen; + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + return GNUNET_OK; +} + + +/** + * Create a new key (we do not have enough). + * + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +create_key (void) +{ + struct Key *key; + struct GNUNET_TIME_Timestamp anchor; + + anchor = GNUNET_TIME_timestamp_get (); + if (NULL != keys_tail) + { + struct GNUNET_TIME_Absolute abs; + + abs = GNUNET_TIME_absolute_add (keys_tail->anchor.abs_time, + GNUNET_TIME_relative_subtract ( + duration, + overlap_duration)); + if (GNUNET_TIME_absolute_cmp (anchor.abs_time, + <, + abs)) + anchor = GNUNET_TIME_absolute_to_timestamp (abs); + } + key = GNUNET_new (struct Key); + key->anchor = anchor; + if (GNUNET_OK != + setup_key (key, + keys_tail)) + { + GNUNET_break (0); + GNUNET_free (key); + GNUNET_SCHEDULER_shutdown (); + globals->global_ret = EXIT_FAILURE; + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * At what time does the current key set require its next action? Basically, + * the minimum of the expiration time of the oldest key, and the expiration + * time of the newest key minus the #lookahead_sign time. + */ +static struct GNUNET_TIME_Absolute +key_action_time (void) +{ + struct Key *nxt; + + nxt = keys_head; + while ( (NULL != nxt) && + (nxt->purge) ) + nxt = nxt->next; + if (NULL == nxt) + return GNUNET_TIME_UNIT_ZERO_ABS; + return GNUNET_TIME_absolute_min ( + GNUNET_TIME_absolute_add (nxt->anchor.abs_time, + duration), + GNUNET_TIME_absolute_subtract ( + GNUNET_TIME_absolute_subtract ( + GNUNET_TIME_absolute_add (keys_tail->anchor.abs_time, + duration), + lookahead_sign), + overlap_duration)); +} + + +/** + * Create new keys and expire ancient keys. + * + * @param cls NULL + */ +static void +update_keys (void *cls) +{ + bool wake = false; + struct Key *nxt; + + (void) cls; + keygen_task = NULL; + GNUNET_assert (0 == pthread_mutex_lock (&keys_lock)); + /* create new keys */ + while ( (NULL == keys_tail) || + GNUNET_TIME_absolute_is_past ( + GNUNET_TIME_absolute_subtract ( + GNUNET_TIME_absolute_subtract ( + GNUNET_TIME_absolute_add (keys_tail->anchor.abs_time, + duration), + lookahead_sign), + overlap_duration)) ) + { + if (! wake) + { + key_gen++; + wake = true; + } + if (GNUNET_OK != + create_key ()) + { + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + GNUNET_break (0); + globals->global_ret = EXIT_FAILURE; + GNUNET_SCHEDULER_shutdown (); + return; + } + } + nxt = keys_head; + /* purge expired keys */ + while ( (NULL != nxt) && + GNUNET_TIME_absolute_is_past ( + GNUNET_TIME_absolute_add (nxt->anchor.abs_time, + duration))) + { + if (! wake) + { + key_gen++; + wake = true; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Purging past key %s (expired %s ago)\n", + TALER_B2S (&nxt->exchange_pub), + GNUNET_TIME_relative2s ( + GNUNET_TIME_absolute_get_duration ( + GNUNET_TIME_absolute_add (nxt->anchor.abs_time, + duration)), + GNUNET_YES)); + purge_key (nxt); + nxt = nxt->next; + } + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + if (wake) + TES_wake_clients (); + keygen_task = GNUNET_SCHEDULER_add_at (key_action_time (), + &update_keys, + NULL); +} + + +/** + * Parse private key from @a filename in @a buf. + * + * @param filename name of the file we are parsing, for logging + * @param buf key material + * @param buf_size number of bytes in @a buf + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +parse_key (const char *filename, + const void *buf, + size_t buf_size) +{ + struct GNUNET_CRYPTO_EddsaPrivateKey priv; + char *anchor_s; + char dummy; + unsigned long long anchor_ll; + struct GNUNET_TIME_Timestamp anchor; + + anchor_s = strrchr (filename, + '/'); + if (NULL == anchor_s) + { + /* File in a directory without '/' in the name, this makes no sense. */ + GNUNET_break (0); + return GNUNET_SYSERR; + } + anchor_s++; + if (1 != sscanf (anchor_s, + "%llu%c", + &anchor_ll, + &dummy)) + { + /* Filenames in KEYDIR must ONLY be the anchor time in seconds! */ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Filename `%s' invalid for key file, skipping\n", + filename); + return GNUNET_SYSERR; + } + anchor.abs_time.abs_value_us = anchor_ll + * GNUNET_TIME_UNIT_SECONDS.rel_value_us; + if (anchor_ll != anchor.abs_time.abs_value_us + / GNUNET_TIME_UNIT_SECONDS.rel_value_us) + { + /* Integer overflow. Bad, invalid filename. */ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Filename `%s' invalid for key file, skipping\n", + filename); + return GNUNET_SYSERR; + } + if (buf_size != sizeof (priv)) + { + /* Parser failure. */ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "File `%s' is malformed, skipping\n", + filename); + return GNUNET_SYSERR; + } + GNUNET_memcpy (&priv, + buf, + buf_size); + + { + struct GNUNET_CRYPTO_EddsaPublicKey pub; + struct Key *key; + struct Key *before; + + GNUNET_CRYPTO_eddsa_key_get_public (&priv, + &pub); + GNUNET_assert (0 == pthread_mutex_lock (&keys_lock)); + key = GNUNET_new (struct Key); + key->exchange_priv.eddsa_priv = priv; + key->exchange_pub.eddsa_pub = pub; + key->anchor = anchor; + key->filename = GNUNET_strdup (filename); + key->key_gen = key_gen; + before = NULL; + for (struct Key *pos = keys_head; + NULL != pos; + pos = pos->next) + { + if (GNUNET_TIME_timestamp_cmp (pos->anchor, >, anchor)) + break; + before = pos; + } + GNUNET_CONTAINER_DLL_insert_after (keys_head, + keys_tail, + before, + key); + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Imported key from `%s'\n", + filename); + } + return GNUNET_OK; +} + + +/** + * Import a private key from @a filename. + * + * @param cls NULL + * @param filename name of a file in the directory + */ +static enum GNUNET_GenericReturnValue +import_key (void *cls, + const char *filename) +{ + struct GNUNET_DISK_FileHandle *fh; + struct GNUNET_DISK_MapHandle *map; + void *ptr; + int fd; + struct stat sbuf; + + (void) cls; + { + struct stat lsbuf; + + if (0 != lstat (filename, + &lsbuf)) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, + "lstat", + filename); + return GNUNET_OK; + } + if (! S_ISREG (lsbuf.st_mode)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "File `%s' is not a regular file, which is not allowed for private keys!\n", + filename); + return GNUNET_OK; + } + } + + fd = open (filename, + O_RDONLY | O_CLOEXEC); + if (-1 == fd) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, + "open", + filename); + return GNUNET_OK; + } + if (0 != fstat (fd, + &sbuf)) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, + "stat", + filename); + GNUNET_break (0 == close (fd)); + return GNUNET_OK; + } + if (! S_ISREG (sbuf.st_mode)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "File `%s' is not a regular file, which is not allowed for private keys!\n", + filename); + GNUNET_break (0 == close (fd)); + return GNUNET_OK; + } + if (0 != (sbuf.st_mode & (S_IWUSR | S_IRWXG | S_IRWXO))) + { + /* permission are NOT tight, try to patch them up! */ + if (0 != + fchmod (fd, + S_IRUSR)) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, + "fchmod", + filename); + /* refuse to use key if file has wrong permissions */ + GNUNET_break (0 == close (fd)); + return GNUNET_OK; + } + } + fh = GNUNET_DISK_get_handle_from_int_fd (fd); + if (NULL == fh) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, + "open", + filename); + GNUNET_break (0 == close (fd)); + return GNUNET_OK; + } + if (sbuf.st_size > 2048) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "File `%s' to big to be a private key\n", + filename); + GNUNET_DISK_file_close (fh); + return GNUNET_OK; + } + ptr = GNUNET_DISK_file_map (fh, + &map, + GNUNET_DISK_MAP_TYPE_READ, + (size_t) sbuf.st_size); + if (NULL == ptr) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, + "mmap", + filename); + GNUNET_DISK_file_close (fh); + return GNUNET_OK; + } + (void) parse_key (filename, + ptr, + (size_t) sbuf.st_size); + GNUNET_DISK_file_unmap (map); + GNUNET_DISK_file_close (fh); + return GNUNET_OK; +} + + +/** + * Load the various duration values from @a kcfg. + * + * @param cfg configuration to use + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +load_durations (const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + char *secname; + + GNUNET_asprintf (&secname, + "%s-secmod-eddsa", + globals->section); + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_time (cfg, + secname, + "OVERLAP_DURATION", + &overlap_duration)) + { + GNUNET_free (secname); + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + secname, + "OVERLAP_DURATION"); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_time (cfg, + secname, + "DURATION", + &duration)) + { + GNUNET_free (secname); + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + secname, + "DURATION"); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_time (cfg, + secname, + "LOOKAHEAD_SIGN", + &lookahead_sign)) + { + GNUNET_free (secname); + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + secname, + "LOOKAHEAD_SIGN"); + return GNUNET_SYSERR; + } + GNUNET_free (secname); + return GNUNET_OK; +} + + +/** + * Function run on shutdown. Stops the various jobs (nicely). + * + * @param cls NULL + */ +static void +do_shutdown (void *cls) +{ + (void) cls; + TES_listen_stop (); + if (NULL != keygen_task) + { + GNUNET_SCHEDULER_cancel (keygen_task); + keygen_task = NULL; + } +} + + +void +TALER_SECMOD_eddsa_run (void *cls, + char *const *args, + const char *cfgfile, + const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + static struct TES_Callbacks cb = { + .dispatch = eddsa_work_dispatch, + .updater = eddsa_update_client_keys, + .init = eddsa_client_init + }; + struct TALER_SECMOD_Options *opt = cls; + char *secname; + + (void) args; + (void) cfgfile; + globals = opt; + if (GNUNET_TIME_timestamp_cmp (opt->global_now, + !=, + opt->global_now_tmp)) + { + /* The user gave "--now", use it! */ + opt->global_now = opt->global_now_tmp; + } + else + { + /* get current time again, we may be timetraveling! */ + opt->global_now = GNUNET_TIME_timestamp_get (); + } + GNUNET_asprintf (&secname, + "%s-secmod-eddsa", + opt->section); + if (GNUNET_OK != + load_durations (cfg)) + { + opt->global_ret = EXIT_NOTCONFIGURED; + return; + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_filename (cfg, + secname, + "KEY_DIR", + &keydir)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + secname, + "KEY_DIR"); + GNUNET_free (secname); + opt->global_ret = EXIT_NOTCONFIGURED; + return; + } + GNUNET_SCHEDULER_add_shutdown (&do_shutdown, + NULL); + opt->global_ret = TES_listen_start (cfg, + secname, + &cb); + if (0 != opt->global_ret) + return; + /* Load keys */ + GNUNET_break (GNUNET_OK == + GNUNET_DISK_directory_create (keydir)); + GNUNET_DISK_directory_scan (keydir, + &import_key, + NULL); + if ( (NULL != keys_head) && + (GNUNET_TIME_absolute_is_future (keys_head->anchor.abs_time)) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Existing anchor is in %s the future. Refusing to start\n", + GNUNET_TIME_relative2s ( + GNUNET_TIME_absolute_get_remaining ( + keys_head->anchor.abs_time), + true)); + opt->global_ret = EXIT_FAILURE; + GNUNET_SCHEDULER_shutdown (); + return; + } + /* start job to keep keys up-to-date; MUST be run before the #listen_task, + hence with priority. */ + keygen_task = GNUNET_SCHEDULER_add_with_priority ( + GNUNET_SCHEDULER_PRIORITY_URGENT, + &update_keys, + NULL); +} |