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 }