anastasis

Credential backup and recovery protocol and service
Log | Files | Refs | Submodules | README | LICENSE

anastasis_api_truth_challenge.c (13087B)


      1 /*
      2   This file is part of Anastasis
      3   Copyright (C) 2020, 2021, 2022 Anastasis SARL
      4 
      5   Anastasis 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   Anastasis 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   Anastasis; see the file COPYING.GPL.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file restclient/anastasis_api_truth_challenge.c
     18  * @brief Implementation of the POST /truth/$TID/challenge request on the client-side
     19  * @author Christian Grothoff
     20  * @author Dennis Neufeld
     21  * @author Dominik Meister
     22  */
     23 #include "platform.h"
     24 #include <curl/curl.h>
     25 #include <jansson.h>
     26 #include <microhttpd.h> /* just for HTTP status codes */
     27 #include "anastasis_service.h"
     28 #include "anastasis_api_curl_defaults.h"
     29 #include <taler/taler_curl_lib.h>
     30 #include <taler/taler_json_lib.h>
     31 #include <taler/taler_merchant_service.h>
     32 
     33 
     34 /**
     35  * @brief A Contract Operation Handle
     36  */
     37 struct ANASTASIS_TruthChallengeOperation
     38 {
     39   /**
     40    * The url for this request.
     41    */
     42   char *url;
     43 
     44   /**
     45    * Handle for the request.
     46    */
     47   struct GNUNET_CURL_Job *job;
     48 
     49   /**
     50    * Function to call with the result.
     51    */
     52   ANASTASIS_TruthChallengeCallback cb;
     53 
     54   /**
     55    * Closure for @a cb.
     56    */
     57   void *cb_cls;
     58 
     59   /**
     60    * Context for #TEH_curl_easy_post(). Keeps the data that must
     61    * persist for Curl to make the upload.
     62    */
     63   struct TALER_CURL_PostContext ctx;
     64 
     65   /**
     66    * Payment URI we received from the service, or NULL.
     67    */
     68   char *pay_uri;
     69 
     70   /**
     71    * Content type of the body.
     72    */
     73   char *content_type;
     74 };
     75 
     76 
     77 void
     78 ANASTASIS_truth_challenge_cancel (
     79   struct ANASTASIS_TruthChallengeOperation *tco)
     80 {
     81   if (NULL != tco->job)
     82   {
     83     GNUNET_CURL_job_cancel (tco->job);
     84     tco->job = NULL;
     85   }
     86   GNUNET_free (tco->pay_uri);
     87   GNUNET_free (tco->url);
     88   GNUNET_free (tco->content_type);
     89   TALER_curl_easy_post_finished (&tco->ctx);
     90   GNUNET_free (tco);
     91 }
     92 
     93 
     94 /**
     95  * Process POST /truth/$TID/challenge response
     96  *
     97  * @param cls our `struct ANASTASIS_TruthChallengeOperation *`
     98  * @param response_code the HTTP status
     99  * @param response parsed JSON result, NULL one rrro
    100  */
    101 static void
    102 handle_truth_challenge_finished (void *cls,
    103                                  long response_code,
    104                                  const void *response)
    105 {
    106   struct ANASTASIS_TruthChallengeOperation *tco = cls;
    107   const json_t *j = response;
    108   struct ANASTASIS_TruthChallengeDetails tcd = {
    109     .http_status = response_code,
    110     .response = j
    111   };
    112 
    113   tco->job = NULL;
    114   switch (response_code)
    115   {
    116   case 0:
    117     /* Hard error */
    118     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    119                 "Backend didn't even return from POST /truth/$TID/challenge\n");
    120     tcd.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    121     break;
    122   case MHD_HTTP_OK:
    123     {
    124       const char *ct;
    125       const char *tan_hint = NULL;
    126       const char *filename = NULL;
    127       const json_t *wire_details = NULL;
    128       struct GNUNET_JSON_Specification spec[] = {
    129         GNUNET_JSON_spec_string (
    130           "challenge_type",
    131           &ct),
    132         GNUNET_JSON_spec_mark_optional (
    133           GNUNET_JSON_spec_string ("tan_address_hint",
    134                                    &tan_hint),
    135           NULL),
    136         GNUNET_JSON_spec_mark_optional (
    137           GNUNET_JSON_spec_string ("filename",
    138                                    &filename),
    139           NULL),
    140         GNUNET_JSON_spec_mark_optional (
    141           GNUNET_JSON_spec_object_const ("wire_details",
    142                                          &wire_details),
    143           NULL),
    144         GNUNET_JSON_spec_end ()
    145       };
    146 
    147       if (GNUNET_OK !=
    148           GNUNET_JSON_parse (j,
    149                              spec,
    150                              NULL, NULL))
    151       {
    152         GNUNET_break_op (0);
    153         tcd.http_status = 0;
    154         tcd.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    155         break;
    156       }
    157       if (0 == strcmp (ct,
    158                        "TAN_SENT"))
    159       {
    160         tcd.details.success.cs = ANASTASIS_CS_TAN_SENT;
    161         tcd.details.success.details.tan_address_hint = tan_hint;
    162         break;
    163       }
    164       if (0 == strcmp (ct,
    165                        "TAN_ALREADY_SENT"))
    166       {
    167         tcd.details.success.cs = ANASTASIS_CS_TAN_ALREADY_SENT;
    168         break;
    169       }
    170       if ( (0 == strcmp (ct,
    171                          "FILE_WRITTEN")) &&
    172            (NULL != filename) )
    173       {
    174         tcd.details.success.cs = ANASTASIS_CS_FILE_WRITTEN;
    175         tcd.details.success.details.challenge_filename = filename;
    176         break;
    177       }
    178       if ( (0 == strcmp (ct,
    179                          "IBAN_WIRE")) &&
    180            (NULL != wire_details) )
    181       {
    182         struct GNUNET_JSON_Specification ispec[] = {
    183           GNUNET_JSON_spec_string (
    184             "credit_iban",
    185             &tcd.details.success.details.wire_funds.target_iban),
    186           GNUNET_JSON_spec_uint64 (
    187             "answer_code",
    188             &tcd.details.success.details.wire_funds.answer_code),
    189           GNUNET_JSON_spec_string (
    190             "business_name",
    191             &tcd.details.success.details.wire_funds.target_business_name),
    192           GNUNET_JSON_spec_string (
    193             "wire_transfer_subject",
    194             &tcd.details.success.details.wire_funds.wire_transfer_subject),
    195           TALER_JSON_spec_amount_any ("challenge_amount",
    196                                       &tcd.details.success.details.wire_funds.
    197                                       amount),
    198           GNUNET_JSON_spec_end ()
    199         };
    200 
    201         if (GNUNET_OK !=
    202             GNUNET_JSON_parse (wire_details,
    203                                ispec,
    204                                NULL, NULL))
    205         {
    206           GNUNET_break_op (0);
    207           tcd.http_status = 0;
    208           tcd.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    209           break;
    210         }
    211         tcd.details.success.cs = ANASTASIS_CS_WIRE_FUNDS;
    212         tco->cb (tco->cb_cls,
    213                  &tcd);
    214         ANASTASIS_truth_challenge_cancel (tco);
    215         return;
    216       }
    217       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    218                   "Unexpected challenge type `%s'\n",
    219                   ct);
    220       json_dumpf (j,
    221                   stderr,
    222                   JSON_INDENT (2));
    223       tcd.http_status = 0;
    224       tcd.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    225       break;
    226     }
    227   case MHD_HTTP_BAD_REQUEST:
    228     /* This should never happen, either us or the anastasis server is buggy
    229        (or API version conflict); just pass JSON reply to the application */
    230     GNUNET_break (0);
    231     tcd.ec = TALER_JSON_get_error_code (j);
    232     break;
    233   case MHD_HTTP_PAYMENT_REQUIRED:
    234     {
    235       struct TALER_MERCHANT_PayUriData pd;
    236 
    237       if ( (NULL == tco->pay_uri) ||
    238            (GNUNET_OK !=
    239             TALER_MERCHANT_parse_pay_uri (tco->pay_uri,
    240                                           &pd)) )
    241       {
    242         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    243                     "Failed to parse `%s'\n",
    244                     tco->pay_uri);
    245         tcd.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    246         break;
    247       }
    248       if (GNUNET_OK !=
    249           GNUNET_STRINGS_string_to_data (
    250             pd.order_id,
    251             strlen (pd.order_id),
    252             &tcd.details.payment_required.ps,
    253             sizeof (tcd.details.payment_required.ps)))
    254       {
    255         GNUNET_break (0);
    256         tcd.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    257         TALER_MERCHANT_parse_pay_uri_free (&pd);
    258         break;
    259       }
    260       tcd.details.payment_required.pd = &pd;
    261       tcd.details.payment_required.payment_request = tco->pay_uri;
    262       tco->cb (tco->cb_cls,
    263                &tcd);
    264       TALER_MERCHANT_parse_pay_uri_free (&pd);
    265       ANASTASIS_truth_challenge_cancel (tco);
    266       return;
    267     }
    268     break;
    269   case MHD_HTTP_FORBIDDEN:
    270     /* Nothing really to verify, authentication required/failed */
    271     tcd.ec = TALER_JSON_get_error_code (j);
    272     break;
    273   case MHD_HTTP_NOT_FOUND:
    274     /* Nothing really to verify */
    275     tcd.ec = TALER_JSON_get_error_code (j);
    276     break;
    277   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    278     tcd.ec = TALER_JSON_get_error_code (j);
    279     break;
    280   case MHD_HTTP_BAD_GATEWAY:
    281     tcd.ec = TALER_JSON_get_error_code (j);
    282     break;
    283   default:
    284     /* unexpected response code */
    285     tcd.ec = TALER_JSON_get_error_code (j);
    286     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    287                 "Unexpected response code %u/%d to POST /truth/$TID/challenge\n",
    288                 (unsigned int) response_code,
    289                 (int) tcd.ec);
    290     GNUNET_break (0);
    291     break;
    292   }
    293   tco->cb (tco->cb_cls,
    294            &tcd);
    295   ANASTASIS_truth_challenge_cancel (tco);
    296 }
    297 
    298 
    299 /**
    300  * Patch value in @a val, replacing new line with '\0'.
    301  *
    302  * @param[in,out] val 0-terminated string to replace '\\n' and '\\r' with '\\0' in.
    303  */
    304 static void
    305 patch_value (char *val)
    306 {
    307   size_t len;
    308 
    309   /* found location URI we care about! */
    310   len = strlen (val);
    311   while ( (len > 0) &&
    312           ( ('\n' == val[len - 1]) ||
    313             ('\r' == val[len - 1]) ) )
    314   {
    315     len--;
    316     val[len] = '\0';
    317   }
    318 }
    319 
    320 
    321 /**
    322  * Handle HTTP header received by curl.
    323  *
    324  * @param buffer one line of HTTP header data
    325  * @param size size of an item
    326  * @param nitems number of items passed
    327  * @param userdata our `struct ANASTASIS_StorePolicyOperation *`
    328  * @return `size * nitems`
    329  */
    330 static size_t
    331 handle_header (char *buffer,
    332                size_t size,
    333                size_t nitems,
    334                void *userdata)
    335 {
    336   struct ANASTASIS_TruthChallengeOperation *tco = userdata;
    337   size_t total = size * nitems;
    338   char *ndup;
    339   const char *hdr_type;
    340   char *hdr_val;
    341   char *sp;
    342 
    343   ndup = GNUNET_strndup (buffer,
    344                          total);
    345   hdr_type = strtok_r (ndup,
    346                        ":",
    347                        &sp);
    348   if (NULL == hdr_type)
    349   {
    350     GNUNET_free (ndup);
    351     return total;
    352   }
    353   hdr_val = strtok_r (NULL,
    354                       "",
    355                       &sp);
    356   if (NULL == hdr_val)
    357   {
    358     GNUNET_free (ndup);
    359     return total;
    360   }
    361   if (' ' == *hdr_val)
    362     hdr_val++;
    363   if (0 == strcasecmp (hdr_type,
    364                        ANASTASIS_HTTP_HEADER_TALER))
    365   {
    366     /* found payment URI we care about! */
    367     GNUNET_free (tco->pay_uri);
    368     tco->pay_uri = GNUNET_strdup (hdr_val);
    369     patch_value (tco->pay_uri);
    370   }
    371   if (0 == strcasecmp (hdr_type,
    372                        MHD_HTTP_HEADER_CONTENT_TYPE))
    373   {
    374     /* found location URI we care about! */
    375     GNUNET_free (tco->content_type);
    376     tco->content_type = GNUNET_strdup (hdr_val);
    377     patch_value (tco->content_type);
    378   }
    379   GNUNET_free (ndup);
    380   return total;
    381 }
    382 
    383 
    384 struct ANASTASIS_TruthChallengeOperation *
    385 ANASTASIS_truth_challenge (
    386   struct GNUNET_CURL_Context *ctx,
    387   const char *backend_url,
    388   const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
    389   const struct ANASTASIS_CRYPTO_TruthKeyP *truth_key,
    390   const struct ANASTASIS_PaymentSecretP *payment_secret,
    391   ANASTASIS_TruthChallengeCallback cb,
    392   void *cb_cls)
    393 {
    394   struct ANASTASIS_TruthChallengeOperation *tco;
    395   CURL *eh;
    396   json_t *body;
    397 
    398   body = GNUNET_JSON_PACK (
    399     GNUNET_JSON_pack_data_auto ("truth_decryption_key",
    400                                 truth_key),
    401     GNUNET_JSON_pack_allow_null (
    402       GNUNET_JSON_pack_data_auto ("payment_secret",
    403                                   payment_secret)));
    404   GNUNET_assert (NULL != body);
    405   tco = GNUNET_new (struct ANASTASIS_TruthChallengeOperation);
    406   tco->cb = cb;
    407   tco->cb_cls = cb_cls;
    408   {
    409     char *path;
    410     char *uuid_str;
    411 
    412     uuid_str = GNUNET_STRINGS_data_to_string_alloc (truth_uuid,
    413                                                     sizeof (*truth_uuid));
    414     GNUNET_asprintf (&path,
    415                      "truth/%s/challenge",
    416                      uuid_str);
    417     GNUNET_free (uuid_str);
    418     tco->url = TALER_url_join (backend_url,
    419                                path,
    420                                NULL);
    421     GNUNET_free (path);
    422   }
    423   eh = ANASTASIS_curl_easy_get_ (tco->url);
    424   if ( (NULL == eh) ||
    425        (GNUNET_OK !=
    426         TALER_curl_easy_post (&tco->ctx,
    427                               eh,
    428                               body)) )
    429   {
    430     GNUNET_break (0);
    431     if (NULL != eh)
    432       curl_easy_cleanup (eh);
    433     json_decref (body);
    434     GNUNET_free (tco->url);
    435     GNUNET_free (tco);
    436     return NULL;
    437   }
    438   json_decref (body);
    439   GNUNET_assert (CURLE_OK ==
    440                  curl_easy_setopt (eh,
    441                                    CURLOPT_HEADERFUNCTION,
    442                                    &handle_header));
    443   GNUNET_assert (CURLE_OK ==
    444                  curl_easy_setopt (eh,
    445                                    CURLOPT_HEADERDATA,
    446                                    tco));
    447   tco->job = GNUNET_CURL_job_add2 (ctx,
    448                                    eh,
    449                                    tco->ctx.headers,
    450                                    &handle_truth_challenge_finished,
    451                                    tco);
    452   return tco;
    453 }
    454 
    455 
    456 /* end of anastasis_api_truth_challenge.c */