exchange

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

testing_api_cmd_check_keys.c (18589B)


      1 /*
      2   This file is part of TALER
      3   (C) 2026 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify
      6   it under the terms of the GNU General Public License as
      7   published by the Free Software Foundation; either version 3, or
      8   (at your option) any later version.
      9 
     10   TALER is distributed in the hope that it will be useful, but
     11   WITHOUT ANY WARRANTY; without even the implied warranty of
     12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13   GNU General Public License for more details.
     14 
     15   You should have received a copy of the GNU General Public
     16   License along with TALER; see the file COPYING.  If not, see
     17   <http://www.gnu.org/licenses/>
     18 */
     19 /**
     20  * @file testing/testing_api_cmd_check_keys.c
     21  * @brief command to compare the /keys of successive "get exchange" commands
     22  * @author Christian Grothoff
     23  */
     24 #include "taler/taler_json_lib.h"
     25 #include <gnunet/gnunet_curl_lib.h>
     26 #include "taler/taler_testing_lib.h"
     27 
     28 
     29 /**
     30  * State for a "check keys" CMD.
     31  */
     32 struct CheckKeysState
     33 {
     34   /**
     35    * Label of the "get exchange" command with the earlier /keys.
     36    */
     37   const char *earlier_ref;
     38 
     39   /**
     40    * Label of the "get exchange" command with the later /keys.
     41    */
     42   const char *later_ref;
     43 
     44   /**
     45    * Label of a "get exchange" command that downloaded the full /keys
     46    * after @e later_ref, or NULL.
     47    */
     48   const char *full_ref;
     49 };
     50 
     51 
     52 /**
     53  * /keys of a "get exchange" command and when they were requested.
     54  */
     55 struct KeysSnapshot
     56 {
     57   /**
     58    * Label of the command.
     59    */
     60   const char *label;
     61 
     62   /**
     63    * The keys.
     64    */
     65   const struct TALER_EXCHANGE_Keys *keys;
     66 
     67   /**
     68    * When the request for @e keys was sent.
     69    */
     70   struct GNUNET_TIME_Absolute request_time;
     71 
     72   /**
     73    * When the reply with @e keys was received.
     74    */
     75   struct GNUNET_TIME_Absolute reply_time;
     76 };
     77 
     78 
     79 /**
     80  * Obtain the /keys of the "get exchange" command @a label.
     81  *
     82  * @param is interpreter state
     83  * @param label label of the command
     84  * @param[out] ks set to the keys of the command
     85  * @return #GNUNET_OK on success
     86  */
     87 static enum GNUNET_GenericReturnValue
     88 get_snapshot (struct TALER_TESTING_Interpreter *is,
     89               const char *label,
     90               struct KeysSnapshot *ks)
     91 {
     92   const struct TALER_TESTING_Command *cmd;
     93   struct TALER_EXCHANGE_Keys *keys;
     94   const struct GNUNET_TIME_Absolute *request_time;
     95   const struct GNUNET_TIME_Absolute *reply_time;
     96 
     97   cmd = TALER_TESTING_interpreter_lookup_command (is,
     98                                                   label);
     99   if (NULL == cmd)
    100   {
    101     GNUNET_break (0);
    102     return GNUNET_SYSERR;
    103   }
    104   if ( (GNUNET_OK !=
    105         TALER_TESTING_get_trait_keys (cmd,
    106                                       &keys)) ||
    107        (NULL == keys) ||
    108        (GNUNET_OK !=
    109         TALER_TESTING_get_trait_absolute_time (cmd,
    110                                                0,
    111                                                &request_time)) ||
    112        (GNUNET_OK !=
    113         TALER_TESTING_get_trait_absolute_time (cmd,
    114                                                1,
    115                                                &reply_time)) )
    116   {
    117     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    118                 "Command `%s' did not download /keys\n",
    119                 label);
    120     return GNUNET_SYSERR;
    121   }
    122   ks->label = label;
    123   ks->keys = keys;
    124   ks->request_time = *request_time;
    125   ks->reply_time = *reply_time;
    126   return GNUNET_OK;
    127 }
    128 
    129 
    130 /**
    131  * Find the denomination @a dk in @a keys.
    132  *
    133  * @param keys keys to search
    134  * @param dk denomination to find (by its hash)
    135  * @return NULL if @a keys does not contain @a dk
    136  */
    137 static const struct TALER_EXCHANGE_DenomPublicKey *
    138 find_denom (const struct TALER_EXCHANGE_Keys *keys,
    139             const struct TALER_EXCHANGE_DenomPublicKey *dk)
    140 {
    141   for (unsigned int i = 0; i<keys->num_denom_keys; i++)
    142     if (0 ==
    143         GNUNET_memcmp (&dk->h_key,
    144                        &keys->denom_keys[i].h_key))
    145       return &keys->denom_keys[i];
    146   return NULL;
    147 }
    148 
    149 
    150 /**
    151  * Check if @a a and @a b are the same amount.
    152  *
    153  * @param a an amount
    154  * @param b another amount
    155  * @return true if they are equal
    156  */
    157 static bool
    158 amount_equal (const struct TALER_Amount *a,
    159               const struct TALER_Amount *b)
    160 {
    161   return (GNUNET_YES ==
    162           TALER_amount_cmp_currency (a,
    163                                      b)) &&
    164          (0 == TALER_amount_cmp (a,
    165                                  b));
    166 }
    167 
    168 
    169 /**
    170  * Check if @a a and @a b describe the same denomination with the
    171  * same meta data.
    172  *
    173  * @param a a denomination
    174  * @param b another denomination
    175  * @return true if they are the same
    176  */
    177 static bool
    178 denom_equal (const struct TALER_EXCHANGE_DenomPublicKey *a,
    179              const struct TALER_EXCHANGE_DenomPublicKey *b)
    180 {
    181   return (0 == GNUNET_memcmp (&a->h_key,
    182                               &b->h_key)) &&
    183          (0 == TALER_denom_pub_cmp (&a->key,
    184                                     &b->key)) &&
    185          (0 == GNUNET_memcmp (&a->master_sig,
    186                               &b->master_sig)) &&
    187          (GNUNET_TIME_timestamp_cmp (a->valid_from,
    188                                      ==,
    189                                      b->valid_from)) &&
    190          (GNUNET_TIME_timestamp_cmp (a->withdraw_valid_until,
    191                                      ==,
    192                                      b->withdraw_valid_until)) &&
    193          (GNUNET_TIME_timestamp_cmp (a->expire_deposit,
    194                                      ==,
    195                                      b->expire_deposit)) &&
    196          (GNUNET_TIME_timestamp_cmp (a->expire_legal,
    197                                      ==,
    198                                      b->expire_legal)) &&
    199          amount_equal (&a->value,
    200                        &b->value) &&
    201          amount_equal (&a->fees.withdraw,
    202                        &b->fees.withdraw) &&
    203          amount_equal (&a->fees.deposit,
    204                        &b->fees.deposit) &&
    205          amount_equal (&a->fees.refresh,
    206                        &b->fees.refresh) &&
    207          amount_equal (&a->fees.refund,
    208                        &b->fees.refund) &&
    209          (a->revoked == b->revoked);
    210 }
    211 
    212 
    213 /**
    214  * Check that every denomination of @a a that is still valid after
    215  * @a valid_after is also in @a b, with the same meta data.
    216  *
    217  * @param a keys with the denominations to look for
    218  * @param b keys to search
    219  * @param valid_after denominations of @a a that expire before this
    220  *        time are not checked
    221  * @return true if all of them were found
    222  */
    223 static bool
    224 denoms_included (const struct KeysSnapshot *a,
    225                  const struct KeysSnapshot *b,
    226                  struct GNUNET_TIME_Absolute valid_after)
    227 {
    228   bool ok = true;
    229 
    230   for (unsigned int i = 0; i<a->keys->num_denom_keys; i++)
    231   {
    232     const struct TALER_EXCHANGE_DenomPublicKey *dk
    233       = &a->keys->denom_keys[i];
    234     const struct TALER_EXCHANGE_DenomPublicKey *dkb;
    235 
    236     if (GNUNET_TIME_absolute_cmp (dk->expire_deposit.abs_time,
    237                                   <=,
    238                                   valid_after))
    239       continue;
    240     dkb = find_denom (b->keys,
    241                       dk);
    242     if (NULL == dkb)
    243     {
    244       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    245                   "Denomination %s (valid until %s) of `%s' missing in `%s'\n",
    246                   GNUNET_h2s (&dk->h_key.hash),
    247                   GNUNET_TIME_timestamp2s (dk->expire_deposit),
    248                   a->label,
    249                   b->label);
    250       ok = false;
    251       continue;
    252     }
    253     if (! denom_equal (dk,
    254                        dkb))
    255     {
    256       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    257                   "Denomination %s differs between `%s' and `%s'\n",
    258                   GNUNET_h2s (&dk->h_key.hash),
    259                   a->label,
    260                   b->label);
    261       ok = false;
    262     }
    263   }
    264   return ok;
    265 }
    266 
    267 
    268 /**
    269  * Check that @a ks lists no denomination that had expired
    270  * when its /keys were requested.
    271  *
    272  * @param ks keys to check
    273  * @return true if no expired denomination is listed
    274  */
    275 static bool
    276 no_expired_denoms (const struct KeysSnapshot *ks)
    277 {
    278   bool ok = true;
    279 
    280   for (unsigned int i = 0; i<ks->keys->num_denom_keys; i++)
    281   {
    282     const struct TALER_EXCHANGE_DenomPublicKey *dk
    283       = &ks->keys->denom_keys[i];
    284 
    285     if (GNUNET_TIME_absolute_cmp (dk->expire_deposit.abs_time,
    286                                   <,
    287                                   ks->request_time))
    288     {
    289       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    290                   "`%s' lists denomination %s that expired at %s\n",
    291                   ks->label,
    292                   GNUNET_h2s (&dk->h_key.hash),
    293                   GNUNET_TIME_timestamp2s (dk->expire_deposit));
    294       ok = false;
    295     }
    296   }
    297   return ok;
    298 }
    299 
    300 
    301 /**
    302  * Check that @a ks lists each denomination only once.
    303  *
    304  * @param ks keys to check
    305  * @return true if there are no duplicates
    306  */
    307 static bool
    308 no_duplicate_denoms (const struct KeysSnapshot *ks)
    309 {
    310   for (unsigned int i = 0; i<ks->keys->num_denom_keys; i++)
    311     for (unsigned int j = i + 1; j<ks->keys->num_denom_keys; j++)
    312       if (0 ==
    313           GNUNET_memcmp (&ks->keys->denom_keys[i].h_key,
    314                          &ks->keys->denom_keys[j].h_key))
    315       {
    316         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    317                     "`%s' lists denomination %s twice\n",
    318                     ks->label,
    319                     GNUNET_h2s (&ks->keys->denom_keys[i].h_key.hash));
    320         return false;
    321       }
    322   return true;
    323 }
    324 
    325 
    326 /**
    327  * Check that @a later is a valid successor of @a earlier: same master
    328  * public key and currency, all denominations of @a earlier that are
    329  * still valid are kept, none that expired, and none twice.
    330  *
    331  * @param earlier earlier /keys
    332  * @param later later /keys
    333  * @return true if @a later is a valid successor of @a earlier
    334  */
    335 static bool
    336 check_successor (const struct KeysSnapshot *earlier,
    337                  const struct KeysSnapshot *later)
    338 {
    339   bool ok = true;
    340 
    341   if (0 !=
    342       GNUNET_memcmp (&earlier->keys->master_pub,
    343                      &later->keys->master_pub))
    344   {
    345     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    346                 "Master public key changed between `%s' and `%s'\n",
    347                 earlier->label,
    348                 later->label);
    349     ok = false;
    350   }
    351   if (0 != strcmp (earlier->keys->currency,
    352                    later->keys->currency))
    353   {
    354     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    355                 "Currency changed between `%s' and `%s'\n",
    356                 earlier->label,
    357                 later->label);
    358     ok = false;
    359   }
    360   /* Denominations that were still valid when the later reply was
    361      received must have been kept. */
    362   if (! denoms_included (earlier,
    363                          later,
    364                          later->reply_time))
    365     ok = false;
    366   if (! no_expired_denoms (later))
    367     ok = false;
    368   if (! no_duplicate_denoms (later))
    369     ok = false;
    370   return ok;
    371 }
    372 
    373 
    374 /**
    375  * Check that the auditors of @a a are also auditors in @a b, auditing
    376  * at least the same denominations, ignoring denominations that expire
    377  * before @a valid_after.
    378  *
    379  * @param a keys with the auditors to look for
    380  * @param b keys to search
    381  * @param valid_after ignore denominations that expire before this time
    382  * @return true if all of them were found
    383  */
    384 static bool
    385 auditors_included (const struct KeysSnapshot *a,
    386                    const struct KeysSnapshot *b,
    387                    struct GNUNET_TIME_Absolute valid_after)
    388 {
    389   for (unsigned int i = 0; i<a->keys->num_auditors; i++)
    390   {
    391     const struct TALER_EXCHANGE_AuditorInformation *ai
    392       = &a->keys->auditors[i];
    393     const struct TALER_EXCHANGE_AuditorInformation *bi = NULL;
    394 
    395     for (unsigned int j = 0; j<b->keys->num_auditors; j++)
    396       if ( (0 ==
    397             GNUNET_memcmp (&ai->auditor_pub,
    398                            &b->keys->auditors[j].auditor_pub)) &&
    399            (0 == strcmp (ai->auditor_url,
    400                          b->keys->auditors[j].auditor_url)) )
    401         bi = &b->keys->auditors[j];
    402     if (NULL == bi)
    403     {
    404       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    405                   "Auditor %s of `%s' missing in `%s'\n",
    406                   ai->auditor_url,
    407                   a->label,
    408                   b->label);
    409       return false;
    410     }
    411     for (unsigned int k = 0; k<ai->num_denom_keys; k++)
    412     {
    413       const struct TALER_EXCHANGE_DenomPublicKey *dk
    414         = &a->keys->denom_keys[ai->denom_keys[k].denom_key_offset];
    415       bool found = false;
    416 
    417       if (GNUNET_TIME_absolute_cmp (dk->expire_deposit.abs_time,
    418                                     <=,
    419                                     valid_after))
    420         continue;
    421       for (unsigned int l = 0; l<bi->num_denom_keys; l++)
    422         if (0 ==
    423             GNUNET_memcmp (
    424               &dk->h_key,
    425               &b->keys->denom_keys[bi->denom_keys[l].denom_key_offset].h_key))
    426           found = true;
    427       if (! found)
    428       {
    429         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    430                     "Auditor %s does not audit denomination %s in `%s'\n",
    431                     ai->auditor_url,
    432                     GNUNET_h2s (&dk->h_key.hash),
    433                     b->label);
    434         return false;
    435       }
    436     }
    437   }
    438   return true;
    439 }
    440 
    441 
    442 /**
    443  * Check that the signing keys of @a a are also in @a b.
    444  *
    445  * @param a keys with the signing keys to look for
    446  * @param b keys to search
    447  * @return true if all of them were found
    448  */
    449 static bool
    450 signkeys_included (const struct KeysSnapshot *a,
    451                    const struct KeysSnapshot *b)
    452 {
    453   for (unsigned int i = 0; i<a->keys->num_sign_keys; i++)
    454   {
    455     const struct TALER_EXCHANGE_SigningPublicKey *sk
    456       = &a->keys->sign_keys[i];
    457     bool found = false;
    458 
    459     for (unsigned int j = 0; j<b->keys->num_sign_keys; j++)
    460       if ( (0 ==
    461             GNUNET_memcmp (&sk->key,
    462                            &b->keys->sign_keys[j].key)) &&
    463            (0 ==
    464             GNUNET_memcmp (&sk->master_sig,
    465                            &b->keys->sign_keys[j].master_sig)) )
    466         found = true;
    467     if (! found)
    468     {
    469       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    470                   "Signing key %s of `%s' missing in `%s'\n",
    471                   TALER_B2S (&sk->key),
    472                   a->label,
    473                   b->label);
    474       return false;
    475     }
    476   }
    477   return true;
    478 }
    479 
    480 
    481 /**
    482  * Check that @a later (cherry-picked and merged by the client) equals
    483  * @a full, downloaded in full right after it.  Denominations that
    484  * expire between the two downloads may legitimately differ and are
    485  * ignored.
    486  *
    487  * @param later cherry-picked /keys
    488  * @param full full /keys
    489  * @return true if they are the same
    490  */
    491 static bool
    492 check_equal (const struct KeysSnapshot *later,
    493              const struct KeysSnapshot *full)
    494 {
    495   bool ok = true;
    496 
    497   if (0 !=
    498       GNUNET_memcmp (&later->keys->master_pub,
    499                      &full->keys->master_pub))
    500   {
    501     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    502                 "Master public key differs between `%s' and `%s'\n",
    503                 later->label,
    504                 full->label);
    505     ok = false;
    506   }
    507   if (0 != strcmp (later->keys->currency,
    508                    full->keys->currency))
    509   {
    510     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    511                 "Currency differs between `%s' and `%s'\n",
    512                 later->label,
    513                 full->label);
    514     ok = false;
    515   }
    516   if (! denoms_included (later,
    517                          full,
    518                          full->reply_time))
    519     ok = false;
    520   if (! denoms_included (full,
    521                          later,
    522                          full->reply_time))
    523     ok = false;
    524   if (! auditors_included (later,
    525                            full,
    526                            full->reply_time))
    527     ok = false;
    528   if (! auditors_included (full,
    529                            later,
    530                            full->reply_time))
    531     ok = false;
    532   if (! signkeys_included (later,
    533                            full))
    534     ok = false;
    535   if (! signkeys_included (full,
    536                            later))
    537     ok = false;
    538   if (! no_expired_denoms (full))
    539     ok = false;
    540   if (! no_duplicate_denoms (full))
    541     ok = false;
    542   return ok;
    543 }
    544 
    545 
    546 /**
    547  * Run the "check keys" command.
    548  *
    549  * @param cls closure.
    550  * @param cmd the command currently being executed.
    551  * @param is the interpreter state.
    552  */
    553 static void
    554 check_keys_run (void *cls,
    555                 const struct TALER_TESTING_Command *cmd,
    556                 struct TALER_TESTING_Interpreter *is)
    557 {
    558   struct CheckKeysState *cks = cls;
    559   struct KeysSnapshot earlier;
    560   struct KeysSnapshot later;
    561 
    562   (void) cmd;
    563   if ( (GNUNET_OK !=
    564         get_snapshot (is,
    565                       cks->earlier_ref,
    566                       &earlier)) ||
    567        (GNUNET_OK !=
    568         get_snapshot (is,
    569                       cks->later_ref,
    570                       &later)) )
    571   {
    572     GNUNET_break (0);
    573     TALER_TESTING_interpreter_fail (is);
    574     return;
    575   }
    576   if (! check_successor (&earlier,
    577                          &later))
    578   {
    579     GNUNET_break (0);
    580     TALER_TESTING_interpreter_fail (is);
    581     return;
    582   }
    583   if (NULL != cks->full_ref)
    584   {
    585     struct KeysSnapshot full;
    586 
    587     if (GNUNET_OK !=
    588         get_snapshot (is,
    589                       cks->full_ref,
    590                       &full))
    591     {
    592       GNUNET_break (0);
    593       TALER_TESTING_interpreter_fail (is);
    594       return;
    595     }
    596     if (GNUNET_TIME_absolute_cmp (full.request_time,
    597                                   <,
    598                                   later.reply_time))
    599     {
    600       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    601                   "`%s' must download /keys after `%s'\n",
    602                   cks->full_ref,
    603                   cks->later_ref);
    604       GNUNET_break (0);
    605       TALER_TESTING_interpreter_fail (is);
    606       return;
    607     }
    608     if (! check_equal (&later,
    609                        &full))
    610     {
    611       GNUNET_break (0);
    612       TALER_TESTING_interpreter_fail (is);
    613       return;
    614     }
    615   }
    616   TALER_TESTING_interpreter_next (is);
    617 }
    618 
    619 
    620 /**
    621  * Cleanup the state.
    622  *
    623  * @param cls closure.
    624  * @param cmd the command which is being cleaned up.
    625  */
    626 static void
    627 check_keys_cleanup (void *cls,
    628                     const struct TALER_TESTING_Command *cmd)
    629 {
    630   struct CheckKeysState *cks = cls;
    631 
    632   (void) cmd;
    633   GNUNET_free (cks);
    634 }
    635 
    636 
    637 struct TALER_TESTING_Command
    638 TALER_TESTING_cmd_check_keys (const char *label,
    639                               const char *earlier_ref,
    640                               const char *later_ref,
    641                               const char *full_ref)
    642 {
    643   struct CheckKeysState *cks;
    644 
    645   GNUNET_assert (NULL != earlier_ref);
    646   GNUNET_assert (NULL != later_ref);
    647   cks = GNUNET_new (struct CheckKeysState);
    648   cks->earlier_ref = earlier_ref;
    649   cks->later_ref = later_ref;
    650   cks->full_ref = full_ref;
    651   {
    652     struct TALER_TESTING_Command cmd = {
    653       .cls = cks,
    654       .label = label,
    655       .run = &check_keys_run,
    656       .cleanup = &check_keys_cleanup
    657     };
    658 
    659     return cmd;
    660   }
    661 }
    662 
    663 
    664 /* end of testing_api_cmd_check_keys.c */