anastasis

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

anastasis_api_discovery.c (15421B)


      1 /*
      2   This file is part of Anastasis
      3   Copyright (C) 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 reducer/anastasis_api_discovery.c
     18  * @brief anastasis recovery policy discovery api
     19  * @author Christian Grothoff
     20  */
     21 #include <platform.h>
     22 #include <jansson.h>
     23 #include "anastasis_redux.h"
     24 #include "anastasis_error_codes.h"
     25 #include <taler/taler_json_lib.h>
     26 #include "anastasis_api_redux.h"
     27 #include "anastasis_api_redux_state.h"
     28 
     29 
     30 /**
     31  * Maximum number of *optional* identity attributes we are willing to
     32  * enumerate subsets of in #ANASTASIS_policy_discovery_start().  The mask
     33  * loop there is 2^n, so this bounds the work at 65536 combinations; it also
     34  * keeps `1LLU << opt_cnt` well-defined (it is undefined for n >= 64) and the
     35  * 64-bit loop counter from wrapping.
     36  */
     37 #define MAX_OPTIONAL_ATTRIBUTES 16
     38 
     39 
     40 /**
     41  * Handle for one request we are doing at a specific provider.
     42  */
     43 struct ProviderOperation
     44 {
     45   /**
     46    * Kept in a DLL.
     47    */
     48   struct ProviderOperation *next;
     49 
     50   /**
     51    * Kept in a DLL.
     52    */
     53   struct ProviderOperation *prev;
     54 
     55   /**
     56    * Base URL of the provider.
     57    */
     58   char *provider_url;
     59 
     60   /**
     61    * Handle to the version check operation we are performing.
     62    */
     63   struct ANASTASIS_VersionCheck *vc;
     64 
     65   /**
     66    * Handle discovery operation we this is a part of.
     67    */
     68   struct ANASTASIS_PolicyDiscovery *pd;
     69 
     70   /**
     71    * Attribute mask applied to the identity attributes
     72    * for this operation.
     73    */
     74   struct ANASTASIS_AttributeMask attribute_mask;
     75 };
     76 
     77 
     78 /**
     79  * Handle for a discovery operation.
     80  */
     81 struct ANASTASIS_PolicyDiscovery
     82 {
     83   /**
     84    * Head of HTTP requests, kept in a DLL.
     85    */
     86   struct ProviderOperation *po_head;
     87 
     88   /**
     89    * Tail of HTTP requests, kept in a DLL.
     90    */
     91   struct ProviderOperation *po_tail;
     92 
     93   /**
     94    * Function to call with results.
     95    */
     96   ANASTASIS_PolicyDiscoveryCallback cb;
     97 
     98   /**
     99    * Closure for @e cb.
    100    */
    101   void *cb_cls;
    102 
    103   /**
    104    * Map for duplicate detection, maps hashes of policies we
    105    * have already seen to a json_array with all providers
    106    * and versions corresponding to this policy hash.
    107    */
    108   struct GNUNET_CONTAINER_MultiHashMap *dd_map;
    109 
    110   /**
    111    * State we are operating on.
    112    */
    113   struct ANASTASIS_ReduxState *rs;
    114 
    115   /**
    116    * Number of optional fields in our identity attributes.
    117    */
    118   unsigned int opt_cnt;
    119 };
    120 
    121 
    122 /**
    123  * Return a copy of @a master_id with the optional attributes selected
    124  * by @a mask removed.  The internal, typed counterpart of
    125  * #ANASTASIS_mask_id_data().
    126  *
    127  * @param common state describing which attributes exist
    128  * @param master_id the user's identity attributes
    129  * @param mask which of the optional attributes to drop
    130  * @return NULL if @a master_id does not match @a common
    131  */
    132 static json_t *
    133 mask_id_data (const struct ANASTASIS_ReduxCommon *common,
    134               const json_t *master_id,
    135               struct ANASTASIS_AttributeMask mask)
    136 {
    137   json_t *ret = json_deep_copy (master_id);
    138   unsigned int bit = 0;
    139 
    140   GNUNET_assert (NULL != ret);
    141   for (unsigned int i = 0; i < common->required_attributes_len; i++)
    142   {
    143     const struct ANASTASIS_ReduxAttributeSpec *a
    144       = &common->required_attributes[i];
    145     bool present;
    146 
    147     present = (NULL !=
    148                json_object_get (master_id,
    149                                 a->name));
    150     if ( (! present) && (! a->optional) )
    151     {
    152       GNUNET_break (0);
    153       json_decref (ret);
    154       return NULL;
    155     }
    156     if (present && a->optional)
    157     {
    158       if (0 != ((1LLU << bit) & mask.mask))
    159       {
    160         GNUNET_assert (0 ==
    161                        json_object_del (ret,
    162                                         a->name));
    163       }
    164       bit++;
    165     }
    166   }
    167   return ret;
    168 }
    169 
    170 
    171 /**
    172  * Callback which passes back meta data about one of the
    173  * recovery documents available at the provider.
    174  *
    175  * @param cls our `struct ProviderOperation *`
    176  * @param version version number of the policy document,
    177  *                0 for the end of the list
    178  * @param server_time time of the backup at the provider
    179  * @param recdoc_id hash of the compressed recovery document, uniquely
    180  *                  identifies the document; NULL for the end of the list
    181  * @param secret_name name of the secret as chosen by the user,
    182  *                  or NULL if the user did not provide a name
    183  */
    184 static void
    185 meta_cb (void *cls,
    186          uint32_t version,
    187          struct GNUNET_TIME_Timestamp server_time,
    188          const struct GNUNET_HashCode *recdoc_id,
    189          const char *secret_name)
    190 {
    191   struct ProviderOperation *po = cls;
    192   struct ANASTASIS_PolicyDiscovery *pd = po->pd;
    193   json_t *pa;
    194   json_t *pe;
    195 
    196   if (NULL == recdoc_id)
    197   {
    198     po->vc = NULL;
    199     GNUNET_CONTAINER_DLL_remove (pd->po_head,
    200                                  pd->po_tail,
    201                                  po);
    202     GNUNET_free (po->provider_url);
    203     GNUNET_free (po);
    204     return;
    205   }
    206   pe = GNUNET_JSON_PACK (
    207     GNUNET_JSON_pack_uint64 ("version",
    208                              version),
    209     GNUNET_JSON_pack_string ("url",
    210                              po->provider_url));
    211 
    212   pa = GNUNET_CONTAINER_multihashmap_get (pd->dd_map,
    213                                           recdoc_id);
    214   if (NULL != pa)
    215   {
    216     GNUNET_break (0 ==
    217                   json_array_append_new (pa,
    218                                          pe));
    219     return;
    220   }
    221   pa = json_array ();
    222   GNUNET_assert (NULL != pa);
    223   GNUNET_break (0 ==
    224                 json_array_append_new (pa,
    225                                        pe));
    226   GNUNET_assert (
    227     GNUNET_OK ==
    228     GNUNET_CONTAINER_multihashmap_put (
    229       pd->dd_map,
    230       recdoc_id,
    231       pa,
    232       GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
    233   pd->cb (pd->cb_cls,
    234           recdoc_id,
    235           po->provider_url,
    236           version,
    237           (json_int_t) po->attribute_mask.mask,
    238           server_time,
    239           secret_name,
    240           pa);
    241 }
    242 
    243 
    244 /**
    245  * Start policy operation for @a pd using identity @a id_data
    246  * at provider @a provider_url.
    247  *
    248  * @param pd policy discovery operation
    249  * @param id_data our identity data, derived using @a mask
    250  * @param mask the mask describing which optional attributes were removed
    251  * @param provider_url which provider to query
    252  * @param cursor cursor telling us from where to query
    253  */
    254 static void
    255 start_po (struct ANASTASIS_PolicyDiscovery *pd,
    256           const json_t *id_data,
    257           struct ANASTASIS_AttributeMask mask,
    258           const char *provider_url,
    259           const json_t *cursor)
    260 {
    261   struct ProviderOperation *po;
    262   uint32_t max_version = UINT32_MAX;
    263   struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt;
    264 
    265   if (NULL != cursor)
    266   {
    267     size_t i;
    268     json_t *obj;
    269 
    270     json_array_foreach ((json_t *) cursor, i, obj)
    271     {
    272       const char *url;
    273       uint64_t cmask;
    274       uint32_t mv;
    275       struct GNUNET_JSON_Specification spec[] = {
    276         GNUNET_JSON_spec_string ("provider_url",
    277                                  &url),
    278         GNUNET_JSON_spec_uint64 ("mask",
    279                                  &cmask),
    280         GNUNET_JSON_spec_uint32 ("max_version",
    281                                  &mv),
    282         GNUNET_JSON_spec_end ()
    283       };
    284 
    285       if (GNUNET_OK !=
    286           GNUNET_JSON_parse (obj,
    287                              spec,
    288                              NULL, NULL))
    289       {
    290         /* cursor invalid */
    291         GNUNET_break (0);
    292         json_dumpf (obj,
    293                     stderr,
    294                     JSON_INDENT (2));
    295         return;
    296       }
    297       if ( (cmask == mask.mask) &&
    298            (0 == strcmp (url,
    299                          provider_url)) )
    300       {
    301         max_version = mv;
    302         break;
    303       }
    304     }
    305   }
    306 
    307   if (GNUNET_OK !=
    308       ANASTASIS_REDUX_lookup_salt_ (&pd->rs->common,
    309                                     provider_url,
    310                                     &provider_salt))
    311   {
    312     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    313                 "No /config for `%s', skipping provider\n",
    314                 provider_url);
    315     return;
    316   }
    317   po = GNUNET_new (struct ProviderOperation);
    318   po->pd = pd;
    319   po->attribute_mask = mask;
    320   po->provider_url = GNUNET_strdup (provider_url);
    321   po->vc = ANASTASIS_recovery_get_versions (ANASTASIS_REDUX_ctx_,
    322                                             id_data,
    323                                             max_version,
    324                                             provider_url,
    325                                             &provider_salt,
    326                                             &meta_cb,
    327                                             po);
    328   if (NULL == po->vc)
    329   {
    330     GNUNET_free (po);
    331   }
    332   else
    333   {
    334     GNUNET_CONTAINER_DLL_insert (pd->po_head,
    335                                  pd->po_tail,
    336                                  po);
    337   }
    338 }
    339 
    340 
    341 struct ANASTASIS_PolicyDiscovery *
    342 ANASTASIS_policy_discovery_start (const json_t *state,
    343                                   const json_t *cursor,
    344                                   ANASTASIS_PolicyDiscoveryCallback cb,
    345                                   void *cb_cls)
    346 {
    347   struct ANASTASIS_PolicyDiscovery *pd;
    348   struct ANASTASIS_ReduxState *rs;
    349   const json_t *master_id;
    350   unsigned int opt_cnt;
    351 
    352   {
    353     enum TALER_ErrorCode ec;
    354     const char *detail;
    355 
    356     rs = ANASTASIS_REDUX_state_parse_ (state,
    357                                        &ec,
    358                                        &detail);
    359     if (NULL == rs)
    360     {
    361       GNUNET_break (0);
    362       json_dumpf ((json_t *) state,
    363                   stderr,
    364                   JSON_INDENT (2));
    365       return NULL;
    366     }
    367   }
    368   master_id = rs->common.identity_attributes;
    369   if ( (NULL == master_id) ||
    370        (! rs->common.have_providers) ||
    371        (! rs->common.have_required_attributes) )
    372   {
    373     GNUNET_break (0);
    374     json_dumpf ((json_t *) state,
    375                 stderr,
    376                 JSON_INDENT (2));
    377     ANASTASIS_REDUX_state_free_ (rs);
    378     return NULL;
    379   }
    380 
    381   /* count optional attributes present in 'master_id' */
    382   opt_cnt = 0;
    383   for (unsigned int i = 0; i < rs->common.required_attributes_len; i++)
    384   {
    385     const struct ANASTASIS_ReduxAttributeSpec *a
    386       = &rs->common.required_attributes[i];
    387     bool present;
    388 
    389     present = (NULL !=
    390                json_object_get (master_id,
    391                                 a->name));
    392     if ((! present) && (! a->optional))
    393     {
    394       GNUNET_break (0);
    395       json_dumpf ((json_t *) master_id,
    396                   stderr,
    397                   JSON_INDENT (2));
    398       ANASTASIS_REDUX_state_free_ (rs);
    399       return NULL;
    400     }
    401     if (present && a->optional)
    402       opt_cnt++;
    403   }
    404   /* `opt_cnt` drives the 2^opt_cnt mask loop below; both it and
    405      `identity_attributes` come from the untrusted `state`, so a crafted
    406      state with ~40 optional attributes would queue ~10^12 provider
    407      downloads.  Fail closed instead. */
    408   if (opt_cnt > MAX_OPTIONAL_ATTRIBUTES)
    409   {
    410     GNUNET_break_op (0);
    411     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    412                 "Refusing policy discovery over %u optional attributes (max %u)\n",
    413                 opt_cnt,
    414                 (unsigned int) MAX_OPTIONAL_ATTRIBUTES);
    415     ANASTASIS_REDUX_state_free_ (rs);
    416     return NULL;
    417   }
    418 
    419   pd = GNUNET_new (struct ANASTASIS_PolicyDiscovery);
    420   pd->dd_map = GNUNET_CONTAINER_multihashmap_create (128,
    421                                                      GNUNET_NO);
    422   pd->cb = cb;
    423   pd->cb_cls = cb_cls;
    424   pd->opt_cnt = opt_cnt;
    425   pd->rs = rs;
    426 
    427   /* Compute 'id_data' for all possible masks, and then
    428      start downloads at all providers for 'id_data' */
    429   for (uint64_t m = 0; m < (1LLU << opt_cnt); m++)
    430   {
    431     struct ANASTASIS_AttributeMask mask = {
    432       .mask = m
    433     };
    434     json_t *id_data = mask_id_data (&rs->common,
    435                                     master_id,
    436                                     mask);
    437 
    438     for (unsigned int i = 0; i < rs->common.providers_len; i++)
    439       start_po (pd,
    440                 id_data,
    441                 mask,
    442                 rs->common.providers[i].url.url,
    443                 cursor);
    444     json_decref (id_data);
    445   }
    446   return pd;
    447 }
    448 
    449 
    450 void
    451 ANASTASIS_policy_discovery_more (struct ANASTASIS_PolicyDiscovery *pd,
    452                                  const char *provider_url,
    453                                  json_t *provider_state)
    454 {
    455   const json_t *master_id = pd->rs->common.identity_attributes;
    456   const char *detail;
    457 
    458   GNUNET_assert (NULL != master_id);
    459   if (GNUNET_OK !=
    460       ANASTASIS_REDUX_provider_set_ (&pd->rs->common,
    461                                      provider_url,
    462                                      provider_state,
    463                                      &detail))
    464   {
    465     GNUNET_break_op (0);
    466     return;
    467   }
    468   /* Compute 'id_data' for all possible masks, and then
    469      start downloads at provider for 'id_data' */
    470   for (uint64_t m = 0; m < (1LLU << pd->opt_cnt); m++)
    471   {
    472     struct ANASTASIS_AttributeMask mask = {
    473       .mask = m
    474     };
    475     json_t *id_data = mask_id_data (&pd->rs->common,
    476                                     master_id,
    477                                     mask);
    478 
    479     start_po (pd,
    480               id_data,
    481               mask,
    482               provider_url,
    483               NULL);
    484     json_decref (id_data);
    485   }
    486 }
    487 
    488 
    489 /**
    490  * Free JSON Arrays from our hash map.
    491  *
    492  * @param cls NULL
    493  * @param key ignored
    494  * @param value `json_t *` to free
    495  * @return #GNUNET_OK
    496  */
    497 static enum GNUNET_GenericReturnValue
    498 free_dd_json (void *cls,
    499               const struct GNUNET_HashCode *key,
    500               void *value)
    501 {
    502   json_t *j = value;
    503 
    504   (void) cls;
    505   (void) key;
    506   json_decref (j);
    507   return GNUNET_OK;
    508 }
    509 
    510 
    511 void
    512 ANASTASIS_policy_discovery_stop (struct ANASTASIS_PolicyDiscovery *pd)
    513 {
    514   struct ProviderOperation *po;
    515 
    516   while (NULL != (po = pd->po_head))
    517   {
    518     GNUNET_CONTAINER_DLL_remove (pd->po_head,
    519                                  pd->po_tail,
    520                                  po);
    521     ANASTASIS_recovery_get_versions_cancel (po->vc);
    522     GNUNET_free (po->provider_url);
    523     GNUNET_free (po);
    524   }
    525   GNUNET_CONTAINER_multihashmap_iterate (pd->dd_map,
    526                                          &free_dd_json,
    527                                          NULL);
    528   GNUNET_CONTAINER_multihashmap_destroy (pd->dd_map);
    529   ANASTASIS_REDUX_state_free_ (pd->rs);
    530   GNUNET_free (pd);
    531 }
    532 
    533 
    534 json_t *
    535 ANASTASIS_mask_id_data (const json_t *state,
    536                         const json_t *master_id,
    537                         json_int_t mask)
    538 {
    539   struct ANASTASIS_ReduxState *rs;
    540   struct ANASTASIS_AttributeMask m = {
    541     .mask = (uint64_t) mask
    542   };
    543   json_t *ret;
    544 
    545   {
    546     enum TALER_ErrorCode ec;
    547     const char *detail;
    548 
    549     rs = ANASTASIS_REDUX_state_parse_ (state,
    550                                        &ec,
    551                                        &detail);
    552     if (NULL == rs)
    553     {
    554       GNUNET_break (0);
    555       return NULL;
    556     }
    557   }
    558   if (! rs->common.have_required_attributes)
    559   {
    560     GNUNET_break (0);
    561     ANASTASIS_REDUX_state_free_ (rs);
    562     return NULL;
    563   }
    564   ret = mask_id_data (&rs->common,
    565                       master_id,
    566                       m);
    567   ANASTASIS_REDUX_state_free_ (rs);
    568   return ret;
    569 }