merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

test_fountain_parsers.c (11429B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2026 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Lesser General Public License as published by the Free
      7   Software Foundation; either version 2.1, 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
     11   FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
     12   more details. You should have received a copy along with TALER; see
     13   COPYING.LGPL. If not, see <http://www.gnu.org/licenses/>.
     14 */
     15 /**
     16  * @file lib/test_fountain_parsers.c
     17  * @brief Exercise the HTTP-200 parsers directly, including hostile array sizes.
     18  */
     19 #define handle_ok info_handle_ok
     20 #include "merchant_api_get-fountain-info.c"
     21 #undef handle_ok
     22 #define handle_ok withdraw_handle_ok
     23 #include "merchant_api_post-fountain-withdraw.c"
     24 #undef handle_ok
     25 #define handle_ok list_handle_ok
     26 #include "merchant_api_get-private-fountains.c"
     27 #undef handle_ok
     28 #define handle_ok detail_handle_ok
     29 #include "merchant_api_get-private-fountains-FOUNTAIN_ID.c"
     30 #undef handle_ok
     31 #include <sys/resource.h>
     32 
     33 struct Expected
     34 {
     35   unsigned int count;
     36   unsigned int calls;
     37   bool malformed;
     38 };
     39 
     40 static void
     41 info_cb (void *cls,
     42          const struct TALER_MERCHANT_GetFountainInfoResponse *r)
     43 {
     44   struct Expected *e = cls;
     45 
     46   e->calls++;
     47   GNUNET_assert (e->malformed ? (0 == r->hr.http_status &&
     48                                  TALER_EC_GENERIC_REPLY_MALFORMED == r->hr.ec)
     49                  : (MHD_HTTP_OK == r->hr.http_status &&
     50                     e->count == r->details.ok.grants_len));
     51   if ( (! e->malformed) && (0 != e->count) )
     52     GNUNET_assert (0 == strcmp (r->details.ok.grants[0].token_family.name,
     53                                 "Test"));
     54 }
     55 
     56 
     57 static void
     58 withdraw_cb (void *cls,
     59              const struct TALER_MERCHANT_PostFountainWithdrawResponse *r)
     60 {
     61   struct Expected *e = cls;
     62 
     63   e->calls++;
     64   GNUNET_assert (e->malformed ? (0 == r->hr.http_status &&
     65                                  TALER_EC_GENERIC_REPLY_MALFORMED == r->hr.ec)
     66                  : (MHD_HTTP_OK == r->hr.http_status &&
     67                     e->count == r->details.ok.results_len));
     68   if ( (! e->malformed) && (0 != e->count) )
     69     GNUNET_assert (0 == strcmp (r->details.ok.results[0].token_family_slug,
     70                                 "test"));
     71 }
     72 
     73 
     74 static void
     75 list_cb (void *cls,
     76          const struct TALER_MERCHANT_GetPrivateFountainsResponse *r)
     77 {
     78   struct Expected *e = cls;
     79 
     80   e->calls++;
     81   GNUNET_assert (e->malformed ? (0 == r->hr.http_status &&
     82                                  TALER_EC_GENERIC_REPLY_MALFORMED == r->hr.ec)
     83                  : (MHD_HTTP_OK == r->hr.http_status &&
     84                     e->count == r->details.ok.fountains_len));
     85   if ( (! e->malformed) && (0 != e->count) )
     86     GNUNET_assert (0 == strcmp (r->details.ok.fountains[0].fountain_id,
     87                                 "test"));
     88 }
     89 
     90 
     91 static void
     92 detail_cb (void *cls,
     93            const struct TALER_MERCHANT_GetPrivateFountainResponse *r)
     94 {
     95   struct Expected *e = cls;
     96 
     97   e->calls++;
     98   GNUNET_assert (e->malformed ? (0 == r->hr.http_status &&
     99                                  TALER_EC_GENERIC_REPLY_MALFORMED == r->hr.ec)
    100                  : (MHD_HTTP_OK == r->hr.http_status &&
    101                     e->count == r->details.ok.grants_len));
    102   if ( (! e->malformed) && (0 != e->count) )
    103     GNUNET_assert (0 == strcmp (r->details.ok.grants[0].token_family_slug,
    104                                 "test"));
    105 }
    106 
    107 
    108 static void
    109 check (json_t *grants,
    110        unsigned int mode,
    111        bool malformed)
    112 {
    113   struct Expected expected = {
    114     .count = json_array_size (grants),
    115     .malformed = malformed
    116   };
    117   json_t *reply = json_pack ("{s:{s:i},s:O,s:O,s:s}",
    118                              "poll_freq", "d_us", 1000000,
    119                              "grants", grants, "fountains", grants,
    120                              "description", "Test");
    121 
    122   if (1 == mode)
    123   {
    124     struct TALER_MERCHANT_PostFountainWithdrawHandle h = {
    125       .cb = &withdraw_cb, .cb_cls = &expected
    126     };
    127     struct TALER_MERCHANT_PostFountainWithdrawResponse r = {
    128       .hr = {.http_status = MHD_HTTP_OK, .reply = reply}
    129     };
    130 
    131     withdraw_handle_ok (&h, &r);
    132   }
    133   else if (0 == mode)
    134   {
    135     struct TALER_MERCHANT_GetFountainInfoHandle h = {
    136       .cb = &info_cb, .cb_cls = &expected
    137     };
    138     struct TALER_MERCHANT_GetFountainInfoResponse r = {
    139       .hr = {.http_status = MHD_HTTP_OK, .reply = reply}
    140     };
    141 
    142     info_handle_ok (&h, &r);
    143   }
    144   else if (2 == mode)
    145   {
    146     struct TALER_MERCHANT_GetPrivateFountainsHandle h = {
    147       .cb = &list_cb, .cb_cls = &expected
    148     };
    149     struct TALER_MERCHANT_GetPrivateFountainsResponse r = {
    150       .hr = {.http_status = MHD_HTTP_OK, .reply = reply}
    151     };
    152 
    153     list_handle_ok (&h, &r);
    154   }
    155   else
    156   {
    157     struct TALER_MERCHANT_GetPrivateFountainHandle h = {
    158       .cb = &detail_cb, .cb_cls = &expected
    159     };
    160     struct TALER_MERCHANT_GetPrivateFountainResponse r = {
    161       .hr = {.http_status = MHD_HTTP_OK, .reply = reply}
    162     };
    163 
    164     detail_handle_ok (&h, &r);
    165   }
    166   GNUNET_assert (1 == expected.calls);
    167   json_decref (reply);
    168 }
    169 
    170 
    171 /**
    172  * Exercise each nested array through the real fountain-info parser. Use a
    173  * preceding valid grant as well, so every failure tests partial cleanup.
    174  */
    175 static void
    176 check_nested_arrays (json_t *info)
    177 {
    178   const char *fields[] = { "keys", "expected_domains", "trusted_domains" };
    179   const unsigned int limits[] = {
    180     TALER_MERCHANT_MAX_TOKEN_FAMILY_KEYS,
    181     TALER_MERCHANT_MAX_TOKEN_FAMILY_DOMAINS,
    182     TALER_MERCHANT_MAX_TOKEN_FAMILY_DOMAINS
    183   };
    184   /* 10 MB of null keys, or 30 MB of null domains, fits the HTTP limit but
    185      previously triggered a 48 MB allocation on a 64-bit client. */
    186   const unsigned int hostile_counts[] = { 2000000, 6000000, 6000000 };
    187   struct TALER_TokenIssuePrivateKey priv;
    188   struct TALER_TokenIssuePublicKey pub;
    189   struct GNUNET_TIME_Timestamp start = {
    190     .abs_time.abs_value_us = 1000000
    191   };
    192   struct GNUNET_TIME_Timestamp end = {
    193     .abs_time.abs_value_us = 2000000
    194   };
    195   json_t *key;
    196   json_t *domain = json_string ("merchant.example");
    197 
    198   GNUNET_CRYPTO_blind_sign_keys_create (&priv.private_key,
    199                                        &pub.public_key,
    200                                        GNUNET_CRYPTO_BSA_CS);
    201   key = GNUNET_JSON_PACK (
    202     TALER_JSON_pack_token_pub (NULL, &pub),
    203     GNUNET_JSON_pack_timestamp ("signature_validity_start", start),
    204     GNUNET_JSON_pack_timestamp ("signature_validity_end", end));
    205   GNUNET_CRYPTO_blind_sign_priv_decref (priv.private_key);
    206   GNUNET_CRYPTO_blind_sign_pub_decref (pub.public_key);
    207   for (unsigned int mode = 0; mode < 3; mode++)
    208   {
    209     json_t *grant = json_deep_copy (info);
    210     json_t *family = json_object_get (grant, "token_family");
    211     json_t *details = json_object_get (family, "details");
    212     json_t *array = json_array ();
    213     json_t *grants = json_array ();
    214     json_t *valid = (0 == mode) ? key : domain;
    215 
    216     if (2 == mode)
    217     {
    218       GNUNET_assert (0 == json_object_set_new (
    219                        details, "class", json_string ("subscription")));
    220       GNUNET_assert (0 == json_object_del (details, "expected_domains"));
    221     }
    222     GNUNET_assert (0 == json_object_set ((0 == mode) ? family : details,
    223                                          fields[mode], array));
    224     GNUNET_assert (0 == json_array_append (grants, info));
    225     GNUNET_assert (0 == json_array_append (grants, grant));
    226     check (grants, 0, false);
    227     GNUNET_assert (0 == json_array_append (array, valid));
    228     check (grants, 0, false);
    229     GNUNET_assert (0 == json_array_append_new (array, json_null ()));
    230     check (grants, 0, true);
    231     json_array_clear (array);
    232     for (unsigned int i = 0; i < limits[mode]; i++)
    233       GNUNET_assert (0 == json_array_append (array, valid));
    234     check (grants, 0, false);
    235     GNUNET_assert (0 == json_array_append (array, valid));
    236     check (grants, 0, true);
    237     json_array_clear (array);
    238     for (unsigned int i = 0; i < hostile_counts[mode]; i++)
    239       GNUNET_assert (0 == json_array_append_new (array, json_null ()));
    240     check (grants, 0, true);
    241     json_decref (grants);
    242     json_decref (grant);
    243     json_decref (array);
    244   }
    245   json_decref (key);
    246   json_decref (domain);
    247 }
    248 
    249 
    250 int
    251 main (void)
    252 {
    253   struct rlimit limit;
    254   json_t *grants = json_array ();
    255   json_t *info = json_loads (
    256     "{\"token_family_slug\":\"test\",\"tokens_per_period_limit\":1,"
    257     "\"tokens_per_period_stash\":1,\"key_window_size\":0,"
    258     "\"token_family\":{\"name\":\"Test\",\"description\":\"Test\","
    259     "\"keys\":[],\"critical\":false,"
    260     "\"details\":{\"class\":\"discount\",\"expected_domains\":[]}}}",
    261     0, NULL);
    262   struct TALER_TokenIssuePublicKeyHashP hash = {0};
    263   json_t *withdraw = GNUNET_JSON_PACK (
    264     GNUNET_JSON_pack_string ("token_family_slug", "test"),
    265     GNUNET_JSON_pack_data_auto ("h_issue", &hash),
    266     GNUNET_JSON_pack_array_steal ("token_sigs", json_array ()));
    267 
    268   json_t *list = json_pack ("{s:s,s:s}",
    269                             "fountain_id", "test", "description", "Test");
    270   json_t *valid_grants[] = { info, withdraw, list, info };
    271   unsigned int large_counts[] = { 100000, 100000, 600000, 300000 };
    272 
    273   GNUNET_assert (0 == getrlimit (RLIMIT_STACK, &limit));
    274   limit.rlim_cur = GNUNET_MIN (limit.rlim_cur, 8 * 1024 * 1024);
    275   GNUNET_assert (0 == setrlimit (RLIMIT_STACK, &limit));
    276   GNUNET_assert (NULL != info);
    277   for (unsigned int mode = 0; mode < 4; mode++)
    278   {
    279     json_t *valid = valid_grants[mode];
    280 
    281     json_array_clear (grants);
    282     check (grants, mode, false);
    283     GNUNET_assert (0 == json_array_append (grants, valid));
    284     check (grants, mode, false);
    285     /* Failure after a parsed grant must release partially parsed data. */
    286     GNUNET_assert (0 == json_array_append_new (
    287                      grants,
    288                      json_pack ("{s:[]}", "token_sigs")));
    289     check (grants, mode, true);
    290     json_array_clear (grants);
    291     for (unsigned int i = 0;
    292          i < TALER_MERCHANT_MAX_FOUNTAIN_RESPONSE_ITEMS; i++)
    293       GNUNET_assert (0 == json_array_append (grants, valid));
    294     check (grants, mode, false);
    295     GNUNET_assert (0 == json_array_append (grants, valid));
    296     check (grants, mode, 2 != mode);
    297     json_array_clear (grants);
    298     for (unsigned int i = 0; i < large_counts[mode]; i++)
    299       GNUNET_assert (0 == json_array_append_new (grants, json_null ()));
    300     check (grants, mode, true);
    301   }
    302   /* A list is not capped at the grant limit, but still has a byte budget. */
    303   json_array_clear (grants);
    304   for (size_t i = 0;
    305        i < GNUNET_MAX_MALLOC_CHECKED / sizeof (struct TALER_MERCHANT_FountainEntry);
    306        i++)
    307     GNUNET_assert (0 == json_array_append (grants, list));
    308   check (grants, 2, true);
    309   /* A small grant array must not hide an excessive signature total. */
    310   json_array_clear (grants);
    311   for (unsigned int i = 0; i < 2; i++)
    312   {
    313     json_t *grant = json_deep_copy (withdraw);
    314     json_t *sigs = json_object_get (grant, "token_sigs");
    315 
    316     for (unsigned int j = 0;
    317          j <= TALER_MERCHANT_MAX_FOUNTAIN_RESPONSE_ITEMS / 2; j++)
    318       GNUNET_assert (0 == json_array_append_new (sigs, json_object ()));
    319     GNUNET_assert (0 == json_array_append_new (grants, grant));
    320   }
    321   check (grants, true, true);
    322   json_decref (grants);
    323   check_nested_arrays (info);
    324   json_decref (info);
    325   json_decref (withdraw);
    326   json_decref (list);
    327   return 0;
    328 }