anastasis

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

anastasis_api_discovery.c (14760B)


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