LCOV - code coverage report
Current view: top level - util - taler-exchange-secmod-eddsa.c (source / functions) Hit Total Coverage
Test: GNU Taler exchange coverage report Lines: 199 381 52.2 %
Date: 2022-08-25 06:15:09 Functions: 16 18 88.9 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   Copyright (C) 2014-2021 Taler Systems SA
       4             : 
       5             :   TALER is free software; you can redistribute it and/or modify it under the
       6             :   terms of the GNU General Public License as published by the Free Software
       7             :   Foundation; either version 3, or (at your option) any later version.
       8             : 
       9             :   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
      10             :   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
      11             :   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
      12             : 
      13             :   You should have received a copy of the GNU General Public License along with
      14             :   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
      15             : */
      16             : /**
      17             :  * @file util/taler-exchange-secmod-eddsa.c
      18             :  * @brief Standalone process to perform private key EDDSA operations
      19             :  * @author Christian Grothoff
      20             :  *
      21             :  * Key design points:
      22             :  * - EVERY thread of the exchange will have its own pair of connections to the
      23             :  *   crypto helpers.  This way, every threat will also have its own /keys state
      24             :  *   and avoid the need to synchronize on those.
      25             :  * - auditor signatures and master signatures are to be kept in the exchange DB,
      26             :  *   and merged with the public keys of the helper by the exchange HTTPD!
      27             :  * - the main loop of the helper is SINGLE-THREADED, but there are
      28             :  *   threads for crypto-workers which (only) do the signing in parallel,
      29             :  *   one per client.
      30             :  * - thread-safety: signing happens in parallel, thus when REMOVING private keys,
      31             :  *   we must ensure that all signers are done before we fully free() the
      32             :  *   private key. This is done by reference counting (as work is always
      33             :  *   assigned and collected by the main thread).
      34             :  */
      35             : #include "platform.h"
      36             : #include "taler_util.h"
      37             : #include "taler-exchange-secmod-eddsa.h"
      38             : #include <gcrypt.h>
      39             : #include <pthread.h>
      40             : #include "taler_error_codes.h"
      41             : #include "taler_signatures.h"
      42             : #include "secmod_common.h"
      43             : #include <poll.h>
      44             : 
      45             : 
      46             : /**
      47             :  * One particular key.
      48             :  */
      49             : struct Key
      50             : {
      51             : 
      52             :   /**
      53             :    * Kept in a DLL. Sorted by anchor time.
      54             :    */
      55             :   struct Key *next;
      56             : 
      57             :   /**
      58             :    * Kept in a DLL. Sorted by anchor time.
      59             :    */
      60             :   struct Key *prev;
      61             : 
      62             :   /**
      63             :    * Name of the file this key is stored under.
      64             :    */
      65             :   char *filename;
      66             : 
      67             :   /**
      68             :    * The private key.
      69             :    */
      70             :   struct TALER_ExchangePrivateKeyP exchange_priv;
      71             : 
      72             :   /**
      73             :    * The public key.
      74             :    */
      75             :   struct TALER_ExchangePublicKeyP exchange_pub;
      76             : 
      77             :   /**
      78             :    * Time at which this key is supposed to become valid.
      79             :    */
      80             :   struct GNUNET_TIME_Timestamp anchor;
      81             : 
      82             :   /**
      83             :    * Generation when this key was created or revoked.
      84             :    */
      85             :   uint64_t key_gen;
      86             : 
      87             :   /**
      88             :    * Reference counter. Counts the number of threads that are
      89             :    * using this key at this time.
      90             :    */
      91             :   unsigned int rc;
      92             : 
      93             :   /**
      94             :    * Flag set to true if this key has been purged and the memory
      95             :    * must be freed as soon as @e rc hits zero.
      96             :    */
      97             :   bool purge;
      98             : 
      99             : };
     100             : 
     101             : 
     102             : /**
     103             :  * Head of DLL of actual keys, sorted by anchor.
     104             :  */
     105             : static struct Key *keys_head;
     106             : 
     107             : /**
     108             :  * Tail of DLL of actual keys.
     109             :  */
     110             : static struct Key *keys_tail;
     111             : 
     112             : /**
     113             :  * How long can a key be used?
     114             :  */
     115             : static struct GNUNET_TIME_Relative duration;
     116             : 
     117             : /**
     118             :  * Return value from main().
     119             :  */
     120             : static int global_ret;
     121             : 
     122             : /**
     123             :  * Time when the key update is executed.
     124             :  * Either the actual current time, or a pretended time.
     125             :  */
     126             : static struct GNUNET_TIME_Timestamp now;
     127             : 
     128             : /**
     129             :  * The time for the key update, as passed by the user
     130             :  * on the command line.
     131             :  */
     132             : static struct GNUNET_TIME_Timestamp now_tmp;
     133             : 
     134             : /**
     135             :  * Where do we store the keys?
     136             :  */
     137             : static char *keydir;
     138             : 
     139             : /**
     140             :  * How much should coin creation duration overlap
     141             :  * with the next key?  Basically, the starting time of two
     142             :  * keys is always #duration - #overlap_duration apart.
     143             :  */
     144             : static struct GNUNET_TIME_Relative overlap_duration;
     145             : 
     146             : /**
     147             :  * How long into the future do we pre-generate keys?
     148             :  */
     149             : static struct GNUNET_TIME_Relative lookahead_sign;
     150             : 
     151             : /**
     152             :  * Task run to generate new keys.
     153             :  */
     154             : static struct GNUNET_SCHEDULER_Task *keygen_task;
     155             : 
     156             : /**
     157             :  * Lock for the keys queue.
     158             :  */
     159             : static pthread_mutex_t keys_lock;
     160             : 
     161             : /**
     162             :  * Current key generation.
     163             :  */
     164             : static uint64_t key_gen;
     165             : 
     166             : 
     167             : /**
     168             :  * Notify @a client about @a key becoming available.
     169             :  *
     170             :  * @param[in,out] client the client to notify; possible freed if transmission fails
     171             :  * @param key the key to notify @a client about
     172             :  * @return #GNUNET_OK on success
     173             :  */
     174             : static enum GNUNET_GenericReturnValue
     175          81 : notify_client_key_add (struct TES_Client *client,
     176             :                        const struct Key *key)
     177             : {
     178         162 :   struct TALER_CRYPTO_EddsaKeyAvailableNotification an = {
     179          81 :     .header.size = htons (sizeof (an)),
     180          81 :     .header.type = htons (TALER_HELPER_EDDSA_MT_AVAIL),
     181          81 :     .anchor_time = GNUNET_TIME_timestamp_hton (key->anchor),
     182          81 :     .duration = GNUNET_TIME_relative_hton (duration),
     183             :     .exchange_pub = key->exchange_pub,
     184             :     .secm_pub = TES_smpub
     185             :   };
     186             : 
     187          81 :   TALER_exchange_secmod_eddsa_sign (&key->exchange_pub,
     188             :                                     key->anchor,
     189             :                                     duration,
     190             :                                     &TES_smpriv,
     191             :                                     &an.secm_sig);
     192          81 :   if (GNUNET_OK !=
     193          81 :       TES_transmit (client->csock,
     194             :                     &an.header))
     195             :   {
     196           0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     197             :                 "Client %p must have disconnected\n",
     198             :                 client);
     199           0 :     return GNUNET_SYSERR;
     200             :   }
     201          81 :   return GNUNET_OK;
     202             : }
     203             : 
     204             : 
     205             : /**
     206             :  * Notify @a client about @a key being purged.
     207             :  *
     208             :  * @param[in,out] client the client to notify; possible freed if transmission fails
     209             :  * @param key the key to notify @a client about
     210             :  * @return #GNUNET_OK on success
     211             :  */
     212             : static enum GNUNET_GenericReturnValue
     213           3 : notify_client_key_del (struct TES_Client *client,
     214             :                        const struct Key *key)
     215             : {
     216           3 :   struct TALER_CRYPTO_EddsaKeyPurgeNotification pn = {
     217           3 :     .header.type = htons (TALER_HELPER_EDDSA_MT_PURGE),
     218           3 :     .header.size = htons (sizeof (pn)),
     219             :     .exchange_pub = key->exchange_pub
     220             :   };
     221             : 
     222           3 :   if (GNUNET_OK !=
     223           3 :       TES_transmit (client->csock,
     224             :                     &pn.header))
     225             :   {
     226           0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     227             :                 "Client %p must have disconnected\n",
     228             :                 client);
     229           0 :     return GNUNET_SYSERR;
     230             :   }
     231           3 :   return GNUNET_OK;
     232             : }
     233             : 
     234             : 
     235             : /**
     236             :  * Handle @a client request @a sr to create signature. Create the
     237             :  * signature using the respective key and return the result to
     238             :  * the client.
     239             :  *
     240             :  * @param client the client making the request
     241             :  * @param sr the request details
     242             :  * @return #GNUNET_OK on success
     243             :  */
     244             : static enum GNUNET_GenericReturnValue
     245         899 : handle_sign_request (struct TES_Client *client,
     246             :                      const struct TALER_CRYPTO_EddsaSignRequest *sr)
     247             : {
     248         899 :   const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose = &sr->purpose;
     249         899 :   size_t purpose_size = ntohs (sr->header.size) - sizeof (*sr)
     250             :                         + sizeof (*purpose);
     251             :   struct Key *key;
     252         899 :   struct TALER_CRYPTO_EddsaSignResponse sres = {
     253         899 :     .header.size = htons (sizeof (sres)),
     254         899 :     .header.type = htons (TALER_HELPER_EDDSA_MT_RES_SIGNATURE)
     255             :   };
     256             :   enum TALER_ErrorCode ec;
     257             : 
     258         899 :   if (purpose_size != htonl (purpose->size))
     259             :   {
     260           0 :     struct TALER_CRYPTO_EddsaSignFailure sf = {
     261           0 :       .header.size = htons (sizeof (sr)),
     262           0 :       .header.type = htons (TALER_HELPER_EDDSA_MT_RES_SIGN_FAILURE),
     263           0 :       .ec = htonl (TALER_EC_GENERIC_PARAMETER_MALFORMED)
     264             :     };
     265             : 
     266           0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     267             :                 "Signing request failed, request malformed\n");
     268           0 :     return TES_transmit (client->csock,
     269             :                          &sf.header);
     270             :   }
     271             : 
     272         899 :   GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
     273         903 :   key = keys_head;
     274        1806 :   while ( (NULL != key) &&
     275         903 :           (GNUNET_TIME_absolute_is_past (
     276             :              GNUNET_TIME_absolute_add (key->anchor.abs_time,
     277             :                                        duration))) )
     278             :   {
     279           0 :     struct Key *nxt = key->next;
     280             : 
     281           0 :     if (0 != key->rc)
     282           0 :       break; /* do later */
     283           0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     284             :                 "Deleting past key %s (expired %s ago)\n",
     285             :                 TALER_B2S (&nxt->exchange_pub),
     286             :                 GNUNET_TIME_relative2s (
     287             :                   GNUNET_TIME_absolute_get_duration (
     288             :                     GNUNET_TIME_absolute_add (key->anchor.abs_time,
     289             :                                               duration)),
     290             :                   GNUNET_YES));
     291           0 :     GNUNET_CONTAINER_DLL_remove (keys_head,
     292             :                                  keys_tail,
     293             :                                  key);
     294           0 :     if ( (! key->purge) &&
     295           0 :          (0 != unlink (key->filename)) )
     296           0 :       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
     297             :                                 "unlink",
     298             :                                 key->filename);
     299           0 :     GNUNET_free (key->filename);
     300           0 :     GNUNET_free (key);
     301           0 :     key = nxt;
     302             :   }
     303         903 :   if (NULL == key)
     304             :   {
     305           0 :     GNUNET_break (0);
     306           0 :     ec = TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING;
     307             :   }
     308             :   else
     309             :   {
     310         903 :     GNUNET_assert (key->rc < UINT_MAX);
     311         903 :     key->rc++;
     312         903 :     GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
     313             : 
     314         900 :     if (GNUNET_OK !=
     315         903 :         GNUNET_CRYPTO_eddsa_sign_ (&key->exchange_priv.eddsa_priv,
     316             :                                    purpose,
     317             :                                    &sres.exchange_sig.eddsa_signature))
     318           0 :       ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
     319             :     else
     320         900 :       ec = TALER_EC_NONE;
     321         900 :     sres.exchange_pub = key->exchange_pub;
     322         900 :     GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
     323         903 :     GNUNET_assert (key->rc > 0);
     324         903 :     key->rc--;
     325             :   }
     326         903 :   GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
     327         903 :   if (TALER_EC_NONE != ec)
     328             :   {
     329           0 :     struct TALER_CRYPTO_EddsaSignFailure sf = {
     330           0 :       .header.size = htons (sizeof (sf)),
     331           0 :       .header.type = htons (TALER_HELPER_EDDSA_MT_RES_SIGN_FAILURE),
     332           0 :       .ec = htonl ((uint32_t) ec)
     333             :     };
     334             : 
     335           0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     336             :                 "Signing request %p failed, worker failed to produce signature\n",
     337             :                 client);
     338           0 :     return TES_transmit (client->csock,
     339             :                          &sf.header);
     340             :   }
     341         903 :   return TES_transmit (client->csock,
     342             :                        &sres.header);
     343             : }
     344             : 
     345             : 
     346             : /**
     347             :  * Initialize key material for key @a key (also on disk).
     348             :  *
     349             :  * @param[in,out] key to compute key material for
     350             :  * @param position where in the DLL will the @a key go
     351             :  * @return #GNUNET_OK on success
     352             :  */
     353             : static enum GNUNET_GenericReturnValue
     354           9 : setup_key (struct Key *key,
     355             :            struct Key *position)
     356             : {
     357             :   struct GNUNET_CRYPTO_EddsaPrivateKey priv;
     358             :   struct GNUNET_CRYPTO_EddsaPublicKey pub;
     359             : 
     360           9 :   GNUNET_CRYPTO_eddsa_key_create (&priv);
     361           9 :   GNUNET_CRYPTO_eddsa_key_get_public (&priv,
     362             :                                       &pub);
     363           9 :   GNUNET_asprintf (&key->filename,
     364             :                    "%s/%llu",
     365             :                    keydir,
     366           9 :                    (unsigned long long) (key->anchor.abs_time.abs_value_us
     367           9 :                                          / GNUNET_TIME_UNIT_SECONDS.rel_value_us));
     368           9 :   if (GNUNET_OK !=
     369           9 :       GNUNET_DISK_fn_write (key->filename,
     370             :                             &priv,
     371             :                             sizeof (priv),
     372             :                             GNUNET_DISK_PERM_USER_READ))
     373             :   {
     374           0 :     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
     375             :                               "write",
     376             :                               key->filename);
     377           0 :     return GNUNET_SYSERR;
     378             :   }
     379           9 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     380             :               "Setup fresh private key in `%s'\n",
     381             :               key->filename);
     382           9 :   key->key_gen = key_gen;
     383           9 :   key->exchange_priv.eddsa_priv = priv;
     384           9 :   key->exchange_pub.eddsa_pub = pub;
     385           9 :   GNUNET_CONTAINER_DLL_insert_after (keys_head,
     386             :                                      keys_tail,
     387             :                                      position,
     388             :                                      key);
     389           9 :   return GNUNET_OK;
     390             : }
     391             : 
     392             : 
     393             : /**
     394             :  * The validity period of a key @a key has expired. Purge it.
     395             :  *
     396             :  * @param[in] key expired or revoked key to purge
     397             :  */
     398             : static void
     399           3 : purge_key (struct Key *key)
     400             : {
     401           3 :   if (key->purge)
     402             :   {
     403           0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     404             :                 "Key %s already purged, skipping\n",
     405             :                 TALER_B2S (&key->exchange_pub));
     406           0 :     return;
     407             :   }
     408           3 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     409             :               "Purging key %s\n",
     410             :               TALER_B2S (&key->exchange_pub));
     411           3 :   if (0 != unlink (key->filename))
     412           0 :     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
     413             :                               "unlink",
     414             :                               key->filename);
     415           3 :   key->purge = true;
     416           3 :   key->key_gen = key_gen;
     417           3 :   GNUNET_free (key->filename);
     418             : }
     419             : 
     420             : 
     421             : /**
     422             :  * A @a client informs us that a key has been revoked.
     423             :  * Check if the key is still in use, and if so replace (!)
     424             :  * it with a fresh key.
     425             :  *
     426             :  * @param client the client making the request
     427             :  * @param rr the revocation request
     428             :  * @return #GNUNET_OK on success
     429             :  */
     430             : static enum GNUNET_GenericReturnValue
     431           3 : handle_revoke_request (struct TES_Client *client,
     432             :                        const struct TALER_CRYPTO_EddsaRevokeRequest *rr)
     433             : {
     434             :   struct Key *key;
     435             :   struct Key *nkey;
     436             : 
     437             :   (void) client;
     438           3 :   key = NULL;
     439           3 :   GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
     440          13 :   for (struct Key *pos = keys_head;
     441             :        NULL != pos;
     442          10 :        pos = pos->next)
     443          13 :     if (0 == GNUNET_memcmp (&pos->exchange_pub,
     444             :                             &rr->exchange_pub))
     445             :     {
     446           3 :       key = pos;
     447           3 :       break;
     448             :     }
     449           3 :   if (NULL == key)
     450             :   {
     451           0 :     GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
     452           0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     453             :                 "Revocation request ignored, key unknown\n");
     454           0 :     return GNUNET_OK;
     455             :   }
     456           3 :   if (key->purge)
     457             :   {
     458           0 :     GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
     459           0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     460             :                 "Revocation request ignored, key %s already revoked\n",
     461             :                 TALER_B2S (&key->exchange_pub));
     462           0 :     return GNUNET_OK;
     463             :   }
     464           3 :   key_gen++;
     465           3 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     466             :               "Revoking key %s, bumping generation to %llu\n",
     467             :               TALER_B2S (&key->exchange_pub),
     468             :               (unsigned long long) key_gen);
     469           3 :   purge_key (key);
     470             : 
     471             :   /* Setup replacement key */
     472           3 :   nkey = GNUNET_new (struct Key);
     473           3 :   nkey->anchor = key->anchor;
     474           3 :   if (GNUNET_OK !=
     475           3 :       setup_key (nkey,
     476             :                  key))
     477             :   {
     478           0 :     GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
     479           0 :     GNUNET_break (0);
     480           0 :     GNUNET_SCHEDULER_shutdown ();
     481           0 :     global_ret = EXIT_FAILURE;
     482           0 :     return GNUNET_SYSERR;
     483             :   }
     484           3 :   GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
     485           3 :   TES_wake_clients ();
     486           3 :   return GNUNET_OK;
     487             : }
     488             : 
     489             : 
     490             : /**
     491             :  * Handle @a hdr message received from @a client.
     492             :  *
     493             :  * @param client the client that received the message
     494             :  * @param hdr message that was received
     495             :  * @return #GNUNET_OK on success
     496             :  */
     497             : static enum GNUNET_GenericReturnValue
     498         902 : eddsa_work_dispatch (struct TES_Client *client,
     499             :                      const struct GNUNET_MessageHeader *hdr)
     500             : {
     501         902 :   uint16_t msize = ntohs (hdr->size);
     502             : 
     503         902 :   switch (ntohs (hdr->type))
     504             :   {
     505         899 :   case TALER_HELPER_EDDSA_MT_REQ_SIGN:
     506         899 :     if (msize < sizeof (struct TALER_CRYPTO_EddsaSignRequest))
     507             :     {
     508           0 :       GNUNET_break_op (0);
     509           0 :       return GNUNET_SYSERR;
     510             :     }
     511         899 :     return handle_sign_request (
     512             :       client,
     513             :       (const struct TALER_CRYPTO_EddsaSignRequest *) hdr);
     514           3 :   case TALER_HELPER_EDDSA_MT_REQ_REVOKE:
     515           3 :     if (msize != sizeof (struct TALER_CRYPTO_EddsaRevokeRequest))
     516             :     {
     517           0 :       GNUNET_break_op (0);
     518           0 :       return GNUNET_SYSERR;
     519             :     }
     520           3 :     return handle_revoke_request (
     521             :       client,
     522             :       (const struct TALER_CRYPTO_EddsaRevokeRequest *) hdr);
     523           0 :   default:
     524           0 :     GNUNET_break_op (0);
     525           0 :     return GNUNET_SYSERR;
     526             :   }
     527             : }
     528             : 
     529             : 
     530             : /**
     531             :  * Send our initial key set to @a client together with the
     532             :  * "sync" terminator.
     533             :  *
     534             :  * @param client the client to inform
     535             :  * @return #GNUNET_OK on success
     536             :  */
     537             : static enum GNUNET_GenericReturnValue
     538           9 : eddsa_client_init (struct TES_Client *client)
     539             : {
     540           9 :   GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
     541          87 :   for (struct Key *key = keys_head;
     542             :        NULL != key;
     543          78 :        key = key->next)
     544             :   {
     545          78 :     if (GNUNET_OK !=
     546          78 :         notify_client_key_add (client,
     547             :                                key))
     548             :     {
     549           0 :       GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
     550           0 :       GNUNET_break (0);
     551           0 :       return GNUNET_SYSERR;
     552             :     }
     553             :   }
     554           9 :   client->key_gen = key_gen;
     555           9 :   GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
     556             :   {
     557           9 :     struct GNUNET_MessageHeader synced = {
     558           9 :       .type = htons (TALER_HELPER_EDDSA_SYNCED),
     559           9 :       .size = htons (sizeof (synced))
     560             :     };
     561             : 
     562           9 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     563             :                 "Client %p synced\n",
     564             :                 client);
     565           9 :     if (GNUNET_OK !=
     566           9 :         TES_transmit (client->csock,
     567             :                       &synced))
     568             :     {
     569           0 :       GNUNET_break (0);
     570           0 :       return GNUNET_SYSERR;
     571             :     }
     572             :   }
     573           9 :   return GNUNET_OK;
     574             : }
     575             : 
     576             : 
     577             : /**
     578             :  * Notify @a client about all changes to the keys since
     579             :  * the last generation known to the @a client.
     580             :  *
     581             :  * @param client the client to notify
     582             :  * @return #GNUNET_OK on success
     583             :  */
     584             : static enum GNUNET_GenericReturnValue
     585           3 : eddsa_update_client_keys (struct TES_Client *client)
     586             : {
     587           3 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     588             :               "Updating client %p to generation %llu\n",
     589             :               client,
     590             :               (unsigned long long) key_gen);
     591           3 :   GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
     592          27 :   for (struct Key *key = keys_head;
     593             :        NULL != key;
     594          24 :        key = key->next)
     595             :   {
     596          24 :     if (key->key_gen <= client->key_gen)
     597             :     {
     598          18 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     599             :                   "Skipping key %s, no change since generation %llu\n",
     600             :                   TALER_B2S (&key->exchange_pub),
     601             :                   (unsigned long long) client->key_gen);
     602          18 :       continue;
     603             :     }
     604           6 :     if (key->purge)
     605             :     {
     606           3 :       if (GNUNET_OK !=
     607           3 :           notify_client_key_del (client,
     608             :                                  key))
     609             :       {
     610           0 :         GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
     611           0 :         return GNUNET_SYSERR;
     612             :       }
     613             :     }
     614             :     else
     615             :     {
     616           3 :       if (GNUNET_OK !=
     617           3 :           notify_client_key_add (client,
     618             :                                  key))
     619             :       {
     620           0 :         GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
     621           0 :         return GNUNET_SYSERR;
     622             :       }
     623             :     }
     624             :   }
     625           3 :   client->key_gen = key_gen;
     626           3 :   GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
     627           3 :   return GNUNET_OK;
     628             : }
     629             : 
     630             : 
     631             : /**
     632             :  * Create a new key (we do not have enough).
     633             :  *
     634             :  * @return #GNUNET_OK on success
     635             :  */
     636             : static enum GNUNET_GenericReturnValue
     637           6 : create_key (void)
     638             : {
     639             :   struct Key *key;
     640             :   struct GNUNET_TIME_Timestamp anchor;
     641             : 
     642           6 :   anchor = GNUNET_TIME_timestamp_get ();
     643           6 :   if (NULL != keys_tail)
     644             :   {
     645             :     struct GNUNET_TIME_Absolute abs;
     646             : 
     647           5 :     abs = GNUNET_TIME_absolute_add (keys_tail->anchor.abs_time,
     648             :                                     GNUNET_TIME_relative_subtract (
     649             :                                       duration,
     650             :                                       overlap_duration));
     651           5 :     if (GNUNET_TIME_absolute_cmp (anchor.abs_time,
     652             :                                   <,
     653             :                                   abs))
     654           5 :       anchor = GNUNET_TIME_absolute_to_timestamp (abs);
     655             :   }
     656           6 :   key = GNUNET_new (struct Key);
     657           6 :   key->anchor = anchor;
     658           6 :   if (GNUNET_OK !=
     659           6 :       setup_key (key,
     660             :                  keys_tail))
     661             :   {
     662           0 :     GNUNET_break (0);
     663           0 :     GNUNET_free (key);
     664           0 :     GNUNET_SCHEDULER_shutdown ();
     665           0 :     global_ret = EXIT_FAILURE;
     666           0 :     return GNUNET_SYSERR;
     667             :   }
     668           6 :   return GNUNET_OK;
     669             : }
     670             : 
     671             : 
     672             : /**
     673             :  * At what time does the current key set require its next action?  Basically,
     674             :  * the minimum of the expiration time of the oldest key, and the expiration
     675             :  * time of the newest key minus the #lookahead_sign time.
     676             :  */
     677             : static struct GNUNET_TIME_Absolute
     678           1 : key_action_time (void)
     679             : {
     680             :   struct Key *nxt;
     681             : 
     682           1 :   nxt = keys_head;
     683           1 :   while ( (NULL != nxt) &&
     684           1 :           (nxt->purge) )
     685           0 :     nxt = nxt->next;
     686           1 :   if (NULL == nxt)
     687           0 :     return GNUNET_TIME_UNIT_ZERO_ABS;
     688           1 :   return GNUNET_TIME_absolute_min (
     689             :     GNUNET_TIME_absolute_add (nxt->anchor.abs_time,
     690             :                               duration),
     691             :     GNUNET_TIME_absolute_subtract (
     692             :       GNUNET_TIME_absolute_subtract (
     693           1 :         GNUNET_TIME_absolute_add (keys_tail->anchor.abs_time,
     694             :                                   duration),
     695             :         lookahead_sign),
     696             :       overlap_duration));
     697             : }
     698             : 
     699             : 
     700             : /**
     701             :  * Create new keys and expire ancient keys.
     702             :  *
     703             :  * @param cls NULL
     704             :  */
     705             : static void
     706           1 : update_keys (void *cls)
     707             : {
     708           1 :   bool wake = false;
     709             :   struct Key *nxt;
     710             : 
     711             :   (void) cls;
     712           1 :   keygen_task = NULL;
     713           1 :   GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
     714             :   /* create new keys */
     715          13 :   while ( (NULL == keys_tail) ||
     716           6 :           GNUNET_TIME_absolute_is_past (
     717             :             GNUNET_TIME_absolute_subtract (
     718             :               GNUNET_TIME_absolute_subtract (
     719           6 :                 GNUNET_TIME_absolute_add (keys_tail->anchor.abs_time,
     720             :                                           duration),
     721             :                 lookahead_sign),
     722             :               overlap_duration)) )
     723             :   {
     724           6 :     if (! wake)
     725             :     {
     726           1 :       key_gen++;
     727           1 :       wake = true;
     728             :     }
     729           6 :     if (GNUNET_OK !=
     730           6 :         create_key ())
     731             :     {
     732           0 :       GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
     733           0 :       GNUNET_break (0);
     734           0 :       global_ret = EXIT_FAILURE;
     735           0 :       GNUNET_SCHEDULER_shutdown ();
     736           0 :       return;
     737             :     }
     738             :   }
     739           1 :   nxt = keys_head;
     740             :   /* purge expired keys */
     741           2 :   while ( (NULL != nxt) &&
     742           1 :           GNUNET_TIME_absolute_is_past (
     743             :             GNUNET_TIME_absolute_add (nxt->anchor.abs_time,
     744             :                                       duration)))
     745             :   {
     746           0 :     if (! wake)
     747             :     {
     748           0 :       key_gen++;
     749           0 :       wake = true;
     750             :     }
     751           0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     752             :                 "Purging past key %s (expired %s ago)\n",
     753             :                 TALER_B2S (&nxt->exchange_pub),
     754             :                 GNUNET_TIME_relative2s (
     755             :                   GNUNET_TIME_absolute_get_duration (
     756             :                     GNUNET_TIME_absolute_add (nxt->anchor.abs_time,
     757             :                                               duration)),
     758             :                   GNUNET_YES));
     759           0 :     purge_key (nxt);
     760           0 :     nxt = nxt->next;
     761             :   }
     762           1 :   GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
     763           1 :   if (wake)
     764           1 :     TES_wake_clients ();
     765           1 :   keygen_task = GNUNET_SCHEDULER_add_at (key_action_time (),
     766             :                                          &update_keys,
     767             :                                          NULL);
     768             : }
     769             : 
     770             : 
     771             : /**
     772             :  * Parse private key from @a filename in @a buf.
     773             :  *
     774             :  * @param filename name of the file we are parsing, for logging
     775             :  * @param buf key material
     776             :  * @param buf_size number of bytes in @a buf
     777             :  * @return #GNUNET_OK on success
     778             :  */
     779             : static enum GNUNET_GenericReturnValue
     780           0 : parse_key (const char *filename,
     781             :            const void *buf,
     782             :            size_t buf_size)
     783             : {
     784             :   struct GNUNET_CRYPTO_EddsaPrivateKey priv;
     785             :   char *anchor_s;
     786             :   char dummy;
     787             :   unsigned long long anchor_ll;
     788             :   struct GNUNET_TIME_Timestamp anchor;
     789             : 
     790           0 :   anchor_s = strrchr (filename,
     791             :                       '/');
     792           0 :   if (NULL == anchor_s)
     793             :   {
     794             :     /* File in a directory without '/' in the name, this makes no sense. */
     795           0 :     GNUNET_break (0);
     796           0 :     return GNUNET_SYSERR;
     797             :   }
     798           0 :   anchor_s++;
     799           0 :   if (1 != sscanf (anchor_s,
     800             :                    "%llu%c",
     801             :                    &anchor_ll,
     802             :                    &dummy))
     803             :   {
     804             :     /* Filenames in KEYDIR must ONLY be the anchor time in seconds! */
     805           0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     806             :                 "Filename `%s' invalid for key file, skipping\n",
     807             :                 filename);
     808           0 :     return GNUNET_SYSERR;
     809             :   }
     810           0 :   anchor.abs_time.abs_value_us = anchor_ll
     811           0 :                                  * GNUNET_TIME_UNIT_SECONDS.rel_value_us;
     812           0 :   if (anchor_ll != anchor.abs_time.abs_value_us
     813           0 :       / GNUNET_TIME_UNIT_SECONDS.rel_value_us)
     814             :   {
     815             :     /* Integer overflow. Bad, invalid filename. */
     816           0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     817             :                 "Filename `%s' invalid for key file, skipping\n",
     818             :                 filename);
     819           0 :     return GNUNET_SYSERR;
     820             :   }
     821           0 :   if (buf_size != sizeof (priv))
     822             :   {
     823             :     /* Parser failure. */
     824           0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     825             :                 "File `%s' is malformed, skipping\n",
     826             :                 filename);
     827           0 :     return GNUNET_SYSERR;
     828             :   }
     829           0 :   memcpy (&priv,
     830             :           buf,
     831             :           buf_size);
     832             : 
     833             :   {
     834             :     struct GNUNET_CRYPTO_EddsaPublicKey pub;
     835             :     struct Key *key;
     836             :     struct Key *before;
     837             : 
     838           0 :     GNUNET_CRYPTO_eddsa_key_get_public (&priv,
     839             :                                         &pub);
     840           0 :     GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
     841           0 :     key = GNUNET_new (struct Key);
     842           0 :     key->exchange_priv.eddsa_priv = priv;
     843           0 :     key->exchange_pub.eddsa_pub = pub;
     844           0 :     key->anchor = anchor;
     845           0 :     key->filename = GNUNET_strdup (filename);
     846           0 :     key->key_gen = key_gen;
     847           0 :     before = NULL;
     848           0 :     for (struct Key *pos = keys_head;
     849             :          NULL != pos;
     850           0 :          pos = pos->next)
     851             :     {
     852           0 :       if (GNUNET_TIME_timestamp_cmp (pos->anchor, >, anchor))
     853           0 :         break;
     854           0 :       before = pos;
     855             :     }
     856           0 :     GNUNET_CONTAINER_DLL_insert_after (keys_head,
     857             :                                        keys_tail,
     858             :                                        before,
     859             :                                        key);
     860           0 :     GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
     861           0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     862             :                 "Imported key from `%s'\n",
     863             :                 filename);
     864             :   }
     865           0 :   return GNUNET_OK;
     866             : }
     867             : 
     868             : 
     869             : /**
     870             :  * Import a private key from @a filename.
     871             :  *
     872             :  * @param cls NULL
     873             :  * @param filename name of a file in the directory
     874             :  */
     875             : static enum GNUNET_GenericReturnValue
     876           0 : import_key (void *cls,
     877             :             const char *filename)
     878             : {
     879             :   struct GNUNET_DISK_FileHandle *fh;
     880             :   struct GNUNET_DISK_MapHandle *map;
     881             :   void *ptr;
     882             :   int fd;
     883             :   struct stat sbuf;
     884             : 
     885             :   (void) cls;
     886             :   {
     887             :     struct stat lsbuf;
     888             : 
     889           0 :     if (0 != lstat (filename,
     890             :                     &lsbuf))
     891             :     {
     892           0 :       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
     893             :                                 "lstat",
     894             :                                 filename);
     895           0 :       return GNUNET_OK;
     896             :     }
     897           0 :     if (! S_ISREG (lsbuf.st_mode))
     898             :     {
     899           0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     900             :                   "File `%s' is not a regular file, which is not allowed for private keys!\n",
     901             :                   filename);
     902           0 :       return GNUNET_OK;
     903             :     }
     904             :   }
     905             : 
     906           0 :   fd = open (filename,
     907             :              O_RDONLY | O_CLOEXEC);
     908           0 :   if (-1 == fd)
     909             :   {
     910           0 :     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
     911             :                               "open",
     912             :                               filename);
     913           0 :     return GNUNET_OK;
     914             :   }
     915           0 :   if (0 != fstat (fd,
     916             :                   &sbuf))
     917             :   {
     918           0 :     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
     919             :                               "stat",
     920             :                               filename);
     921           0 :     GNUNET_break (0 == close (fd));
     922           0 :     return GNUNET_OK;
     923             :   }
     924           0 :   if (! S_ISREG (sbuf.st_mode))
     925             :   {
     926           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     927             :                 "File `%s' is not a regular file, which is not allowed for private keys!\n",
     928             :                 filename);
     929           0 :     GNUNET_break (0 == close (fd));
     930           0 :     return GNUNET_OK;
     931             :   }
     932           0 :   if (0 != (sbuf.st_mode & (S_IWUSR | S_IRWXG | S_IRWXO)))
     933             :   {
     934             :     /* permission are NOT tight, try to patch them up! */
     935           0 :     if (0 !=
     936           0 :         fchmod (fd,
     937             :                 S_IRUSR))
     938             :     {
     939           0 :       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
     940             :                                 "fchmod",
     941             :                                 filename);
     942             :       /* refuse to use key if file has wrong permissions */
     943           0 :       GNUNET_break (0 == close (fd));
     944           0 :       return GNUNET_OK;
     945             :     }
     946             :   }
     947           0 :   fh = GNUNET_DISK_get_handle_from_int_fd (fd);
     948           0 :   if (NULL == fh)
     949             :   {
     950           0 :     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
     951             :                               "open",
     952             :                               filename);
     953           0 :     GNUNET_break (0 == close (fd));
     954           0 :     return GNUNET_OK;
     955             :   }
     956           0 :   if (sbuf.st_size > 2048)
     957             :   {
     958           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     959             :                 "File `%s' to big to be a private key\n",
     960             :                 filename);
     961           0 :     GNUNET_DISK_file_close (fh);
     962           0 :     return GNUNET_OK;
     963             :   }
     964           0 :   ptr = GNUNET_DISK_file_map (fh,
     965             :                               &map,
     966             :                               GNUNET_DISK_MAP_TYPE_READ,
     967           0 :                               (size_t) sbuf.st_size);
     968           0 :   if (NULL == ptr)
     969             :   {
     970           0 :     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
     971             :                               "mmap",
     972             :                               filename);
     973           0 :     GNUNET_DISK_file_close (fh);
     974           0 :     return GNUNET_OK;
     975             :   }
     976           0 :   (void) parse_key (filename,
     977             :                     ptr,
     978           0 :                     (size_t) sbuf.st_size);
     979           0 :   GNUNET_DISK_file_unmap (map);
     980           0 :   GNUNET_DISK_file_close (fh);
     981           0 :   return GNUNET_OK;
     982             : }
     983             : 
     984             : 
     985             : /**
     986             :  * Load the various duration values from @a kcfg.
     987             :  *
     988             :  * @param cfg configuration to use
     989             :  * @return #GNUNET_OK on success
     990             :  */
     991             : static enum GNUNET_GenericReturnValue
     992           1 : load_durations (const struct GNUNET_CONFIGURATION_Handle *cfg)
     993             : {
     994           1 :   if (GNUNET_OK !=
     995           1 :       GNUNET_CONFIGURATION_get_value_time (cfg,
     996             :                                            "taler-exchange-secmod-eddsa",
     997             :                                            "OVERLAP_DURATION",
     998             :                                            &overlap_duration))
     999             :   {
    1000           0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    1001             :                                "taler-exchange-secmod-eddsa",
    1002             :                                "OVERLAP_DURATION");
    1003           0 :     return GNUNET_SYSERR;
    1004             :   }
    1005           1 :   if (GNUNET_OK !=
    1006           1 :       GNUNET_CONFIGURATION_get_value_time (cfg,
    1007             :                                            "taler-exchange-secmod-eddsa",
    1008             :                                            "DURATION",
    1009             :                                            &duration))
    1010             :   {
    1011           0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    1012             :                                "taler-exchange-secmod-eddsa",
    1013             :                                "DURATION");
    1014           0 :     return GNUNET_SYSERR;
    1015             :   }
    1016           1 :   if (GNUNET_OK !=
    1017           1 :       GNUNET_CONFIGURATION_get_value_time (cfg,
    1018             :                                            "taler-exchange-secmod-eddsa",
    1019             :                                            "LOOKAHEAD_SIGN",
    1020             :                                            &lookahead_sign))
    1021             :   {
    1022           0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    1023             :                                "taler-exchange-secmod-eddsa",
    1024             :                                "LOOKAHEAD_SIGN");
    1025           0 :     return GNUNET_SYSERR;
    1026             :   }
    1027           1 :   return GNUNET_OK;
    1028             : }
    1029             : 
    1030             : 
    1031             : /**
    1032             :  * Function run on shutdown. Stops the various jobs (nicely).
    1033             :  *
    1034             :  * @param cls NULL
    1035             :  */
    1036             : static void
    1037           1 : do_shutdown (void *cls)
    1038             : {
    1039             :   (void) cls;
    1040           1 :   TES_listen_stop ();
    1041           1 :   if (NULL != keygen_task)
    1042             :   {
    1043           1 :     GNUNET_SCHEDULER_cancel (keygen_task);
    1044           1 :     keygen_task = NULL;
    1045             :   }
    1046           1 : }
    1047             : 
    1048             : 
    1049             : /**
    1050             :  * Main function that will be run under the GNUnet scheduler.
    1051             :  *
    1052             :  * @param cls closure
    1053             :  * @param args remaining command-line arguments
    1054             :  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
    1055             :  * @param cfg configuration
    1056             :  */
    1057             : static void
    1058           1 : run (void *cls,
    1059             :      char *const *args,
    1060             :      const char *cfgfile,
    1061             :      const struct GNUNET_CONFIGURATION_Handle *cfg)
    1062             : {
    1063             :   static struct TES_Callbacks cb = {
    1064             :     .dispatch = eddsa_work_dispatch,
    1065             :     .updater = eddsa_update_client_keys,
    1066             :     .init = eddsa_client_init
    1067             :   };
    1068             : 
    1069             :   (void) cls;
    1070             :   (void) args;
    1071             :   (void) cfgfile;
    1072           1 :   if (GNUNET_TIME_timestamp_cmp (now, !=, now_tmp))
    1073             :   {
    1074             :     /* The user gave "--now", use it! */
    1075           0 :     now = now_tmp;
    1076             :   }
    1077             :   else
    1078             :   {
    1079             :     /* get current time again, we may be timetraveling! */
    1080           1 :     now = GNUNET_TIME_timestamp_get ();
    1081             :   }
    1082           1 :   if (GNUNET_OK !=
    1083           1 :       load_durations (cfg))
    1084             :   {
    1085           0 :     global_ret = EXIT_NOTCONFIGURED;
    1086           0 :     return;
    1087             :   }
    1088           1 :   if (GNUNET_OK !=
    1089           1 :       GNUNET_CONFIGURATION_get_value_filename (cfg,
    1090             :                                                "taler-exchange-secmod-eddsa",
    1091             :                                                "KEY_DIR",
    1092             :                                                &keydir))
    1093             :   {
    1094           0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    1095             :                                "taler-exchange-secmod-eddsa",
    1096             :                                "KEY_DIR");
    1097           0 :     global_ret = EXIT_NOTCONFIGURED;
    1098           0 :     return;
    1099             :   }
    1100           1 :   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
    1101             :                                  NULL);
    1102           1 :   global_ret = TES_listen_start (cfg,
    1103             :                                  "taler-exchange-secmod-eddsa",
    1104             :                                  &cb);
    1105           1 :   if (0 != global_ret)
    1106           0 :     return;
    1107             :   /* Load keys */
    1108           1 :   GNUNET_break (GNUNET_OK ==
    1109             :                 GNUNET_DISK_directory_create (keydir));
    1110           1 :   GNUNET_DISK_directory_scan (keydir,
    1111             :                               &import_key,
    1112             :                               NULL);
    1113           1 :   if ( (NULL != keys_head) &&
    1114           0 :        (GNUNET_TIME_absolute_is_future (keys_head->anchor.abs_time)) )
    1115             :   {
    1116           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1117             :                 "Existing anchor is in %s the future. Refusing to start\n",
    1118             :                 GNUNET_TIME_relative2s (
    1119             :                   GNUNET_TIME_absolute_get_remaining (
    1120             :                     keys_head->anchor.abs_time),
    1121             :                   GNUNET_YES));
    1122           0 :     global_ret = EXIT_FAILURE;
    1123           0 :     GNUNET_SCHEDULER_shutdown ();
    1124           0 :     return;
    1125             :   }
    1126             :   /* start job to keep keys up-to-date; MUST be run before the #listen_task,
    1127             :      hence with priority. */
    1128           1 :   keygen_task = GNUNET_SCHEDULER_add_with_priority (
    1129             :     GNUNET_SCHEDULER_PRIORITY_URGENT,
    1130             :     &update_keys,
    1131             :     NULL);
    1132             : }
    1133             : 
    1134             : 
    1135             : /**
    1136             :  * The entry point.
    1137             :  *
    1138             :  * @param argc number of arguments in @a argv
    1139             :  * @param argv command-line arguments
    1140             :  * @return 0 on normal termination
    1141             :  */
    1142             : int
    1143           1 : main (int argc,
    1144             :       char **argv)
    1145             : {
    1146           1 :   struct GNUNET_GETOPT_CommandLineOption options[] = {
    1147           1 :     GNUNET_GETOPT_option_timetravel ('T',
    1148             :                                      "timetravel"),
    1149           1 :     GNUNET_GETOPT_option_timestamp ('t',
    1150             :                                     "time",
    1151             :                                     "TIMESTAMP",
    1152             :                                     "pretend it is a different time for the update",
    1153             :                                     &now_tmp),
    1154             :     GNUNET_GETOPT_OPTION_END
    1155             :   };
    1156             :   enum GNUNET_GenericReturnValue ret;
    1157             : 
    1158             :   /* Restrict permissions for the key files that we create. */
    1159           1 :   (void) umask (S_IWGRP | S_IROTH | S_IWOTH | S_IXOTH);
    1160             : 
    1161             :   /* force linker to link against libtalerutil; if we do
    1162             :    not do this, the linker may "optimize" libtalerutil
    1163             :    away and skip #TALER_OS_init(), which we do need */
    1164           1 :   TALER_OS_init ();
    1165           1 :   now_tmp = now = GNUNET_TIME_timestamp_get ();
    1166           1 :   ret = GNUNET_PROGRAM_run (argc,
    1167             :                             argv,
    1168             :                             "taler-exchange-secmod-eddsa",
    1169             :                             "Handle private EDDSA key operations for a Taler exchange",
    1170             :                             options,
    1171             :                             &run,
    1172             :                             NULL);
    1173           1 :   if (GNUNET_NO == ret)
    1174           0 :     return EXIT_SUCCESS;
    1175           1 :   if (GNUNET_SYSERR == ret)
    1176           0 :     return EXIT_INVALIDARGUMENT;
    1177           1 :   return global_ret;
    1178             : }

Generated by: LCOV version 1.14