exchange

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

taler-exchange-httpd_post-management-keys.c (17842B)


      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 Affero 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 Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file taler-exchange-httpd_post-management-keys.c
     18  * @brief Handle request to POST /management/keys
     19  * @author Christian Grothoff
     20  */
     21 #include <gnunet/gnunet_util_lib.h>
     22 #include <gnunet/gnunet_json_lib.h>
     23 #include <jansson.h>
     24 #include <microhttpd.h>
     25 #include <pthread.h>
     26 #include "taler/taler_json_lib.h"
     27 #include "taler/taler_mhd_lib.h"
     28 #include "taler-exchange-httpd_get-keys.h"
     29 #include "taler-exchange-httpd_secmod-helpers.h"
     30 #include "taler-exchange-httpd_management.h"
     31 #include "taler-exchange-httpd_responses.h"
     32 #include "exchange-database/get_signkey.h"
     33 #include "exchange-database/insert_signkey.h"
     34 #include "exchange-database/insert_denomination_info.h"
     35 #include "exchange-database/get_denomination_meta.h"
     36 
     37 
     38 /**
     39  * Denomination signature provided.
     40  */
     41 struct DenomSig
     42 {
     43   /**
     44    * Hash of a denomination public key.
     45    */
     46   struct TALER_DenominationHashP h_denom_pub;
     47 
     48   /**
     49    * Master signature for the @e h_denom_pub.
     50    */
     51   struct TALER_MasterSignatureP master_sig;
     52 
     53   /**
     54    * Fee structure for this key, as per our configuration.
     55    */
     56   struct TALER_EXCHANGEDB_DenominationKeyMetaData meta;
     57 
     58   /**
     59    * The full public key.
     60    */
     61   struct TALER_DenominationPublicKey denom_pub;
     62 
     63 };
     64 
     65 
     66 /**
     67  * Signkey signature provided.
     68  */
     69 struct SigningSig
     70 {
     71   /**
     72    * Online signing key of the exchange.
     73    */
     74   struct TALER_ExchangePublicKeyP exchange_pub;
     75 
     76   /**
     77    * Master signature for the @e exchange_pub.
     78    */
     79   struct TALER_MasterSignatureP master_sig;
     80 
     81   /**
     82    * Our meta data on this key.
     83    */
     84   struct TALER_EXCHANGEDB_SignkeyMetaData meta;
     85 
     86 };
     87 
     88 
     89 /**
     90  * Closure for the #add_keys transaction.
     91  */
     92 struct AddKeysContext
     93 {
     94 
     95   /**
     96    * Array of @e nd_sigs denomination signatures.
     97    */
     98   struct DenomSig *d_sigs;
     99 
    100   /**
    101    * Array of @e ns_sigs signkey signatures.
    102    */
    103   struct SigningSig *s_sigs;
    104 
    105   /**
    106    * Our key state.
    107    */
    108   struct TEH_KeyStateHandle *ksh;
    109 
    110   /**
    111    * Length of the d_sigs array.
    112    */
    113   unsigned int nd_sigs;
    114 
    115   /**
    116    * Length of the n_sigs array.
    117    */
    118   unsigned int ns_sigs;
    119 
    120 };
    121 
    122 
    123 /**
    124  * Compare meta-data of two denomination keys for equality,
    125  * except for the "serial" number.
    126  *
    127  * @param m1 meta data to compare to @a m2
    128  * @param m2 meta data to compare to @a m1
    129  * @return true if both are equal
    130  */
    131 static bool
    132 denomination_meta_cmp (
    133   const struct TALER_EXCHANGEDB_DenominationKeyMetaData *m1,
    134   const struct TALER_EXCHANGEDB_DenominationKeyMetaData *m2)
    135 {
    136   if ( (GNUNET_TIME_timestamp_cmp (m1->start,
    137                                    !=,
    138                                    m2->start)) ||
    139        (GNUNET_TIME_timestamp_cmp (m1->expire_withdraw,
    140                                    !=,
    141                                    m2->expire_withdraw)) ||
    142        (GNUNET_TIME_timestamp_cmp (m1->expire_deposit,
    143                                    !=,
    144                                    m2->expire_deposit)) ||
    145        (GNUNET_TIME_timestamp_cmp (m1->expire_legal,
    146                                    !=,
    147                                    m2->expire_legal)) )
    148     return false;
    149   if (0 !=
    150       TALER_amount_cmp (&m1->value,
    151                         &m2->value))
    152     return false;
    153   if (0 !=
    154       GNUNET_memcmp (&m1->fees,
    155                      &m2->fees))
    156     return false;
    157   if (m1->age_mask.bits !=
    158       m2->age_mask.bits)
    159     return false;
    160   return true;
    161 }
    162 
    163 
    164 /**
    165  * Compare meta-data of two signing keys for equality.
    166  *
    167  * @param m1 meta data to compare to @a m2
    168  * @param m2 meta data to compare to @a m1
    169  * @return true if both are equal
    170  */
    171 static bool
    172 signkey_meta_cmp (
    173   const struct TALER_EXCHANGEDB_SignkeyMetaData *m1,
    174   const struct TALER_EXCHANGEDB_SignkeyMetaData *m2)
    175 {
    176   if ( (GNUNET_TIME_timestamp_cmp (m1->start,
    177                                    !=,
    178                                    m2->start)) ||
    179        (GNUNET_TIME_timestamp_cmp (m1->expire_sign,
    180                                    !=,
    181                                    m2->expire_sign)) ||
    182        (GNUNET_TIME_timestamp_cmp (m1->expire_legal,
    183                                    !=,
    184                                    m2->expire_legal)) )
    185     return false;
    186   return true;
    187 }
    188 
    189 
    190 /**
    191  * Function implementing database transaction to add offline signing keys.
    192  * Runs the transaction logic; IF it returns a non-error code, the transaction
    193  * logic MUST NOT queue a MHD response.  IF it returns an hard error, the
    194  * transaction logic MUST queue a MHD response and set @a mhd_ret.  IF it
    195  * returns the soft error code, the function MAY be called again to retry and
    196  * MUST not queue a MHD response.
    197  *
    198  * @param cls closure with a `struct AddKeysContext`
    199  * @param connection MHD request which triggered the transaction
    200  * @param[out] mhd_ret set to MHD response status for @a connection,
    201  *             if transaction failed (!)
    202  * @return transaction status
    203  */
    204 static enum GNUNET_DB_QueryStatus
    205 add_keys (void *cls,
    206           struct MHD_Connection *connection,
    207           enum MHD_Result *mhd_ret)
    208 {
    209   struct AddKeysContext *akc = cls;
    210 
    211   /* activate all denomination keys */
    212   for (unsigned int i = 0; i<akc->nd_sigs; i++)
    213   {
    214     struct DenomSig *d = &akc->d_sigs[i];
    215     enum GNUNET_DB_QueryStatus qs;
    216     struct TALER_EXCHANGEDB_DenominationKeyMetaData meta;
    217 
    218     /* For idempotency, check if the key is already active */
    219     qs = TALER_EXCHANGEDB_get_denomination_meta (
    220       TEH_pg,
    221       &d->h_denom_pub,
    222       &meta);
    223     if (qs < 0)
    224     {
    225       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    226         return qs;
    227       GNUNET_break (0);
    228       *mhd_ret = TALER_MHD_reply_with_error (connection,
    229                                              MHD_HTTP_INTERNAL_SERVER_ERROR,
    230                                              TALER_EC_GENERIC_DB_FETCH_FAILED,
    231                                              "lookup denomination key");
    232       return qs;
    233     }
    234     if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
    235     {
    236       if (! denomination_meta_cmp (&d->meta,
    237                                    &meta))
    238       {
    239         GNUNET_break_op (0);
    240         *mhd_ret = TALER_MHD_reply_with_error (
    241           connection,
    242           MHD_HTTP_CONFLICT,
    243           TALER_EC_EXCHANGE_MANAGEMENT_CONFLICTING_DENOMINATION_META_DATA,
    244           "conflicting meta data previously set for the same denomination key");
    245         return GNUNET_DB_STATUS_HARD_ERROR;
    246       }
    247       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    248                   "Denomination key %s already active, skipping\n",
    249                   GNUNET_h2s (&d->h_denom_pub.hash));
    250       continue; /* skip, already known */
    251     }
    252 
    253     {
    254       struct TALER_EXCHANGEDB_DenominationKeyInformation issue = {
    255         .signature = d->master_sig,
    256         .start = d->meta.start,
    257         .expire_withdraw = d->meta.expire_withdraw,
    258         .expire_deposit = d->meta.expire_deposit,
    259         .expire_legal = d->meta.expire_legal,
    260         .value = d->meta.value,
    261         .fees = d->meta.fees,
    262         .denom_hash = d->h_denom_pub,
    263         .age_mask = d->meta.age_mask
    264       };
    265 
    266       qs = TALER_EXCHANGEDB_insert_denomination_info (
    267         TEH_pg,
    268         &d->denom_pub,
    269         &issue);
    270     }
    271     if (qs < 0)
    272     {
    273       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    274         return qs;
    275       GNUNET_break (0);
    276       *mhd_ret = TALER_MHD_reply_with_error (connection,
    277                                              MHD_HTTP_INTERNAL_SERVER_ERROR,
    278                                              TALER_EC_GENERIC_DB_STORE_FAILED,
    279                                              "activate denomination key");
    280       return qs;
    281     }
    282     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    283                 "Added offline signature for denomination `%s'\n",
    284                 GNUNET_h2s (&d->h_denom_pub.hash));
    285     GNUNET_assert (0 != qs);
    286   }
    287 
    288   for (unsigned int i = 0; i<akc->ns_sigs; i++)
    289   {
    290     struct SigningSig *s = &akc->s_sigs[i];
    291     enum GNUNET_DB_QueryStatus qs;
    292     struct TALER_EXCHANGEDB_SignkeyMetaData meta;
    293 
    294     qs = TALER_EXCHANGEDB_get_signkey (
    295       TEH_pg,
    296       &s->exchange_pub,
    297       &meta);
    298     if (qs < 0)
    299     {
    300       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    301         return qs;
    302       GNUNET_break (0);
    303       *mhd_ret = TALER_MHD_reply_with_error (connection,
    304                                              MHD_HTTP_INTERNAL_SERVER_ERROR,
    305                                              TALER_EC_GENERIC_DB_FETCH_FAILED,
    306                                              "lookup signing key");
    307       return qs;
    308     }
    309     if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
    310     {
    311       if (! signkey_meta_cmp (&s->meta,
    312                               &meta))
    313       {
    314         GNUNET_break_op (0);
    315         *mhd_ret = TALER_MHD_reply_with_error (
    316           connection,
    317           MHD_HTTP_CONFLICT,
    318           TALER_EC_EXCHANGE_MANAGEMENT_CONFLICTING_SIGNKEY_META_DATA,
    319           "conflicting meta data previously set for the same signing key");
    320         return GNUNET_DB_STATUS_HARD_ERROR;
    321       }
    322       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    323                   "Signing key %s already active, skipping\n",
    324                   TALER_B2S (&s->exchange_pub));
    325       continue;   /* skip, already known */
    326     }
    327     qs = TALER_EXCHANGEDB_insert_signkey (
    328       TEH_pg,
    329       &s->exchange_pub,
    330       &s->meta,
    331       &s->master_sig);
    332     if (qs < 0)
    333     {
    334       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    335         return qs;
    336       GNUNET_break (0);
    337       *mhd_ret = TALER_MHD_reply_with_error (connection,
    338                                              MHD_HTTP_INTERNAL_SERVER_ERROR,
    339                                              TALER_EC_GENERIC_DB_STORE_FAILED,
    340                                              "activate signing key");
    341       return qs;
    342     }
    343     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    344                 "Added offline signature for signing key `%s'\n",
    345                 TALER_B2S (&s->exchange_pub));
    346     GNUNET_assert (0 != qs);
    347   }
    348   return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; /* only 'success', so >=0, matters here */
    349 }
    350 
    351 
    352 /**
    353  * Clean up state in @a akc, but do not free @a akc itself
    354  *
    355  * @param[in,out] akc state to clean up
    356  */
    357 static void
    358 cleanup_akc (struct AddKeysContext *akc)
    359 {
    360   for (unsigned int i = 0; i<akc->nd_sigs; i++)
    361   {
    362     struct DenomSig *d = &akc->d_sigs[i];
    363 
    364     TALER_denom_pub_free (&d->denom_pub);
    365   }
    366   GNUNET_free (akc->d_sigs);
    367   GNUNET_free (akc->s_sigs);
    368 }
    369 
    370 
    371 enum MHD_Result
    372 TEH_handler_management_post_keys (
    373   struct MHD_Connection *connection,
    374   const json_t *root)
    375 {
    376   struct AddKeysContext akc = { 0 };
    377   const json_t *denom_sigs;
    378   const json_t *signkey_sigs;
    379   struct GNUNET_JSON_Specification spec[] = {
    380     GNUNET_JSON_spec_array_const ("denom_sigs",
    381                                   &denom_sigs),
    382     GNUNET_JSON_spec_array_const ("signkey_sigs",
    383                                   &signkey_sigs),
    384     GNUNET_JSON_spec_end ()
    385   };
    386   enum MHD_Result ret;
    387 
    388   {
    389     enum GNUNET_GenericReturnValue res;
    390 
    391     res = TALER_MHD_parse_json_data (connection,
    392                                      root,
    393                                      spec);
    394     if (GNUNET_SYSERR == res)
    395       return MHD_NO; /* hard failure */
    396     if (GNUNET_NO == res)
    397       return MHD_YES; /* failure */
    398   }
    399   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    400               "Received POST /management/keys request\n");
    401 
    402   akc.ksh = TEH_keys_get_state_for_management_only (); /* may start its own transaction, thus must be done here, before we run ours! */
    403   if (NULL == akc.ksh)
    404   {
    405     GNUNET_break_op (0);
    406     return TALER_MHD_reply_with_error (
    407       connection,
    408       MHD_HTTP_INTERNAL_SERVER_ERROR,
    409       TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
    410       "no key state (not even for management)");
    411   }
    412 
    413   akc.nd_sigs = json_array_size (denom_sigs);
    414   akc.d_sigs = GNUNET_new_array (akc.nd_sigs,
    415                                  struct DenomSig);
    416   for (unsigned int i = 0; i<akc.nd_sigs; i++)
    417   {
    418     struct DenomSig *d = &akc.d_sigs[i];
    419     struct GNUNET_JSON_Specification ispec[] = {
    420       GNUNET_JSON_spec_fixed_auto ("master_sig",
    421                                    &d->master_sig),
    422       GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
    423                                    &d->h_denom_pub),
    424       GNUNET_JSON_spec_end ()
    425     };
    426     enum GNUNET_GenericReturnValue res;
    427 
    428     res = TALER_MHD_parse_json_data (connection,
    429                                      json_array_get (denom_sigs,
    430                                                      i),
    431                                      ispec);
    432     if (GNUNET_OK != res)
    433     {
    434       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    435                   "Failure to handle /management/keys\n");
    436       cleanup_akc (&akc);
    437       return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
    438     }
    439 
    440     res = TEH_SECMOD_denom_load_meta (&d->h_denom_pub,
    441                                       &d->denom_pub,
    442                                       &d->meta);
    443     switch (res)
    444     {
    445     case GNUNET_SYSERR:
    446       ret = TALER_MHD_reply_with_error (
    447         connection,
    448         MHD_HTTP_INTERNAL_SERVER_ERROR,
    449         TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
    450         GNUNET_h2s (&d->h_denom_pub.hash));
    451       cleanup_akc (&akc);
    452       return ret;
    453     case GNUNET_NO:
    454       ret = TALER_MHD_reply_with_error (
    455         connection,
    456         MHD_HTTP_NOT_FOUND,
    457         TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN,
    458         GNUNET_h2s (&d->h_denom_pub.hash));
    459       cleanup_akc (&akc);
    460       return ret;
    461     case GNUNET_OK:
    462       break;
    463     }
    464     /* check signature is valid */
    465     TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
    466     if (GNUNET_OK !=
    467         TALER_exchange_offline_denom_validity_verify (
    468           &d->h_denom_pub,
    469           d->meta.start,
    470           d->meta.expire_withdraw,
    471           d->meta.expire_deposit,
    472           d->meta.expire_legal,
    473           &d->meta.value,
    474           &d->meta.fees,
    475           &TEH_master_public_key,
    476           &d->master_sig))
    477     {
    478       GNUNET_break_op (0);
    479       ret = TALER_MHD_reply_with_error (
    480         connection,
    481         MHD_HTTP_FORBIDDEN,
    482         TALER_EC_EXCHANGE_MANAGEMENT_KEYS_DENOMKEY_ADD_SIGNATURE_INVALID,
    483         GNUNET_h2s (&d->h_denom_pub.hash));
    484       cleanup_akc (&akc);
    485       return ret;
    486     }
    487   }
    488 
    489   akc.ns_sigs = json_array_size (signkey_sigs);
    490   akc.s_sigs = GNUNET_new_array (akc.ns_sigs,
    491                                  struct SigningSig);
    492   for (unsigned int i = 0; i<akc.ns_sigs; i++)
    493   {
    494     struct SigningSig *s = &akc.s_sigs[i];
    495     struct GNUNET_JSON_Specification ispec[] = {
    496       GNUNET_JSON_spec_fixed_auto ("master_sig",
    497                                    &s->master_sig),
    498       GNUNET_JSON_spec_fixed_auto ("exchange_pub",
    499                                    &s->exchange_pub),
    500       GNUNET_JSON_spec_end ()
    501     };
    502     enum GNUNET_GenericReturnValue res;
    503 
    504     res = TALER_MHD_parse_json_data (connection,
    505                                      json_array_get (signkey_sigs,
    506                                                      i),
    507                                      ispec);
    508     if (GNUNET_OK != res)
    509     {
    510       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    511                   "Failure to handle /management/keys\n");
    512       cleanup_akc (&akc);
    513       return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
    514     }
    515     res = TEH_SECMOD_esign_load_meta (&s->exchange_pub,
    516                                       &s->meta);
    517     switch (res)
    518     {
    519     case GNUNET_SYSERR:
    520       ret = TALER_MHD_reply_with_error (
    521         connection,
    522         MHD_HTTP_INTERNAL_SERVER_ERROR,
    523         TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
    524         TALER_B2S (&s->exchange_pub));
    525       cleanup_akc (&akc);
    526       return ret;
    527     case GNUNET_NO:
    528       /* For idempotency, check if the key is already active */
    529       ret = TALER_MHD_reply_with_error (
    530         connection,
    531         MHD_HTTP_NOT_FOUND,
    532         TALER_EC_EXCHANGE_MANAGEMENT_KEYS_SIGNKEY_UNKNOWN,
    533         TALER_B2S (&s->exchange_pub));
    534       cleanup_akc (&akc);
    535       return ret;
    536     case GNUNET_OK:
    537       break;
    538     }
    539 
    540     /* check signature is valid */
    541     TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
    542     if (GNUNET_OK !=
    543         TALER_exchange_offline_signkey_validity_verify (
    544           &s->exchange_pub,
    545           s->meta.start,
    546           s->meta.expire_sign,
    547           s->meta.expire_legal,
    548           &TEH_master_public_key,
    549           &s->master_sig))
    550     {
    551       GNUNET_break_op (0);
    552       ret = TALER_MHD_reply_with_error (
    553         connection,
    554         MHD_HTTP_FORBIDDEN,
    555         TALER_EC_EXCHANGE_MANAGEMENT_KEYS_SIGNKEY_ADD_SIGNATURE_INVALID,
    556         TALER_B2S (&s->exchange_pub));
    557       cleanup_akc (&akc);
    558       return ret;
    559     }
    560   }
    561   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    562               "Received %u denomination and %u signing key signatures\n",
    563               akc.nd_sigs,
    564               akc.ns_sigs);
    565   {
    566     enum GNUNET_GenericReturnValue res;
    567 
    568     res = TEH_DB_run_transaction (connection,
    569                                   "add keys",
    570                                   TEH_MT_REQUEST_OTHER,
    571                                   &ret,
    572                                   &add_keys,
    573                                   &akc);
    574     cleanup_akc (&akc);
    575     if (GNUNET_SYSERR == res)
    576       return ret;
    577   }
    578   TEH_keys_update_states ();
    579   return TALER_MHD_reply_static (
    580     connection,
    581     MHD_HTTP_NO_CONTENT,
    582     NULL,
    583     NULL,
    584     0);
    585 }
    586 
    587 
    588 /* end of taler-exchange-httpd_management_management_post_keys.c */