exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

taler-auditor-offline.c (39930B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2020-2023 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 taler-auditor-offline.c
     18  * @brief Support for operations involving the auditor's (offline) key.
     19  * @author Christian Grothoff
     20  */
     21 #include "taler/platform.h"
     22 #include <gnunet/gnunet_json_lib.h>
     23 #include <microhttpd.h>
     24 #include "taler/taler_json_lib.h"
     25 #include "taler/taler_exchange_service.h"
     26 
     27 /**
     28  * Name of the input of a denomination key signature for the 'upload' operation.
     29  * The "auditor-" prefix ensures that there is no ambiguity between
     30  * taler-exchange-offline and taler-auditor-offline JSON formats.
     31  * The last component --by convention-- identifies the protocol version
     32  * and should be incremented whenever the JSON format of the 'argument' changes.
     33  */
     34 #define OP_SIGN_DENOMINATION "auditor-sign-denomination-0"
     35 
     36 /**
     37  * Name of the input for the 'sign' and 'show' operations.
     38  * The "auditor-" prefix ensures that there is no ambiguity between
     39  * taler-exchange-offline and taler-auditor-offline JSON formats.
     40  * The last component --by convention-- identifies the protocol version
     41  * and should be incremented whenever the JSON format of the 'argument' changes.
     42  */
     43 #define OP_INPUT_KEYS "auditor-keys-0"
     44 
     45 /**
     46  * Show the offline signing key.
     47  * The last component --by convention-- identifies the protocol version
     48  * and should be incremented whenever the JSON format of the 'argument' changes.
     49  */
     50 #define OP_SETUP "auditor-setup-0"
     51 
     52 /**
     53  * Our private key, initialized in #load_offline_key().
     54  */
     55 static struct TALER_AuditorPrivateKeyP auditor_priv;
     56 
     57 /**
     58  * Our private key, initialized in #load_offline_key().
     59  */
     60 static struct TALER_AuditorPublicKeyP auditor_pub;
     61 
     62 /**
     63  * Base URL of this auditor's REST endpoint.
     64  */
     65 static char *auditor_url;
     66 
     67 /**
     68  * Exchange's master public key.
     69  */
     70 static struct TALER_MasterPublicKeyP master_pub;
     71 
     72 /**
     73  * Our context for making HTTP requests.
     74  */
     75 static struct GNUNET_CURL_Context *ctx;
     76 
     77 /**
     78  * Reschedule context for #ctx.
     79  */
     80 static struct GNUNET_CURL_RescheduleContext *rc;
     81 
     82 /**
     83  * Handle to the exchange's configuration
     84  */
     85 static const struct GNUNET_CONFIGURATION_Handle *kcfg;
     86 
     87 /**
     88  * Return value from main().
     89  */
     90 static int global_ret;
     91 
     92 /**
     93  * Input to consume.
     94  */
     95 static json_t *in;
     96 
     97 /**
     98  * Array of actions to perform.
     99  */
    100 static json_t *out;
    101 
    102 /**
    103  * Currency supported by this auditor.
    104  */
    105 static char *currency;
    106 
    107 
    108 /**
    109  * A subcommand supported by this program.
    110  */
    111 struct SubCommand
    112 {
    113   /**
    114    * Name of the command.
    115    */
    116   const char *name;
    117 
    118   /**
    119    * Help text for the command.
    120    */
    121   const char *help;
    122 
    123   /**
    124    * Function implementing the command.
    125    *
    126    * @param args subsequent command line arguments (char **)
    127    */
    128   void (*cb)(char *const *args);
    129 };
    130 
    131 
    132 /**
    133  * Data structure for wire add requests.
    134  */
    135 struct DenominationAddRequest
    136 {
    137 
    138   /**
    139    * Kept in a DLL.
    140    */
    141   struct DenominationAddRequest *next;
    142 
    143   /**
    144    * Kept in a DLL.
    145    */
    146   struct DenominationAddRequest *prev;
    147 
    148   /**
    149    * Operation handle.
    150    */
    151   struct TALER_EXCHANGE_AuditorAddDenominationHandle *h;
    152 
    153   /**
    154    * Array index of the associated command.
    155    */
    156   size_t idx;
    157 };
    158 
    159 
    160 /**
    161  * Next work item to perform.
    162  */
    163 static struct GNUNET_SCHEDULER_Task *nxt;
    164 
    165 /**
    166  * Active denomination add requests.
    167  */
    168 static struct DenominationAddRequest *dar_head;
    169 
    170 /**
    171  * Active denomination add requests.
    172  */
    173 static struct DenominationAddRequest *dar_tail;
    174 
    175 /**
    176  * Handle to the exchange, used to request /keys.
    177  */
    178 static struct TALER_EXCHANGE_GetKeysHandle *exchange;
    179 
    180 
    181 /**
    182  * Shutdown task. Invoked when the application is being terminated.
    183  *
    184  * @param cls NULL
    185  */
    186 static void
    187 do_shutdown (void *cls)
    188 {
    189   (void) cls;
    190 
    191   {
    192     struct DenominationAddRequest *dar;
    193 
    194     while (NULL != (dar = dar_head))
    195     {
    196       fprintf (stderr,
    197                "Aborting incomplete wire add #%u\n",
    198                (unsigned int) dar->idx);
    199       TALER_EXCHANGE_add_auditor_denomination_cancel (dar->h);
    200       GNUNET_CONTAINER_DLL_remove (dar_head,
    201                                    dar_tail,
    202                                    dar);
    203       GNUNET_free (dar);
    204     }
    205   }
    206   if (NULL != out)
    207   {
    208     json_dumpf (out,
    209                 stdout,
    210                 JSON_INDENT (2));
    211     json_decref (out);
    212     out = NULL;
    213   }
    214   if (NULL != in)
    215   {
    216     fprintf (stderr,
    217              "Darning: input not consumed!\n");
    218     json_decref (in);
    219     in = NULL;
    220   }
    221   if (NULL != exchange)
    222   {
    223     TALER_EXCHANGE_get_keys_cancel (exchange);
    224     exchange = NULL;
    225   }
    226   if (NULL != nxt)
    227   {
    228     GNUNET_SCHEDULER_cancel (nxt);
    229     nxt = NULL;
    230   }
    231   if (NULL != ctx)
    232   {
    233     GNUNET_CURL_fini (ctx);
    234     ctx = NULL;
    235   }
    236   if (NULL != rc)
    237   {
    238     GNUNET_CURL_gnunet_rc_destroy (rc);
    239     rc = NULL;
    240   }
    241 }
    242 
    243 
    244 /**
    245  * Test if we should shut down because all tasks are done.
    246  */
    247 static void
    248 test_shutdown (void)
    249 {
    250   if ( (NULL == dar_head) &&
    251        (NULL == exchange) &&
    252        (NULL == nxt) )
    253     GNUNET_SCHEDULER_shutdown ();
    254 }
    255 
    256 
    257 /**
    258  * Function to continue processing the next command.
    259  *
    260  * @param cls must be a `char *const*` with the array of
    261  *        command-line arguments to process next
    262  */
    263 static void
    264 work (void *cls);
    265 
    266 
    267 /**
    268  * Function to schedule job to process the next command.
    269  *
    270  * @param args the array of command-line arguments to process next
    271  */
    272 static void
    273 next (char *const *args)
    274 {
    275   GNUNET_assert (NULL == nxt);
    276   if (NULL == args[0])
    277   {
    278     test_shutdown ();
    279     return;
    280   }
    281   nxt = GNUNET_SCHEDULER_add_now (&work,
    282                                   (void *) args);
    283 }
    284 
    285 
    286 /**
    287  * Add an operation to the #out JSON array for processing later.
    288  *
    289  * @param op_name name of the operation
    290  * @param op_value values for the operation (consumed)
    291  */
    292 static void
    293 output_operation (const char *op_name,
    294                   json_t *op_value)
    295 {
    296   json_t *action;
    297 
    298   GNUNET_assert (NULL != out);
    299   action = GNUNET_JSON_PACK (
    300     GNUNET_JSON_pack_string ("operation",
    301                              op_name),
    302     GNUNET_JSON_pack_object_steal ("arguments",
    303                                    op_value));
    304   GNUNET_break (0 ==
    305                 json_array_append_new (out,
    306                                        action));
    307 }
    308 
    309 
    310 /**
    311  * Information about a subroutine for an upload.
    312  */
    313 struct UploadHandler
    314 {
    315   /**
    316    * Key to trigger this subroutine.
    317    */
    318   const char *key;
    319 
    320   /**
    321    * Function implementing an upload.
    322    *
    323    * @param exchange_url URL of the exchange
    324    * @param idx index of the operation we are performing
    325    * @param value arguments to drive the upload.
    326    */
    327   void (*cb)(const char *exchange_url,
    328              size_t idx,
    329              const json_t *value);
    330 
    331 };
    332 
    333 
    334 /**
    335  * Load the offline key (if not yet done). Triggers shutdown on failure.
    336  *
    337  * @param do_create #GNUNET_YES if the key may be created
    338  * @return #GNUNET_OK on success
    339  */
    340 static int
    341 load_offline_key (int do_create)
    342 {
    343   static bool done;
    344   int ret;
    345   char *fn;
    346 
    347   if (done)
    348     return GNUNET_OK;
    349   if (GNUNET_OK !=
    350       GNUNET_CONFIGURATION_get_value_filename (kcfg,
    351                                                "auditor",
    352                                                "AUDITOR_PRIV_FILE",
    353                                                &fn))
    354   {
    355     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    356                                "auditor",
    357                                "AUDITOR_PRIV_FILE");
    358     test_shutdown ();
    359     return GNUNET_SYSERR;
    360   }
    361   ret = GNUNET_CRYPTO_eddsa_key_from_file (fn,
    362                                            do_create,
    363                                            &auditor_priv.eddsa_priv);
    364   if (GNUNET_SYSERR == ret)
    365   {
    366     if (do_create)
    367       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    368                   "Failed to initialize auditor key at `%s': %s\n",
    369                   fn,
    370                   "could not create file");
    371     else
    372       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    373                   "Failed to load auditor key from file `%s': try running `taler-auditor-offline setup'?\n",
    374                   fn);
    375     GNUNET_free (fn);
    376     test_shutdown ();
    377     return GNUNET_SYSERR;
    378   }
    379   GNUNET_free (fn);
    380   GNUNET_CRYPTO_eddsa_key_get_public (&auditor_priv.eddsa_priv,
    381                                       &auditor_pub.eddsa_pub);
    382   done = true;
    383   return GNUNET_OK;
    384 }
    385 
    386 
    387 /**
    388  * Function called with information about the post denomination (signature)
    389  * add operation result.
    390  *
    391  * @param cls closure with a `struct DenominationAddRequest`
    392  * @param adr response data
    393  */
    394 static void
    395 denomination_add_cb (
    396   void *cls,
    397   const struct TALER_EXCHANGE_AuditorAddDenominationResponse *adr)
    398 {
    399   struct DenominationAddRequest *dar = cls;
    400   const struct TALER_EXCHANGE_HttpResponse *hr = &adr->hr;
    401 
    402   if (MHD_HTTP_NO_CONTENT != hr->http_status)
    403   {
    404     fprintf (stderr,
    405              "Upload failed for command #%u with status %u: %s (%s)\n",
    406              (unsigned int) dar->idx,
    407              hr->http_status,
    408              TALER_ErrorCode_get_hint (hr->ec),
    409              NULL != hr->hint
    410              ? hr->hint
    411              : "no hint provided");
    412     global_ret = EXIT_FAILURE;
    413   }
    414   GNUNET_CONTAINER_DLL_remove (dar_head,
    415                                dar_tail,
    416                                dar);
    417   GNUNET_free (dar);
    418   test_shutdown ();
    419 }
    420 
    421 
    422 /**
    423  * Upload denomination add data.
    424  *
    425  * @param exchange_url base URL of the exchange
    426  * @param idx index of the operation we are performing (for logging)
    427  * @param value arguments for denomination revocation
    428  */
    429 static void
    430 upload_denomination_add (const char *exchange_url,
    431                          size_t idx,
    432                          const json_t *value)
    433 {
    434   struct TALER_AuditorSignatureP auditor_sig;
    435   struct TALER_DenominationHashP h_denom_pub;
    436   struct DenominationAddRequest *dar;
    437   const char *err_name;
    438   unsigned int err_line;
    439   struct GNUNET_JSON_Specification spec[] = {
    440     GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
    441                                  &h_denom_pub),
    442     GNUNET_JSON_spec_fixed_auto ("auditor_sig",
    443                                  &auditor_sig),
    444     GNUNET_JSON_spec_end ()
    445   };
    446 
    447   if (GNUNET_OK !=
    448       GNUNET_JSON_parse (value,
    449                          spec,
    450                          &err_name,
    451                          &err_line))
    452   {
    453     fprintf (stderr,
    454              "Invalid input for adding denomination: %s#%u at %u (skipping)\n",
    455              err_name,
    456              err_line,
    457              (unsigned int) idx);
    458     global_ret = EXIT_FAILURE;
    459     test_shutdown ();
    460     return;
    461   }
    462   dar = GNUNET_new (struct DenominationAddRequest);
    463   dar->idx = idx;
    464   dar->h =
    465     TALER_EXCHANGE_add_auditor_denomination (ctx,
    466                                              exchange_url,
    467                                              &h_denom_pub,
    468                                              &auditor_pub,
    469                                              &auditor_sig,
    470                                              &denomination_add_cb,
    471                                              dar);
    472   GNUNET_CONTAINER_DLL_insert (dar_head,
    473                                dar_tail,
    474                                dar);
    475 }
    476 
    477 
    478 /**
    479  * Perform uploads based on the JSON in #out.
    480  *
    481  * @param exchange_url base URL of the exchange to use
    482  */
    483 static void
    484 trigger_upload (const char *exchange_url)
    485 {
    486   struct UploadHandler uhs[] = {
    487     {
    488       .key = OP_SIGN_DENOMINATION,
    489       .cb = &upload_denomination_add
    490     },
    491     /* array termination */
    492     {
    493       .key = NULL
    494     }
    495   };
    496   size_t index;
    497   json_t *obj;
    498 
    499   json_array_foreach (out, index, obj) {
    500     bool found = false;
    501     const char *key;
    502     const json_t *value;
    503 
    504     key = json_string_value (json_object_get (obj, "operation"));
    505     value = json_object_get (obj, "arguments");
    506     if (NULL == key)
    507     {
    508       fprintf (stderr,
    509                "Malformed JSON input\n");
    510       global_ret = EXIT_FAILURE;
    511       test_shutdown ();
    512       return;
    513     }
    514     /* block of code that uses key and value */
    515     for (unsigned int i = 0; NULL != uhs[i].key; i++)
    516     {
    517       if (0 == strcasecmp (key,
    518                            uhs[i].key))
    519       {
    520         found = true;
    521         uhs[i].cb (exchange_url,
    522                    index,
    523                    value);
    524         break;
    525       }
    526     }
    527     if (! found)
    528     {
    529       fprintf (stderr,
    530                "Upload does not know how to handle `%s'\n",
    531                key);
    532       global_ret = EXIT_FAILURE;
    533       test_shutdown ();
    534       return;
    535     }
    536   }
    537   /* test here, in case no upload was triggered (i.e. empty input) */
    538   test_shutdown ();
    539 }
    540 
    541 
    542 /**
    543  * Upload operation result (signatures) to exchange.
    544  *
    545  * @param args the array of command-line arguments to process next
    546  */
    547 static void
    548 do_upload (char *const *args)
    549 {
    550   char *exchange_url;
    551 
    552   (void) args;
    553   if (GNUNET_YES == GNUNET_is_zero (&auditor_pub))
    554   {
    555     /* private key not available, try configuration for public key */
    556     char *auditor_public_key_str;
    557 
    558     if (GNUNET_OK !=
    559         GNUNET_CONFIGURATION_get_value_string (kcfg,
    560                                                "auditor",
    561                                                "PUBLIC_KEY",
    562                                                &auditor_public_key_str))
    563     {
    564       GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    565                                  "auditor",
    566                                  "PUBLIC_KEY");
    567       global_ret = EXIT_NOTCONFIGURED;
    568       test_shutdown ();
    569       return;
    570     }
    571     if (GNUNET_OK !=
    572         GNUNET_CRYPTO_eddsa_public_key_from_string (
    573           auditor_public_key_str,
    574           strlen (auditor_public_key_str),
    575           &auditor_pub.eddsa_pub))
    576     {
    577       GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    578                                  "auditor",
    579                                  "PUBLIC_KEY",
    580                                  "invalid key");
    581       GNUNET_free (auditor_public_key_str);
    582       global_ret = EXIT_NOTCONFIGURED;
    583       test_shutdown ();
    584       return;
    585     }
    586     GNUNET_free (auditor_public_key_str);
    587   }
    588   if (NULL != in)
    589   {
    590     fprintf (stderr,
    591              "Downloaded data was not consumed, refusing upload\n");
    592     test_shutdown ();
    593     global_ret = EXIT_FAILURE;
    594     return;
    595   }
    596   if (NULL == out)
    597   {
    598     json_error_t err;
    599 
    600     out = json_loadf (stdin,
    601                       JSON_REJECT_DUPLICATES,
    602                       &err);
    603     if (NULL == out)
    604     {
    605       fprintf (stderr,
    606                "Failed to read JSON input: %s at %d:%s (offset: %d)\n",
    607                err.text,
    608                err.line,
    609                err.source,
    610                err.position);
    611       test_shutdown ();
    612       global_ret = EXIT_FAILURE;
    613       return;
    614     }
    615   }
    616   if (! json_is_array (out))
    617   {
    618     fprintf (stderr,
    619              "Error: expected JSON array for `upload` command\n");
    620     test_shutdown ();
    621     global_ret = EXIT_FAILURE;
    622     return;
    623   }
    624   if (GNUNET_OK !=
    625       GNUNET_CONFIGURATION_get_value_string (kcfg,
    626                                              "exchange",
    627                                              "BASE_URL",
    628                                              &exchange_url))
    629   {
    630     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    631                                "exchange",
    632                                "BASE_URL");
    633     global_ret = EXIT_NOTCONFIGURED;
    634     test_shutdown ();
    635     return;
    636   }
    637   trigger_upload (exchange_url);
    638   json_decref (out);
    639   out = NULL;
    640   GNUNET_free (exchange_url);
    641 }
    642 
    643 
    644 /**
    645  * Function called with information about who is auditing
    646  * a particular exchange and what keys the exchange is using.
    647  *
    648  * @param cls closure with the `char **` remaining args
    649  * @param kr response data
    650  * @param keys key data from the exchange
    651  */
    652 static void
    653 keys_cb (
    654   void *cls,
    655   const struct TALER_EXCHANGE_KeysResponse *kr,
    656   struct TALER_EXCHANGE_Keys *keys)
    657 {
    658   char *const *args = cls;
    659 
    660   exchange = NULL;
    661   switch (kr->hr.http_status)
    662   {
    663   case MHD_HTTP_OK:
    664     if (NULL == kr->hr.reply)
    665     {
    666       GNUNET_break (0);
    667       test_shutdown ();
    668       global_ret = EXIT_FAILURE;
    669       return;
    670     }
    671     break;
    672   default:
    673     fprintf (stderr,
    674              "Failed to download keys: %s (HTTP status: %u/%u)\n",
    675              kr->hr.hint,
    676              kr->hr.http_status,
    677              (unsigned int) kr->hr.ec);
    678     test_shutdown ();
    679     global_ret = EXIT_FAILURE;
    680     return;
    681   }
    682   in = GNUNET_JSON_PACK (
    683     GNUNET_JSON_pack_string ("operation",
    684                              OP_INPUT_KEYS),
    685     GNUNET_JSON_pack_object_incref ("arguments",
    686                                     (json_t *) kr->hr.reply));
    687   if (NULL == args[0])
    688   {
    689     json_dumpf (in,
    690                 stdout,
    691                 JSON_INDENT (2));
    692     json_decref (in);
    693     in = NULL;
    694   }
    695   next (args);
    696   TALER_EXCHANGE_keys_decref (keys);
    697 }
    698 
    699 
    700 /**
    701  * Download future keys.
    702  *
    703  * @param args the array of command-line arguments to process next
    704  */
    705 static void
    706 do_download (char *const *args)
    707 {
    708   char *exchange_url;
    709 
    710   if (GNUNET_OK !=
    711       GNUNET_CONFIGURATION_get_value_string (kcfg,
    712                                              "exchange",
    713                                              "BASE_URL",
    714                                              &exchange_url))
    715   {
    716     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    717                                "exchange",
    718                                "BASE_URL");
    719     test_shutdown ();
    720     global_ret = EXIT_NOTCONFIGURED;
    721     return;
    722   }
    723   exchange = TALER_EXCHANGE_get_keys (ctx,
    724                                       exchange_url,
    725                                       NULL,
    726                                       &keys_cb,
    727                                       (void *) args);
    728   GNUNET_free (exchange_url);
    729 }
    730 
    731 
    732 /**
    733  * Output @a denomkeys for human consumption.
    734  *
    735  * @param denomkeys keys to output
    736  * @return #GNUNET_OK on success
    737  */
    738 static enum GNUNET_GenericReturnValue
    739 show_denomkeys (const json_t *denomkeys)
    740 {
    741   size_t index;
    742   json_t *value;
    743 
    744   json_array_foreach (denomkeys, index, value) {
    745     struct TALER_DenominationGroup group;
    746     const json_t *denoms;
    747     const char *err_name;
    748     unsigned int err_line;
    749     struct GNUNET_JSON_Specification spec[] = {
    750       TALER_JSON_spec_denomination_group (NULL,
    751                                           currency,
    752                                           &group),
    753       GNUNET_JSON_spec_array_const ("denoms",
    754                                     &denoms),
    755       GNUNET_JSON_spec_end ()
    756     };
    757     size_t index2;
    758     json_t *value2;
    759 
    760     if (GNUNET_OK !=
    761         GNUNET_JSON_parse (value,
    762                            spec,
    763                            &err_name,
    764                            &err_line))
    765     {
    766       fprintf (stderr,
    767                "Invalid input for denomination key to 'show': %s#%u at %u (skipping)\n",
    768                err_name,
    769                err_line,
    770                (unsigned int) index);
    771       GNUNET_JSON_parse_free (spec);
    772       global_ret = EXIT_FAILURE;
    773       test_shutdown ();
    774       return GNUNET_SYSERR;
    775     }
    776     json_array_foreach (denoms, index2, value2) {
    777       struct GNUNET_TIME_Timestamp stamp_start;
    778       struct GNUNET_TIME_Timestamp stamp_expire_withdraw;
    779       struct GNUNET_TIME_Timestamp stamp_expire_deposit;
    780       struct GNUNET_TIME_Timestamp stamp_expire_legal;
    781       struct TALER_DenominationPublicKey denom_pub;
    782       struct TALER_MasterSignatureP master_sig;
    783       struct GNUNET_JSON_Specification ispec[] = {
    784         TALER_JSON_spec_denom_pub_cipher (NULL,
    785                                           group.cipher,
    786                                           &denom_pub),
    787         GNUNET_JSON_spec_timestamp ("stamp_start",
    788                                     &stamp_start),
    789         GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw",
    790                                     &stamp_expire_withdraw),
    791         GNUNET_JSON_spec_timestamp ("stamp_expire_deposit",
    792                                     &stamp_expire_deposit),
    793         GNUNET_JSON_spec_timestamp ("stamp_expire_legal",
    794                                     &stamp_expire_legal),
    795         GNUNET_JSON_spec_fixed_auto ("master_sig",
    796                                      &master_sig),
    797         GNUNET_JSON_spec_end ()
    798       };
    799       struct GNUNET_TIME_Relative duration;
    800       struct TALER_DenominationHashP h_denom_pub;
    801 
    802       if (GNUNET_OK !=
    803           GNUNET_JSON_parse (value2,
    804                              ispec,
    805                              &err_name,
    806                              &err_line))
    807       {
    808         fprintf (stderr,
    809                  "Invalid input for denomination key to 'show': %s#%u at %u/%u (skipping)\n",
    810                  err_name,
    811                  err_line,
    812                  (unsigned int) index,
    813                  (unsigned int) index2);
    814         GNUNET_JSON_parse_free (spec);
    815         global_ret = EXIT_FAILURE;
    816         test_shutdown ();
    817         return GNUNET_SYSERR;
    818       }
    819       duration = GNUNET_TIME_absolute_get_difference (
    820         stamp_start.abs_time,
    821         stamp_expire_withdraw.abs_time);
    822       TALER_denom_pub_hash (&denom_pub,
    823                             &h_denom_pub);
    824       if (GNUNET_OK !=
    825           TALER_exchange_offline_denom_validity_verify (
    826             &h_denom_pub,
    827             stamp_start,
    828             stamp_expire_withdraw,
    829             stamp_expire_deposit,
    830             stamp_expire_legal,
    831             &group.value,
    832             &group.fees,
    833             &master_pub,
    834             &master_sig))
    835       {
    836         fprintf (stderr,
    837                  "Invalid master signature for key %s (aborting)\n",
    838                  TALER_B2S (&h_denom_pub));
    839         global_ret = EXIT_FAILURE;
    840         GNUNET_JSON_parse_free (ispec);
    841         GNUNET_JSON_parse_free (spec);
    842         test_shutdown ();
    843         return GNUNET_SYSERR;
    844       }
    845 
    846       {
    847         char *withdraw_fee_s;
    848         char *deposit_fee_s;
    849         char *refresh_fee_s;
    850         char *refund_fee_s;
    851         char *deposit_s;
    852         char *legal_s;
    853 
    854         withdraw_fee_s = TALER_amount_to_string (&group.fees.withdraw);
    855         deposit_fee_s = TALER_amount_to_string (&group.fees.deposit);
    856         refresh_fee_s = TALER_amount_to_string (&group.fees.refresh);
    857         refund_fee_s = TALER_amount_to_string (&group.fees.refund);
    858         deposit_s = GNUNET_strdup (
    859           GNUNET_TIME_timestamp2s (stamp_expire_deposit));
    860         legal_s = GNUNET_strdup (
    861           GNUNET_TIME_timestamp2s (stamp_expire_legal));
    862 
    863         printf (
    864           "DENOMINATION-KEY %s of value %s starting at %s "
    865           "(used for: %s, deposit until: %s legal end: %s) with fees %s/%s/%s/%s\n",
    866           TALER_B2S (&h_denom_pub),
    867           TALER_amount2s (&group.value),
    868           GNUNET_TIME_timestamp2s (stamp_start),
    869           GNUNET_TIME_relative2s (duration,
    870                                   false),
    871           deposit_s,
    872           legal_s,
    873           withdraw_fee_s,
    874           deposit_fee_s,
    875           refresh_fee_s,
    876           refund_fee_s);
    877         GNUNET_free (withdraw_fee_s);
    878         GNUNET_free (deposit_fee_s);
    879         GNUNET_free (refresh_fee_s);
    880         GNUNET_free (refund_fee_s);
    881         GNUNET_free (deposit_s);
    882         GNUNET_free (legal_s);
    883       }
    884       GNUNET_JSON_parse_free (ispec);
    885     }
    886     GNUNET_JSON_parse_free (spec);
    887   }
    888   return GNUNET_OK;
    889 }
    890 
    891 
    892 /**
    893  * Parse the '/keys' input for operation called @a command_name.
    894  *
    895  * @param command_name name of the command, for logging errors
    896  * @return NULL if the input is malformed
    897  */
    898 static json_t *
    899 parse_keys (const char *command_name)
    900 {
    901   json_t *keys;
    902   const char *op_str;
    903   struct GNUNET_JSON_Specification spec[] = {
    904     GNUNET_JSON_spec_json ("arguments",
    905                            &keys),
    906     GNUNET_JSON_spec_string ("operation",
    907                              &op_str),
    908     GNUNET_JSON_spec_end ()
    909   };
    910   const char *err_name;
    911   unsigned int err_line;
    912 
    913   if (NULL == in)
    914   {
    915     json_error_t err;
    916 
    917     in = json_loadf (stdin,
    918                      JSON_REJECT_DUPLICATES,
    919                      &err);
    920     if (NULL == in)
    921     {
    922       fprintf (stderr,
    923                "Failed to read JSON input: %s at %d:%s (offset: %d)\n",
    924                err.text,
    925                err.line,
    926                err.source,
    927                err.position);
    928       global_ret = EXIT_FAILURE;
    929       test_shutdown ();
    930       return NULL;
    931     }
    932   }
    933   if (GNUNET_OK !=
    934       GNUNET_JSON_parse (in,
    935                          spec,
    936                          &err_name,
    937                          &err_line))
    938   {
    939     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    940                 "Invalid input to '%s': %s#%u (skipping)\n",
    941                 command_name,
    942                 err_name,
    943                 err_line);
    944     json_dumpf (in,
    945                 stderr,
    946                 JSON_INDENT (2));
    947     global_ret = EXIT_FAILURE;
    948     test_shutdown ();
    949     return NULL;
    950   }
    951   if (0 != strcmp (op_str,
    952                    OP_INPUT_KEYS))
    953   {
    954     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    955                 "Invalid input to '%s' : operation is `%s', expected `%s'\n",
    956                 command_name,
    957                 op_str,
    958                 OP_INPUT_KEYS);
    959     GNUNET_JSON_parse_free (spec);
    960     return NULL;
    961   }
    962   json_decref (in);
    963   in = NULL;
    964   return keys;
    965 }
    966 
    967 
    968 /**
    969  * Show exchange denomination keys.
    970  *
    971  * @param args the array of command-line arguments to process next
    972  */
    973 static void
    974 do_show (char *const *args)
    975 {
    976   json_t *keys;
    977   const char *err_name;
    978   unsigned int err_line;
    979   const json_t *denomkeys;
    980   struct TALER_MasterPublicKeyP mpub;
    981   struct GNUNET_JSON_Specification spec[] = {
    982     GNUNET_JSON_spec_array_const ("denominations",
    983                                   &denomkeys),
    984     GNUNET_JSON_spec_fixed_auto ("master_public_key",
    985                                  &mpub),
    986     GNUNET_JSON_spec_end ()
    987   };
    988 
    989   keys = parse_keys ("show");
    990   if (NULL == keys)
    991   {
    992     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    993                 "Showing failed: no valid input\n");
    994     return;
    995   }
    996   if (GNUNET_OK !=
    997       GNUNET_JSON_parse (keys,
    998                          spec,
    999                          &err_name,
   1000                          &err_line))
   1001   {
   1002     fprintf (stderr,
   1003              "Invalid input to 'show': %s#%u (skipping)\n",
   1004              err_name,
   1005              err_line);
   1006     global_ret = EXIT_FAILURE;
   1007     test_shutdown ();
   1008     json_decref (keys);
   1009     return;
   1010   }
   1011   if (0 !=
   1012       GNUNET_memcmp (&mpub,
   1013                      &master_pub))
   1014   {
   1015     fprintf (stderr,
   1016              "Exchange master public key does not match key we have configured (aborting)\n");
   1017     global_ret = EXIT_FAILURE;
   1018     test_shutdown ();
   1019     json_decref (keys);
   1020     return;
   1021   }
   1022   if (GNUNET_OK !=
   1023       show_denomkeys (denomkeys))
   1024   {
   1025     global_ret = EXIT_FAILURE;
   1026     test_shutdown ();
   1027     json_decref (keys);
   1028     return;
   1029   }
   1030   json_decref (keys);
   1031   /* do NOT consume input if next argument is '-' */
   1032   if ( (NULL != args[0]) &&
   1033        (0 == strcmp ("-",
   1034                      args[0])) )
   1035   {
   1036     next (args + 1);
   1037     return;
   1038   }
   1039   next (args);
   1040 }
   1041 
   1042 
   1043 /**
   1044  * Sign @a denomkeys with offline key.
   1045  *
   1046  * @param denomkeys keys to output
   1047  * @return #GNUNET_OK on success
   1048  */
   1049 static enum GNUNET_GenericReturnValue
   1050 sign_denomkeys (const json_t *denomkeys)
   1051 {
   1052   size_t group_idx;
   1053   json_t *value;
   1054 
   1055   json_array_foreach (denomkeys, group_idx, value) {
   1056     struct TALER_DenominationGroup group = { 0 };
   1057     const json_t *denom_keys_array;
   1058     const char *err_name;
   1059     unsigned int err_line;
   1060     struct GNUNET_JSON_Specification spec[] = {
   1061       TALER_JSON_spec_denomination_group (NULL,
   1062                                           currency,
   1063                                           &group),
   1064       GNUNET_JSON_spec_array_const ("denoms",
   1065                                     &denom_keys_array),
   1066       GNUNET_JSON_spec_end ()
   1067     };
   1068     size_t index;
   1069     json_t *denom_key_obj;
   1070 
   1071     if (GNUNET_OK !=
   1072         GNUNET_JSON_parse (value,
   1073                            spec,
   1074                            &err_name,
   1075                            &err_line))
   1076     {
   1077       fprintf (stderr,
   1078                "Invalid input for denomination key to 'sign': %s#%u at %u (skipping)\n",
   1079                err_name,
   1080                err_line,
   1081                (unsigned int) group_idx);
   1082       GNUNET_JSON_parse_free (spec);
   1083       global_ret = EXIT_FAILURE;
   1084       test_shutdown ();
   1085       return GNUNET_SYSERR;
   1086     }
   1087     json_array_foreach (denom_keys_array, index, denom_key_obj) {
   1088       struct GNUNET_TIME_Timestamp stamp_start;
   1089       struct GNUNET_TIME_Timestamp stamp_expire_withdraw;
   1090       struct GNUNET_TIME_Timestamp stamp_expire_deposit;
   1091       struct GNUNET_TIME_Timestamp stamp_expire_legal;
   1092       struct TALER_DenominationPublicKey denom_pub = {
   1093         .age_mask = group.age_mask
   1094       };
   1095       struct TALER_MasterSignatureP master_sig;
   1096       struct GNUNET_JSON_Specification ispec[] = {
   1097         TALER_JSON_spec_denom_pub_cipher (NULL,
   1098                                           group.cipher,
   1099                                           &denom_pub),
   1100         GNUNET_JSON_spec_timestamp ("stamp_start",
   1101                                     &stamp_start),
   1102         GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw",
   1103                                     &stamp_expire_withdraw),
   1104         GNUNET_JSON_spec_timestamp ("stamp_expire_deposit",
   1105                                     &stamp_expire_deposit),
   1106         GNUNET_JSON_spec_timestamp ("stamp_expire_legal",
   1107                                     &stamp_expire_legal),
   1108         GNUNET_JSON_spec_fixed_auto ("master_sig",
   1109                                      &master_sig),
   1110         GNUNET_JSON_spec_end ()
   1111       };
   1112       struct TALER_DenominationHashP h_denom_pub;
   1113 
   1114       if (GNUNET_OK !=
   1115           GNUNET_JSON_parse (denom_key_obj,
   1116                              ispec,
   1117                              &err_name,
   1118                              &err_line))
   1119       {
   1120         fprintf (stderr,
   1121                  "Invalid input for denomination key to 'show': %s#%u at %u/%u (skipping)\n",
   1122                  err_name,
   1123                  err_line,
   1124                  (unsigned int) group_idx,
   1125                  (unsigned int) index);
   1126         GNUNET_JSON_parse_free (spec);
   1127         global_ret = EXIT_FAILURE;
   1128         test_shutdown ();
   1129         return GNUNET_SYSERR;
   1130       }
   1131       TALER_denom_pub_hash (&denom_pub,
   1132                             &h_denom_pub);
   1133       if (GNUNET_OK !=
   1134           TALER_exchange_offline_denom_validity_verify (
   1135             &h_denom_pub,
   1136             stamp_start,
   1137             stamp_expire_withdraw,
   1138             stamp_expire_deposit,
   1139             stamp_expire_legal,
   1140             &group.value,
   1141             &group.fees,
   1142             &master_pub,
   1143             &master_sig))
   1144       {
   1145         fprintf (stderr,
   1146                  "Invalid master signature for key %s (aborting)\n",
   1147                  TALER_B2S (&h_denom_pub));
   1148         global_ret = EXIT_FAILURE;
   1149         test_shutdown ();
   1150         return GNUNET_SYSERR;
   1151       }
   1152 
   1153       {
   1154         struct TALER_AuditorSignatureP auditor_sig;
   1155 
   1156         TALER_auditor_denom_validity_sign (auditor_url,
   1157                                            &h_denom_pub,
   1158                                            &master_pub,
   1159                                            stamp_start,
   1160                                            stamp_expire_withdraw,
   1161                                            stamp_expire_deposit,
   1162                                            stamp_expire_legal,
   1163                                            &group.value,
   1164                                            &group.fees,
   1165                                            &auditor_priv,
   1166                                            &auditor_sig);
   1167         output_operation (OP_SIGN_DENOMINATION,
   1168                           GNUNET_JSON_PACK (
   1169                             GNUNET_JSON_pack_data_auto ("h_denom_pub",
   1170                                                         &h_denom_pub),
   1171                             GNUNET_JSON_pack_data_auto ("auditor_sig",
   1172                                                         &auditor_sig)));
   1173       }
   1174       GNUNET_JSON_parse_free (ispec);
   1175     }
   1176     GNUNET_JSON_parse_free (spec);
   1177   }
   1178   return GNUNET_OK;
   1179 }
   1180 
   1181 
   1182 /**
   1183  * Sign denomination keys.
   1184  *
   1185  * @param args the array of command-line arguments to process next
   1186  */
   1187 static void
   1188 do_sign (char *const *args)
   1189 {
   1190   json_t *keys;
   1191   const char *err_name;
   1192   unsigned int err_line;
   1193   struct TALER_MasterPublicKeyP mpub;
   1194   const json_t *denomkeys;
   1195   struct GNUNET_JSON_Specification spec[] = {
   1196     GNUNET_JSON_spec_array_const ("denominations",
   1197                                   &denomkeys),
   1198     GNUNET_JSON_spec_fixed_auto ("master_public_key",
   1199                                  &mpub),
   1200     GNUNET_JSON_spec_end ()
   1201   };
   1202 
   1203   keys = parse_keys ("sign");
   1204   if (NULL == keys)
   1205   {
   1206     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1207                 "Signing failed: no valid input\n");
   1208     return;
   1209   }
   1210   if (GNUNET_OK !=
   1211       load_offline_key (GNUNET_NO))
   1212   {
   1213     json_decref (keys);
   1214     return;
   1215   }
   1216   if (GNUNET_OK !=
   1217       GNUNET_JSON_parse (keys,
   1218                          spec,
   1219                          &err_name,
   1220                          &err_line))
   1221   {
   1222     fprintf (stderr,
   1223              "Invalid input to 'sign': %s#%u (skipping)\n",
   1224              err_name,
   1225              err_line);
   1226     global_ret = EXIT_FAILURE;
   1227     test_shutdown ();
   1228     json_decref (keys);
   1229     return;
   1230   }
   1231   if (0 !=
   1232       GNUNET_memcmp (&mpub,
   1233                      &master_pub))
   1234   {
   1235     fprintf (stderr,
   1236              "Exchange master public key does not match key we have configured (aborting)\n");
   1237     global_ret = EXIT_FAILURE;
   1238     test_shutdown ();
   1239     json_decref (keys);
   1240     return;
   1241   }
   1242   if (NULL == out)
   1243   {
   1244     out = json_array ();
   1245     GNUNET_assert (NULL != out);
   1246   }
   1247   if (GNUNET_OK !=
   1248       sign_denomkeys (denomkeys))
   1249   {
   1250     global_ret = EXIT_FAILURE;
   1251     test_shutdown ();
   1252     json_decref (keys);
   1253     return;
   1254   }
   1255   json_decref (keys);
   1256   next (args);
   1257 }
   1258 
   1259 
   1260 /**
   1261  * Setup and output offline signing key.
   1262  *
   1263  * @param args the array of command-line arguments to process next
   1264  */
   1265 static void
   1266 do_setup (char *const *args)
   1267 {
   1268   if (GNUNET_OK !=
   1269       load_offline_key (GNUNET_YES))
   1270   {
   1271     global_ret = EXIT_FAILURE;
   1272     return;
   1273   }
   1274   if (NULL != *args)
   1275   {
   1276     if (NULL == out)
   1277     {
   1278       out = json_array ();
   1279       GNUNET_assert (NULL != out);
   1280     }
   1281     output_operation (OP_SETUP,
   1282                       GNUNET_JSON_PACK (
   1283                         GNUNET_JSON_pack_data_auto ("auditor_pub",
   1284                                                     &auditor_pub)));
   1285   }
   1286 
   1287   else
   1288   {
   1289     char *pub_s;
   1290 
   1291     pub_s = GNUNET_STRINGS_data_to_string_alloc (&auditor_pub,
   1292                                                  sizeof (auditor_pub));
   1293     fprintf (stdout,
   1294              "%s\n",
   1295              pub_s);
   1296     GNUNET_free (pub_s);
   1297   }
   1298   if ( (NULL != *args) &&
   1299        (0 == strcmp (*args,
   1300                      "-")) )
   1301     args++;
   1302   next (args);
   1303 }
   1304 
   1305 
   1306 static void
   1307 work (void *cls)
   1308 {
   1309   char *const *args = cls;
   1310   struct SubCommand cmds[] = {
   1311     {
   1312       .name = "setup",
   1313       .help =
   1314         "setup auditor offline private key and show the public key",
   1315       .cb = &do_setup
   1316     },
   1317     {
   1318       .name = "download",
   1319       .help =
   1320         "obtain keys from exchange (to be performed online!)",
   1321       .cb = &do_download
   1322     },
   1323     {
   1324       .name = "show",
   1325       .help =
   1326         "display keys from exchange for human review (pass '-' as argument to disable consuming input)",
   1327       .cb = &do_show
   1328     },
   1329     {
   1330       .name = "sign",
   1331       .help =
   1332         "sing all denomination keys from the input",
   1333       .cb = &do_sign
   1334     },
   1335     {
   1336       .name = "upload",
   1337       .help =
   1338         "upload operation result to exchange (to be performed online!)",
   1339       .cb = &do_upload
   1340     },
   1341     /* list terminator */
   1342     {
   1343       .name = NULL,
   1344     }
   1345   };
   1346   (void) cls;
   1347 
   1348   nxt = NULL;
   1349   for (unsigned int i = 0; NULL != cmds[i].name; i++)
   1350   {
   1351     if (0 == strcasecmp (cmds[i].name,
   1352                          args[0]))
   1353     {
   1354       cmds[i].cb (&args[1]);
   1355       return;
   1356     }
   1357   }
   1358 
   1359   if (0 != strcasecmp ("help",
   1360                        args[0]))
   1361   {
   1362     fprintf (stderr,
   1363              "Unexpected command `%s'\n",
   1364              args[0]);
   1365     global_ret = EXIT_INVALIDARGUMENT;
   1366   }
   1367   fprintf (stderr,
   1368            "Supported subcommands:\n");
   1369   for (unsigned int i = 0; NULL != cmds[i].name; i++)
   1370   {
   1371     fprintf (stderr,
   1372              "\t%s - %s\n",
   1373              cmds[i].name,
   1374              cmds[i].help);
   1375   }
   1376 }
   1377 
   1378 
   1379 /**
   1380  * Main function that will be run.
   1381  *
   1382  * @param cls closure
   1383  * @param args remaining command-line arguments
   1384  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
   1385  * @param cfg configuration
   1386  */
   1387 static void
   1388 run (void *cls,
   1389      char *const *args,
   1390      const char *cfgfile,
   1391      const struct GNUNET_CONFIGURATION_Handle *cfg)
   1392 {
   1393   (void) cls;
   1394   (void) cfgfile;
   1395   kcfg = cfg;
   1396   if (GNUNET_OK !=
   1397       TALER_config_get_currency (kcfg,
   1398                                  "exchange",
   1399                                  &currency))
   1400   {
   1401     global_ret = EXIT_NOTCONFIGURED;
   1402     return;
   1403   }
   1404   if (GNUNET_OK !=
   1405       GNUNET_CONFIGURATION_get_value_string (kcfg,
   1406                                              "auditor",
   1407                                              "BASE_URL",
   1408                                              &auditor_url))
   1409   {
   1410     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
   1411                                "auditor",
   1412                                "BASE_URL");
   1413     global_ret = EXIT_NOTCONFIGURED;
   1414     return;
   1415   }
   1416   {
   1417     char *master_public_key_str;
   1418 
   1419     if (GNUNET_OK !=
   1420         GNUNET_CONFIGURATION_get_value_string (cfg,
   1421                                                "exchange",
   1422                                                "MASTER_PUBLIC_KEY",
   1423                                                &master_public_key_str))
   1424     {
   1425       GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
   1426                                  "exchange",
   1427                                  "MASTER_PUBLIC_KEY");
   1428       global_ret = EXIT_NOTCONFIGURED;
   1429       return;
   1430     }
   1431     if (GNUNET_OK !=
   1432         GNUNET_CRYPTO_eddsa_public_key_from_string (
   1433           master_public_key_str,
   1434           strlen (master_public_key_str),
   1435           &master_pub.eddsa_pub))
   1436     {
   1437       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1438                   "Invalid master public key given in exchange configuration.");
   1439       GNUNET_free (master_public_key_str);
   1440       global_ret = EXIT_NOTCONFIGURED;
   1441       return;
   1442     }
   1443     GNUNET_free (master_public_key_str);
   1444   }
   1445   ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
   1446                           &rc);
   1447   rc = GNUNET_CURL_gnunet_rc_create (ctx);
   1448   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
   1449                                  NULL);
   1450   next (args);
   1451 }
   1452 
   1453 
   1454 /**
   1455  * The main function of the taler-auditor-offline tool.  This tool is used to
   1456  * sign denomination keys with the auditor's key.  It uses the long-term
   1457  * offline private key of the auditor and generates signatures with it. It
   1458  * also supports online operations with the exchange to download its input
   1459  * data and to upload its results. Those online operations should be performed
   1460  * on another machine in production!
   1461  *
   1462  * @param argc number of arguments from the command line
   1463  * @param argv command line arguments
   1464  * @return 0 ok, 1 on error
   1465  */
   1466 int
   1467 main (int argc,
   1468       char *const *argv)
   1469 {
   1470   struct GNUNET_GETOPT_CommandLineOption options[] = {
   1471     GNUNET_GETOPT_OPTION_END
   1472   };
   1473   enum GNUNET_GenericReturnValue ret;
   1474 
   1475   ret = GNUNET_PROGRAM_run (
   1476     TALER_AUDITOR_project_data (),
   1477     argc, argv,
   1478     "taler-auditor-offline",
   1479     gettext_noop ("Operations for offline signing for a Taler exchange"),
   1480     options,
   1481     &run, NULL);
   1482   if (GNUNET_SYSERR == ret)
   1483     return EXIT_INVALIDARGUMENT;
   1484   if (GNUNET_NO == ret)
   1485     return EXIT_SUCCESS;
   1486   return global_ret;
   1487 }
   1488 
   1489 
   1490 /* end of taler-auditor-offline.c */