anastasis

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

anastasis_api_config.c (9241B)


      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_config.c
     18  * @brief Implementation of the /config GET
     19  * @author Christian Grothoff
     20  * @author Dennis Neufeld
     21  * @author Dominik Meister
     22  */
     23 #include "platform.h"
     24 #include <curl/curl.h>
     25 #include <microhttpd.h> /* just for HTTP status codes */
     26 #include "anastasis_service.h"
     27 #include "anastasis_api_curl_defaults.h"
     28 #include <gnunet/gnunet_json_lib.h>
     29 #include <taler/taler_json_lib.h>
     30 
     31 
     32 /**
     33  * Which version of the Taler protocol is implemented
     34  * by this library?  Used to determine compatibility.
     35  *
     36  * Raised to 1 when the key derivation was fixed to use the full context
     37  * string: every key derived by a 0:x:x client differs, so backups written by
     38  * one side cannot be recovered by the other.  Hence 0 must stay outside of
     39  * the compatibility range.
     40  */
     41 #define ANASTASIS_PROTOCOL_CURRENT 2
     42 
     43 /**
     44  * How many versions are we backwards compatible with?
     45  */
     46 #define ANASTASIS_PROTOCOL_AGE 1
     47 
     48 
     49 struct ANASTASIS_ConfigOperation
     50 {
     51   /**
     52    * The url for this request.
     53    */
     54   char *url;
     55 
     56   /**
     57    * Handle for the request.
     58    */
     59   struct GNUNET_CURL_Job *job;
     60 
     61   /**
     62    * Reference to the execution context.
     63    */
     64   struct GNUNET_CURL_Context *ctx;
     65 
     66   /**
     67   * The callback to pass the backend response to
     68   */
     69   ANASTASIS_ConfigCallback cb;
     70 
     71   /**
     72    * Closure for @a cb.
     73    */
     74   void *cb_cls;
     75 
     76 };
     77 
     78 
     79 /**
     80  * Function called when we're done processing the
     81  * HTTP /config request.
     82  *
     83  * @param cls the `struct ANASTASIS_ConfigOperation`
     84  * @param response_code HTTP response code, 0 on error
     85  * @param response parsed JSON result, NULL on error
     86  */
     87 static void
     88 handle_config_finished (void *cls,
     89                         long response_code,
     90                         const void *response)
     91 {
     92   struct ANASTASIS_ConfigOperation *co = cls;
     93   const json_t *json = response;
     94   struct ANASTASIS_Config acfg = {
     95     .http_status = response_code,
     96     .response = json
     97   };
     98 
     99   co->job = NULL;
    100   switch (response_code)
    101   {
    102   case 0:
    103     /* No reply received */
    104     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    105                 "Backend `%s' failed to respond to GET /config\n",
    106                 co->url);
    107     break;
    108   case MHD_HTTP_OK:
    109     {
    110       const char *name;
    111       const json_t *methods;
    112       struct TALER_JSON_ProtocolVersion pv;
    113       struct GNUNET_JSON_Specification spec[] = {
    114         GNUNET_JSON_spec_string ("name",
    115                                  &name),
    116         GNUNET_JSON_spec_string ("business_name",
    117                                  &acfg.details.ok.business_name),
    118         GNUNET_JSON_spec_string ("version",
    119                                  &acfg.details.ok.version),
    120         TALER_JSON_spec_version ("version",
    121                                  &pv),
    122         GNUNET_JSON_spec_array_const ("methods",
    123                                       &methods),
    124         GNUNET_JSON_spec_uint32 ("storage_limit_in_megabytes",
    125                                  &acfg.details.ok.storage_limit_in_megabytes),
    126         TALER_JSON_spec_amount_any ("annual_fee",
    127                                     &acfg.details.ok.annual_fee),
    128         TALER_JSON_spec_amount_any ("truth_upload_fee",
    129                                     &acfg.details.ok.truth_upload_fee),
    130         TALER_JSON_spec_amount_any ("liability_limit",
    131                                     &acfg.details.ok.liability_limit),
    132         GNUNET_JSON_spec_fixed_auto ("provider_salt",
    133                                      &acfg.details.ok.provider_salt),
    134         GNUNET_JSON_spec_end ()
    135       };
    136 
    137       if (GNUNET_OK !=
    138           GNUNET_JSON_parse (json,
    139                              spec,
    140                              NULL, NULL))
    141       {
    142         GNUNET_break_op (0);
    143         json_dumpf (json,
    144                     stderr,
    145                     JSON_INDENT (2));
    146         acfg.http_status = 0;
    147         acfg.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    148         break;
    149       }
    150       if (0 != strcmp (name,
    151                        "anastasis"))
    152       {
    153         GNUNET_JSON_parse_free (spec);
    154         acfg.http_status = 0;
    155         acfg.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    156         break;
    157       }
    158       if ( (ANASTASIS_PROTOCOL_CURRENT < pv.current) &&
    159            (ANASTASIS_PROTOCOL_CURRENT < pv.current - pv.age) )
    160       {
    161         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    162                     "Provider protocol version too new\n");
    163         acfg.http_status = 0;
    164         acfg.ec = TALER_EC_GENERIC_VERSION_MALFORMED;
    165         break;
    166       }
    167       if ( (ANASTASIS_PROTOCOL_CURRENT > pv.current) &&
    168            (ANASTASIS_PROTOCOL_CURRENT - ANASTASIS_PROTOCOL_AGE > pv.current) )
    169       {
    170         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    171                     "Provider protocol version too old\n");
    172         GNUNET_break_op (0);
    173         acfg.http_status = 0;
    174         acfg.ec = TALER_EC_GENERIC_VERSION_MALFORMED;
    175         break;
    176       }
    177       acfg.details.ok.methods_length = (unsigned int) json_array_size (methods);
    178       if (((size_t) acfg.details.ok.methods_length) !=
    179           json_array_size (methods))
    180       {
    181         GNUNET_break_op (0);
    182         acfg.http_status = 0;
    183         acfg.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    184         break;
    185       }
    186       /* Bound the array so a malicious provider cannot force an unbounded
    187          stack VLA (a ~40 MB response could otherwise yield ~10^6 elements). */
    188       if (acfg.details.ok.methods_length > 1024)
    189       {
    190         GNUNET_break_op (0);
    191         acfg.http_status = 0;
    192         acfg.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    193         break;
    194       }
    195       {
    196         struct ANASTASIS_AuthorizationMethodConfig mcfg[
    197           GNUNET_NZL (acfg.details.ok.methods_length)];
    198 
    199         for (unsigned int i = 0; i<acfg.details.ok.methods_length; i++)
    200         {
    201           struct ANASTASIS_AuthorizationMethodConfig *m = &mcfg[i];
    202           struct GNUNET_JSON_Specification ispec[] = {
    203             GNUNET_JSON_spec_string ("type",
    204                                      &m->type),
    205             TALER_JSON_spec_amount_any ("cost",
    206                                         &m->usage_fee),
    207             GNUNET_JSON_spec_end ()
    208           };
    209 
    210           if ( (GNUNET_OK !=
    211                 GNUNET_JSON_parse (json_array_get (methods,
    212                                                    i),
    213                                    ispec,
    214                                    NULL, NULL)) )
    215           {
    216             GNUNET_break_op (0);
    217             acfg.http_status = 0;
    218             acfg.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    219             goto end;
    220           }
    221         }
    222         acfg.details.ok.methods = mcfg;
    223         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    224                     "Good backend found at `%s'\n",
    225                     co->url);
    226         co->cb (co->cb_cls,
    227                 &acfg);
    228         ANASTASIS_config_cancel (co);
    229         return;
    230       }
    231     }
    232   case MHD_HTTP_BAD_REQUEST:
    233     /* This should never happen, either us or the anastasis server is buggy
    234        (or API version conflict); just pass JSON reply to the application */
    235     break;
    236   case MHD_HTTP_NOT_FOUND:
    237     /* Nothing really to verify */
    238     break;
    239   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    240     /* Server had an internal issue; we should retry, but this API
    241        leaves this to the application */
    242     break;
    243   default:
    244     /* unexpected response code */
    245     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    246                 "Unexpected response code %u\n",
    247                 (unsigned int) response_code);
    248     GNUNET_break_op (0);
    249     break;
    250   }
    251 end:
    252   co->cb (co->cb_cls,
    253           &acfg);
    254   ANASTASIS_config_cancel (co);
    255 }
    256 
    257 
    258 struct ANASTASIS_ConfigOperation *
    259 ANASTASIS_get_config (struct GNUNET_CURL_Context *ctx,
    260                       const char *base_url,
    261                       ANASTASIS_ConfigCallback cb,
    262                       void *cb_cls)
    263 {
    264   struct ANASTASIS_ConfigOperation *co;
    265 
    266   co = GNUNET_new (struct ANASTASIS_ConfigOperation);
    267   co->url = TALER_url_join (base_url,
    268                             "config",
    269                             NULL);
    270   co->ctx = ctx;
    271   co->cb = cb;
    272   co->cb_cls = cb_cls;
    273   {
    274     CURL *eh;
    275 
    276     eh = ANASTASIS_curl_easy_get_ (co->url);
    277     co->job = GNUNET_CURL_job_add (ctx,
    278                                    eh,
    279                                    &handle_config_finished,
    280                                    co);
    281   }
    282   if (NULL == co->job)
    283   {
    284     GNUNET_free (co->url);
    285     GNUNET_free (co);
    286     return NULL;
    287   }
    288   return co;
    289 }
    290 
    291 
    292 void
    293 ANASTASIS_config_cancel (struct ANASTASIS_ConfigOperation *co)
    294 {
    295   if (NULL != co->job)
    296   {
    297     GNUNET_CURL_job_cancel (co->job);
    298     co->job = NULL;
    299   }
    300   GNUNET_free (co->url);
    301   GNUNET_free (co);
    302 }
    303 
    304 
    305 /* end of anastasis_api_config.c */