anastasis

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

anastasis_api_keyshare_lookup.c (17726B)


      1 /*
      2   This file is part of Anastasis
      3   Copyright (C) 2020, 2021 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_keyshare_lookup.c
     18  * @brief Implementation of the GET /truth client
     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_json_lib.h>
     30 #include <taler/taler_merchant_service.h>
     31 
     32 
     33 /**
     34  * @brief A Contract Operation Handle
     35  */
     36 struct ANASTASIS_KeyShareLookupOperation
     37 {
     38   /**
     39    * The url for this request, including parameters.
     40    */
     41   char *url;
     42 
     43   /**
     44    * The url for this request, without response parameter.
     45    */
     46   char *display_url;
     47 
     48   /**
     49    * Handle for the request.
     50    */
     51   struct GNUNET_CURL_Job *job;
     52 
     53   /**
     54    * Function to call with the result.
     55    */
     56   ANASTASIS_KeyShareLookupCallback cb;
     57 
     58   /**
     59    * Closure for @a cb.
     60    */
     61   void *cb_cls;
     62 
     63   /**
     64    * Reference to the execution context.
     65    */
     66   struct GNUNET_CURL_Context *ctx;
     67 
     68   /**
     69    * Identification of the Truth Object
     70    */
     71   const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_public_key;
     72 
     73   /**
     74    * Key to decrypt the truth on the server
     75    */
     76   const struct ANASTASIS_CRYPTO_TruthKeyP *truth_key;
     77 
     78   /**
     79    * Hash of the response (security question)
     80    */
     81   const struct GNUNET_HashCode *hashed_answer;
     82 
     83   /**
     84    * Payment URI we received from the service, or NULL.
     85    */
     86   char *pay_uri;
     87 
     88   /**
     89    * Location URI we received from the service, or NULL.
     90    */
     91   char *location;
     92 
     93   /**
     94    * Content type of the body.
     95    */
     96   char *content_type;
     97 };
     98 
     99 
    100 void
    101 ANASTASIS_keyshare_lookup_cancel (
    102   struct ANASTASIS_KeyShareLookupOperation *kslo)
    103 {
    104   if (NULL != kslo->job)
    105   {
    106     GNUNET_CURL_job_cancel (kslo->job);
    107     kslo->job = NULL;
    108   }
    109   GNUNET_free (kslo->location);
    110   GNUNET_free (kslo->pay_uri);
    111   GNUNET_free (kslo->display_url);
    112   GNUNET_free (kslo->url);
    113   GNUNET_free (kslo->content_type);
    114   GNUNET_free (kslo);
    115 }
    116 
    117 
    118 /**
    119  * Process GET /truth response
    120  *
    121  * @param cls our `struct ANASTASIS_KeyShareLookupOperation *`
    122  * @param response_code the HTTP status
    123  * @param data the body of the response
    124  * @param data_size number of bytes in @a data
    125  */
    126 static void
    127 handle_keyshare_lookup_finished (void *cls,
    128                                  long response_code,
    129                                  const void *data,
    130                                  size_t data_size)
    131 {
    132   struct ANASTASIS_KeyShareLookupOperation *kslo = cls;
    133   struct ANASTASIS_KeyShareDownloadDetails kdd;
    134 
    135   kslo->job = NULL;
    136   memset (&kdd,
    137           0,
    138           sizeof (kdd));
    139   kdd.server_url = kslo->display_url;
    140   switch (response_code)
    141   {
    142   case 0:
    143     /* Hard error */
    144     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    145                 "Backend didn't even return from GET /truth\n");
    146     kdd.status = ANASTASIS_KSD_SERVER_ERROR;
    147     kdd.details.server_failure.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    148     break;
    149   case MHD_HTTP_OK:
    150     if (sizeof (struct ANASTASIS_CRYPTO_EncryptedKeyShareP) != data_size)
    151     {
    152       GNUNET_break_op (0);
    153       kdd.status = ANASTASIS_KSD_SERVER_ERROR;
    154       kdd.details.server_failure.http_status = MHD_HTTP_OK;
    155       kdd.details.server_failure.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    156       break;
    157     }
    158     /* Success, call callback with all details! */
    159     memcpy (&kdd.details.eks,
    160             data,
    161             data_size);
    162     break;
    163   case MHD_HTTP_ACCEPTED:
    164     kdd.details.external_challenge = json_loadb (data,
    165                                                  data_size,
    166                                                  JSON_REJECT_DUPLICATES,
    167                                                  NULL);
    168     if (NULL == kdd.details.external_challenge)
    169     {
    170       GNUNET_break_op (0);
    171       kdd.status = ANASTASIS_KSD_SERVER_ERROR;
    172       kdd.details.server_failure.http_status = MHD_HTTP_ACCEPTED;
    173       kdd.details.server_failure.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    174       break;
    175     }
    176     kdd.status = ANASTASIS_KSD_EXTERNAL_CHALLENGE_INSTRUCTIONS;
    177     break;
    178   case MHD_HTTP_BAD_REQUEST:
    179     /* This should never happen, either us or the anastasis server is buggy
    180        (or API version conflict); just pass JSON reply to the application */
    181     GNUNET_break (0);
    182     kdd.status = ANASTASIS_KSD_CLIENT_FAILURE;
    183     kdd.details.server_failure.http_status = MHD_HTTP_BAD_REQUEST;
    184     kdd.details.server_failure.ec = TALER_EC_GENERIC_JSON_INVALID;
    185     break;
    186   case MHD_HTTP_PAYMENT_REQUIRED:
    187     {
    188       struct TALER_MERCHANT_PayUriData pd;
    189 
    190       if ( (NULL == kslo->pay_uri) ||
    191            (GNUNET_OK !=
    192             TALER_MERCHANT_parse_pay_uri (kslo->pay_uri,
    193                                           &pd)) )
    194       {
    195         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    196                     "Failed to parse `%s'\n",
    197                     kslo->pay_uri);
    198         kdd.status = ANASTASIS_KSD_SERVER_ERROR;
    199         kdd.details.server_failure.http_status = MHD_HTTP_PAYMENT_REQUIRED;
    200         kdd.details.server_failure.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    201         break;
    202       }
    203       if (GNUNET_OK !=
    204           GNUNET_STRINGS_string_to_data (
    205             pd.order_id,
    206             strlen (pd.order_id),
    207             &kdd.details.payment_required.payment_secret,
    208             sizeof (kdd.details.payment_required.payment_secret)))
    209       {
    210         GNUNET_break (0);
    211         kdd.status = ANASTASIS_KSD_SERVER_ERROR;
    212         kdd.details.server_failure.http_status = MHD_HTTP_PAYMENT_REQUIRED;
    213         kdd.details.server_failure.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    214         TALER_MERCHANT_parse_pay_uri_free (&pd);
    215         break;
    216       }
    217       kdd.status = ANASTASIS_KSD_PAYMENT_REQUIRED;
    218       kdd.details.payment_required.taler_pay_uri = kslo->pay_uri;
    219       kslo->cb (kslo->cb_cls,
    220                 &kdd);
    221       ANASTASIS_keyshare_lookup_cancel (kslo);
    222       TALER_MERCHANT_parse_pay_uri_free (&pd);
    223       return;
    224     }
    225     break;
    226   case MHD_HTTP_SEE_OTHER:
    227     /* Nothing really to verify, authentication required/failed */
    228     kdd.status = ANASTASIS_KSD_REDIRECT_FOR_AUTHENTICATION;
    229     kdd.details.redirect_url = kslo->location;
    230     break;
    231   case MHD_HTTP_FORBIDDEN:
    232     /* Nothing really to verify, authentication required/failed */
    233     kdd.status = ANASTASIS_KSD_INVALID_ANSWER;
    234     kdd.details.open_challenge.body = data;
    235     kdd.details.open_challenge.body_size = data_size;
    236     kdd.details.open_challenge.content_type = kslo->content_type;
    237     kdd.details.open_challenge.http_status = response_code;
    238     break;
    239   case MHD_HTTP_NOT_FOUND:
    240     /* Nothing really to verify */
    241     kdd.status = ANASTASIS_KSD_TRUTH_UNKNOWN;
    242     break;
    243   case MHD_HTTP_REQUEST_TIMEOUT:
    244     /* Nothing really to verify */
    245     kdd.status = ANASTASIS_KSD_AUTHENTICATION_TIMEOUT;
    246     break;
    247   case MHD_HTTP_CONFLICT:
    248     /* Nothing really to verify */
    249     kdd.status = ANASTASIS_KSD_CLIENT_FAILURE;
    250     kdd.details.server_failure.http_status = MHD_HTTP_CONFLICT;
    251     kdd.details.server_failure.ec = TALER_JSON_get_error_code2 (data,
    252                                                                 data_size);
    253     break;
    254   case MHD_HTTP_GONE:
    255     /* Nothing really to verify */
    256     kdd.status = ANASTASIS_KSD_TRUTH_UNKNOWN;
    257     break;
    258   case MHD_HTTP_TOO_MANY_REQUESTS:
    259     kdd.status = ANASTASIS_KSD_RATE_LIMIT_EXCEEDED;
    260     {
    261       struct GNUNET_JSON_Specification spec[] = {
    262         GNUNET_JSON_spec_uint32 (
    263           "request_limit",
    264           &kdd.details.rate_limit_exceeded.request_limit),
    265         GNUNET_JSON_spec_relative_time (
    266           "request_frequency",
    267           &kdd.details.rate_limit_exceeded.request_frequency),
    268         GNUNET_JSON_spec_end ()
    269       };
    270       json_t *reply;
    271 
    272       reply = json_loadb (data,
    273                           data_size,
    274                           JSON_REJECT_DUPLICATES,
    275                           NULL);
    276       if (NULL == reply)
    277       {
    278         GNUNET_break_op (0);
    279         kdd.status = ANASTASIS_KSD_SERVER_ERROR;
    280         kdd.details.server_failure.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    281         kdd.details.server_failure.http_status = response_code;
    282         break;
    283       }
    284       if (GNUNET_OK !=
    285           GNUNET_JSON_parse (reply,
    286                              spec,
    287                              NULL, NULL))
    288       {
    289         GNUNET_break_op (0);
    290         kdd.status = ANASTASIS_KSD_SERVER_ERROR;
    291         kdd.details.server_failure.ec = TALER_JSON_get_error_code (reply);
    292         kdd.details.server_failure.http_status = response_code;
    293         json_decref (reply);
    294         break;
    295       }
    296       json_decref (reply);
    297     }
    298     break;
    299   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    300     /* Server had an internal issue; we should retry, but this API
    301        leaves this to the application */
    302     kdd.status = ANASTASIS_KSD_SERVER_ERROR;
    303     kdd.details.server_failure.ec = TALER_JSON_get_error_code2 (data,
    304                                                                 data_size);
    305     kdd.details.server_failure.http_status = response_code;
    306     break;
    307   case MHD_HTTP_BAD_GATEWAY:
    308     kdd.status = ANASTASIS_KSD_SERVER_ERROR;
    309     kdd.details.server_failure.ec = TALER_JSON_get_error_code2 (data,
    310                                                                 data_size);
    311     kdd.details.server_failure.http_status = response_code;
    312     break;
    313   default:
    314     /* unexpected response code */
    315     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    316                 "Unexpected response code %u to GET /truth\n",
    317                 (unsigned int) response_code);
    318     GNUNET_break (0);
    319     kdd.status = ANASTASIS_KSD_SERVER_ERROR;
    320     kdd.details.server_failure.ec = TALER_JSON_get_error_code2 (data,
    321                                                                 data_size);
    322     kdd.details.server_failure.http_status = response_code;
    323     break;
    324   }
    325   kslo->cb (kslo->cb_cls,
    326             &kdd);
    327   ANASTASIS_keyshare_lookup_cancel (kslo);
    328 }
    329 
    330 
    331 /**
    332  * Patch value in @a val, replacing new line with '\0'.
    333  *
    334  * @param[in,out] val 0-terminated string to replace '\\n' and '\\r' with '\\0' in.
    335  */
    336 static void
    337 patch_value (char *val)
    338 {
    339   size_t len;
    340 
    341   /* found location URI we care about! */
    342   len = strlen (val);
    343   while ( (len > 0) &&
    344           ( ('\n' == val[len - 1]) ||
    345             ('\r' == val[len - 1]) ) )
    346   {
    347     len--;
    348     val[len] = '\0';
    349   }
    350 }
    351 
    352 
    353 /**
    354  * Handle HTTP header received by curl.
    355  *
    356  * @param buffer one line of HTTP header data
    357  * @param size size of an item
    358  * @param nitems number of items passed
    359  * @param userdata our `struct ANASTASIS_StorePolicyOperation *`
    360  * @return `size * nitems`
    361  */
    362 static size_t
    363 handle_header (char *buffer,
    364                size_t size,
    365                size_t nitems,
    366                void *userdata)
    367 {
    368   struct ANASTASIS_KeyShareLookupOperation *kslo = userdata;
    369   size_t total = size * nitems;
    370   char *ndup;
    371   const char *hdr_type;
    372   char *hdr_val;
    373   char *sp;
    374 
    375   ndup = GNUNET_strndup (buffer,
    376                          total);
    377   hdr_type = strtok_r (ndup,
    378                        ":",
    379                        &sp);
    380   if (NULL == hdr_type)
    381   {
    382     GNUNET_free (ndup);
    383     return total;
    384   }
    385   hdr_val = strtok_r (NULL,
    386                       "",
    387                       &sp);
    388   if (NULL == hdr_val)
    389   {
    390     GNUNET_free (ndup);
    391     return total;
    392   }
    393   if (' ' == *hdr_val)
    394     hdr_val++;
    395   if (0 == strcasecmp (hdr_type,
    396                        ANASTASIS_HTTP_HEADER_TALER))
    397   {
    398     /* found payment URI we care about! */
    399     GNUNET_free (kslo->pay_uri);
    400     kslo->pay_uri = GNUNET_strdup (hdr_val);
    401     patch_value (kslo->pay_uri);
    402   }
    403   if (0 == strcasecmp (hdr_type,
    404                        MHD_HTTP_HEADER_LOCATION))
    405   {
    406     /* found location URI we care about! */
    407     GNUNET_free (kslo->location);
    408     kslo->location = GNUNET_strdup (hdr_val);
    409     patch_value (kslo->location);
    410   }
    411   if (0 == strcasecmp (hdr_type,
    412                        MHD_HTTP_HEADER_CONTENT_TYPE))
    413   {
    414     /* found location URI we care about! */
    415     GNUNET_free (kslo->content_type);
    416     kslo->content_type = GNUNET_strdup (hdr_val);
    417     patch_value (kslo->content_type);
    418   }
    419   GNUNET_free (ndup);
    420   return total;
    421 }
    422 
    423 
    424 struct ANASTASIS_KeyShareLookupOperation *
    425 ANASTASIS_keyshare_lookup (
    426   struct GNUNET_CURL_Context *ctx,
    427   const char *backend_url,
    428   const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
    429   const struct ANASTASIS_CRYPTO_TruthKeyP *truth_key,
    430   const struct ANASTASIS_PaymentSecretP *payment_secret,
    431   struct GNUNET_TIME_Relative timeout,
    432   const struct GNUNET_HashCode *hashed_answer,
    433   ANASTASIS_KeyShareLookupCallback cb,
    434   void *cb_cls)
    435 {
    436   struct ANASTASIS_KeyShareLookupOperation *kslo;
    437   CURL *eh;
    438   struct curl_slist *job_headers;
    439   char *path;
    440   char *answer_s;
    441   unsigned long long tms;
    442 
    443   tms = (unsigned long long) (timeout.rel_value_us
    444                               / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
    445   job_headers = NULL;
    446   {
    447     struct curl_slist *ext;
    448     char *val;
    449     char *hdr;
    450 
    451     /* Set Anastasis-Truth-Decryption-Key header */
    452     val = GNUNET_STRINGS_data_to_string_alloc (truth_key,
    453                                                sizeof (*truth_key));
    454     GNUNET_asprintf (&hdr,
    455                      "%s: %s",
    456                      ANASTASIS_HTTP_HEADER_TRUTH_DECRYPTION_KEY,
    457                      val);
    458     GNUNET_free (val);
    459     ext = curl_slist_append (job_headers,
    460                              hdr);
    461     GNUNET_free (hdr);
    462     if (NULL == ext)
    463     {
    464       GNUNET_break (0);
    465       curl_slist_free_all (job_headers);
    466       return NULL;
    467     }
    468     job_headers = ext;
    469 
    470     /* Setup Payment-Identifier header */
    471     if (NULL != payment_secret)
    472     {
    473       char *paid_order_id;
    474 
    475       paid_order_id = GNUNET_STRINGS_data_to_string_alloc (
    476         payment_secret,
    477         sizeof (*payment_secret));
    478       GNUNET_asprintf (&hdr,
    479                        "%s: %s",
    480                        ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER,
    481                        paid_order_id);
    482       GNUNET_free (paid_order_id);
    483       ext = curl_slist_append (job_headers,
    484                                hdr);
    485       GNUNET_free (hdr);
    486       if (NULL == ext)
    487       {
    488         GNUNET_break (0);
    489         curl_slist_free_all (job_headers);
    490         return NULL;
    491       }
    492       job_headers = ext;
    493     }
    494   }
    495   kslo = GNUNET_new (struct ANASTASIS_KeyShareLookupOperation);
    496   kslo->ctx = ctx;
    497   kslo->truth_key = truth_key;
    498   {
    499     char *uuid_str;
    500 
    501     uuid_str = GNUNET_STRINGS_data_to_string_alloc (truth_uuid,
    502                                                     sizeof (*truth_uuid));
    503     GNUNET_asprintf (&path,
    504                      "truth/%s",
    505                      uuid_str);
    506     GNUNET_free (uuid_str);
    507   }
    508   {
    509     char timeout_ms[32];
    510 
    511     GNUNET_snprintf (timeout_ms,
    512                      sizeof (timeout_ms),
    513                      "%llu",
    514                      tms);
    515     if (NULL != hashed_answer)
    516     {
    517       answer_s = GNUNET_STRINGS_data_to_string_alloc (hashed_answer,
    518                                                       sizeof (*hashed_answer));
    519       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    520                   "Querying challenge with existing response code\n");
    521       kslo->url = TALER_url_join (backend_url,
    522                                   path,
    523                                   "response",
    524                                   answer_s,
    525                                   "timeout_ms",
    526                                   (0 != timeout.rel_value_us)
    527                                   ? timeout_ms
    528                                   : NULL,
    529                                   NULL);
    530       GNUNET_free (answer_s);
    531     }
    532     else
    533     {
    534       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    535                   "Querying challenge without response code\n");
    536       kslo->url = TALER_url_join (backend_url,
    537                                   path,
    538                                   "timeout_ms",
    539                                   (0 != timeout.rel_value_us)
    540                                   ? timeout_ms
    541                                   : NULL,
    542                                   NULL);
    543     }
    544   }
    545   kslo->display_url = TALER_url_join (backend_url,
    546                                       path,
    547                                       NULL);
    548   GNUNET_free (path);
    549   eh = ANASTASIS_curl_easy_get_ (kslo->url);
    550   if (0 != tms)
    551     GNUNET_assert (CURLE_OK ==
    552                    curl_easy_setopt (eh,
    553                                      CURLOPT_TIMEOUT_MS,
    554                                      (long) (tms + 5000)));
    555   GNUNET_assert (CURLE_OK ==
    556                  curl_easy_setopt (eh,
    557                                    CURLOPT_HEADERFUNCTION,
    558                                    &handle_header));
    559   GNUNET_assert (CURLE_OK ==
    560                  curl_easy_setopt (eh,
    561                                    CURLOPT_HEADERDATA,
    562                                    kslo));
    563   kslo->cb = cb;
    564   kslo->cb_cls = cb_cls;
    565   kslo->job = GNUNET_CURL_job_add_raw (ctx,
    566                                        eh,
    567                                        job_headers,
    568                                        &handle_keyshare_lookup_finished,
    569                                        kslo);
    570   curl_slist_free_all (job_headers);
    571   return kslo;
    572 }
    573 
    574 
    575 /* end of anastasis_api_keyshare_lookup.c */