summaryrefslogtreecommitdiff
path: root/src/reducer/anastasis_api_discovery.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/reducer/anastasis_api_discovery.c')
-rw-r--r--src/reducer/anastasis_api_discovery.c549
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;
+}