diff options
Diffstat (limited to 'src/reducer/anastasis_api_discovery.c')
-rw-r--r-- | src/reducer/anastasis_api_discovery.c | 549 |
1 files changed, 549 insertions, 0 deletions
diff --git a/src/reducer/anastasis_api_discovery.c b/src/reducer/anastasis_api_discovery.c new file mode 100644 index 0000000..3470d97 --- /dev/null +++ b/src/reducer/anastasis_api_discovery.c @@ -0,0 +1,549 @@ +/* + This file is part of Anastasis + Copyright (C) 2022 Anastasis SARL + + Anastasis is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file reducer/anastasis_api_discovery.c + * @brief anastasis recovery policy discovery api + * @author Christian Grothoff + */ +#include <platform.h> +#include <jansson.h> +#include "anastasis_redux.h" +#include "anastasis_error_codes.h" +#include <taler/taler_json_lib.h> +#include "anastasis_api_redux.h" +#include <dlfcn.h> + + +/** + * Handle for one request we are doing at a specific provider. + */ +struct ProviderOperation +{ + /** + * Kept in a DLL. + */ + struct ProviderOperation *next; + + /** + * Kept in a DLL. + */ + struct ProviderOperation *prev; + + /** + * Base URL of the provider. + */ + char *provider_url; + + /** + * Handle to the version check operation we are performing. + */ + struct ANASTASIS_VersionCheck *vc; + + /** + * Handle discovery operation we this is a part of. + */ + struct ANASTASIS_PolicyDiscovery *pd; + + /** + * Attribute mask applied to the identity attributes + * for this operation. + */ + json_int_t attribute_mask; +}; + + +/** + * Handle for a discovery operation. + */ +struct ANASTASIS_PolicyDiscovery +{ + /** + * Head of HTTP requests, kept in a DLL. + */ + struct ProviderOperation *po_head; + + /** + * Tail of HTTP requests, kept in a DLL. + */ + struct ProviderOperation *po_tail; + + /** + * Function to call with results. + */ + ANASTASIS_PolicyDiscoveryCallback cb; + + /** + * Closure for @e cb. + */ + void *cb_cls; + + /** + * Map for duplicate detection, maps hashes of policies we + * have already seen to a json_array with all providers + * and versions corresponding to this policy hash. + */ + struct GNUNET_CONTAINER_MultiHashMap *dd_map; + + /** + * State we are operating on. + */ + json_t *state; + + /** + * Number of optional fields in our identity attributes. + */ + json_int_t opt_cnt; +}; + + +/** + * Callback which passes back meta data about one of the + * recovery documents available at the provider. + * + * @param cls our `struct ProviderOperation *` + * @param version version number of the policy document, + * 0 for the end of the list + * @param server_time time of the backup at the provider + * @param recdoc_id hash of the compressed recovery document, uniquely + * identifies the document; NULL for the end of the list + * @param secret_name name of the secret as chosen by the user, + * or NULL if the user did not provide a name + */ +static void +meta_cb (void *cls, + uint32_t version, + struct GNUNET_TIME_Timestamp server_time, + const struct GNUNET_HashCode *recdoc_id, + const char *secret_name) +{ + struct ProviderOperation *po = cls; + struct ANASTASIS_PolicyDiscovery *pd = po->pd; + json_t *pa; + json_t *pe; + + if (NULL == recdoc_id) + { + po->vc = NULL; + GNUNET_CONTAINER_DLL_remove (pd->po_head, + pd->po_tail, + po); + GNUNET_free (po->provider_url); + GNUNET_free (po); + return; + } + pe = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_uint64 ("version", + version), + GNUNET_JSON_pack_string ("url", + po->provider_url)); + + pa = GNUNET_CONTAINER_multihashmap_get (pd->dd_map, + recdoc_id); + if (NULL != pa) + { + GNUNET_break (0 == + json_array_append_new (pa, + pe)); + return; + } + pa = json_array (); + GNUNET_assert (NULL != pa); + GNUNET_break (0 == + json_array_append_new (pa, + pe)); + GNUNET_assert ( + GNUNET_OK == + GNUNET_CONTAINER_multihashmap_put ( + pd->dd_map, + recdoc_id, + pa, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); + pd->cb (pd->cb_cls, + recdoc_id, + po->provider_url, + version, + po->attribute_mask, + server_time, + secret_name, + pa); +} + + +/** + * Start policy operation for @a pd using identity @a id_data + * at provider @a provider_url. + * + * @param pd policy discovery operation + * @param id_data our identity data, derived using @a mask + * @param mask the mask describing which optional attributes were removed + * @param provider_url which provider to query + * @param cursor cursor telling us from where to query + */ +static void +start_po (struct ANASTASIS_PolicyDiscovery *pd, + const json_t *id_data, + json_int_t mask, + const char *provider_url, + const json_t *cursor) +{ + const json_t *state = pd->state; + struct ProviderOperation *po; + uint32_t max_version = UINT32_MAX; + struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt; + + if (NULL != cursor) + { + size_t i; + json_t *obj; + + json_array_foreach ((json_t *) cursor, i, obj) + { + const char *url; + uint64_t cmask; + uint32_t mv; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("provider_url", + &url), + GNUNET_JSON_spec_uint64 ("mask", + &cmask), + GNUNET_JSON_spec_uint32 ("max_version", + &mv), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (obj, + spec, + NULL, NULL)) + { + /* cursor invalid */ + GNUNET_break (0); + json_dumpf (obj, + stderr, + JSON_INDENT (2)); + return; + } + if ( (cmask == mask) && + (0 == strcmp (url, + provider_url)) ) + { + max_version = mv; + break; + } + } + } + + if (GNUNET_OK != + ANASTASIS_reducer_lookup_salt (state, + provider_url, + &provider_salt)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "No /config for `%s', skipping provider\n", + provider_url); + return; + } + po = GNUNET_new (struct ProviderOperation); + po->pd = pd; + po->attribute_mask = mask; + po->provider_url = GNUNET_strdup (provider_url); + po->vc = ANASTASIS_recovery_get_versions (ANASTASIS_REDUX_ctx_, + id_data, + max_version, + provider_url, + &provider_salt, + &meta_cb, + po); + if (NULL == po->vc) + { + GNUNET_free (po); + } + else + { + GNUNET_CONTAINER_DLL_insert (pd->po_head, + pd->po_tail, + po); + } +} + + +struct ANASTASIS_PolicyDiscovery * +ANASTASIS_policy_discovery_start (const json_t *state, + const json_t *cursor, + ANASTASIS_PolicyDiscoveryCallback cb, + void *cb_cls) +{ + struct ANASTASIS_PolicyDiscovery *pd; + json_t *master_id = json_object_get (state, + "identity_attributes"); + json_t *providers = json_object_get (state, + "authentication_providers"); + json_t *required_attributes = json_object_get (state, + "required_attributes"); + unsigned int opt_cnt; + + if ( (NULL == master_id) || + (! json_is_object (master_id)) ) + { + GNUNET_break (0); + json_dumpf (state, + stderr, + JSON_INDENT (2)); + return NULL; + } + if ( (NULL == providers) || + (! json_is_object (providers)) ) + { + GNUNET_break (0); + json_dumpf (state, + stderr, + JSON_INDENT (2)); + return NULL; + } + if ( (NULL == required_attributes) || + (! json_is_array (required_attributes)) ) + { + GNUNET_break (0); + json_dumpf (required_attributes, + stderr, + JSON_INDENT (2)); + return NULL; + } + + /* count optional attributes present in 'master_id' */ + opt_cnt = 0; + { + size_t index; + json_t *required_attribute; + + json_array_foreach (required_attributes, + index, + required_attribute) + { + const char *name; + int optional = false; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("name", + &name), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_boolean ("optional", + &optional), + NULL), + GNUNET_JSON_spec_end () + }; + bool present; + + if (GNUNET_OK != + GNUNET_JSON_parse (required_attribute, + spec, + NULL, NULL)) + { + GNUNET_break (0); + json_dumpf (required_attribute, + stderr, + JSON_INDENT (2)); + return NULL; + } + present = (NULL != + json_object_get (master_id, + name)); + if ((! present) && (! optional)) + { + GNUNET_break (0); + json_dumpf (master_id, + stderr, + JSON_INDENT (2)); + return NULL; + } + if (present && optional) + opt_cnt++; + } + } + + pd = GNUNET_new (struct ANASTASIS_PolicyDiscovery); + pd->dd_map = GNUNET_CONTAINER_multihashmap_create (128, + GNUNET_NO); + pd->cb = cb; + pd->cb_cls = cb_cls; + pd->opt_cnt = opt_cnt; + pd->state = json_deep_copy (state); + + /* Compute 'id_data' for all possible masks, and then + start downloads at all providers for 'id_data' */ + for (json_int_t mask = 0; mask < (1LL << opt_cnt); mask++) + { + json_t *id_data = ANASTASIS_mask_id_data (state, + master_id, + mask); + json_t *value; + const char *url; + + json_object_foreach (providers, url, value) + { + start_po (pd, + id_data, + mask, + url, + cursor); + } + json_decref (id_data); + } + return pd; +} + + +void +ANASTASIS_policy_discovery_more (struct ANASTASIS_PolicyDiscovery *pd, + const char *provider_url, + json_t *provider_state) +{ + json_t *master_id = json_object_get (pd->state, + "identity_attributes"); + json_t *providers = json_object_get (pd->state, + "authentication_providers"); + + GNUNET_assert (NULL != master_id); + GNUNET_assert (NULL != providers); + GNUNET_assert (0 == + json_object_set (providers, + provider_url, + provider_state)); + /* Compute 'id_data' for all possible masks, and then + start downloads at provider for 'id_data' */ + for (json_int_t mask = 0; mask < (1LL << pd->opt_cnt); mask++) + { + json_t *id_data = ANASTASIS_mask_id_data (pd->state, + master_id, + mask); + + start_po (pd, + id_data, + mask, + provider_url, + NULL); + json_decref (id_data); + } +} + + +/** + * Free JSON Arrays from our hash map. + * + * @param cls NULL + * @param key ignored + * @param value `json_t *` to free + * @return #GNUNET_OK + */ +static enum GNUNET_GenericReturnValue +free_dd_json (void *cls, + const struct GNUNET_HashCode *key, + void *value) +{ + json_t *j = value; + + (void) cls; + (void) key; + json_decref (j); + return GNUNET_OK; +} + + +void +ANASTASIS_policy_discovery_stop (struct ANASTASIS_PolicyDiscovery *pd) +{ + struct ProviderOperation *po; + + while (NULL != (po = pd->po_head)) + { + GNUNET_CONTAINER_DLL_remove (pd->po_head, + pd->po_tail, + po); + ANASTASIS_recovery_get_versions_cancel (po->vc); + GNUNET_free (po->provider_url); + GNUNET_free (po); + } + GNUNET_CONTAINER_multihashmap_iterate (pd->dd_map, + &free_dd_json, + NULL); + GNUNET_CONTAINER_multihashmap_destroy (pd->dd_map); + json_decref (pd->state); + GNUNET_free (pd); +} + + +json_t * +ANASTASIS_mask_id_data (const json_t *state, + const json_t *master_id, + json_int_t mask) +{ + json_t *required_attributes = json_object_get (state, + "required_attributes"); + size_t index; + json_t *required_attribute; + json_t *ret = json_deep_copy (master_id); + unsigned int bit = 0; + + if ( (NULL == required_attributes) || + (! json_is_array (required_attributes)) ) + { + GNUNET_break (0); + return NULL; + } + + json_array_foreach (required_attributes, index, required_attribute) + { + const char *name; + int optional = false; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("name", + &name), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_boolean ("optional", + &optional), + NULL), + GNUNET_JSON_spec_end () + }; + bool present; + + if (GNUNET_OK != + GNUNET_JSON_parse (required_attribute, + spec, + NULL, NULL)) + { + GNUNET_break (0); + return NULL; + } + present = (NULL != + json_object_get (master_id, + name)); + if ((! present) && (! optional)) + { + GNUNET_break (0); + return NULL; + } + if (present && optional) + { + if (0 != ((1LL << bit) & mask)) + { + GNUNET_assert (0 == + json_object_del (ret, + name)); + } + bit++; + } + } + return ret; +} |