/*
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;
}