/* 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 */ /** * @file reducer/anastasis_api_discovery.c * @brief anastasis recovery policy discovery api * @author Christian Grothoff */ #include #include #include "anastasis_redux.h" #include "anastasis_error_codes.h" #include #include "anastasis_api_redux.h" #include /** * 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; }