anastasis_api_discovery.c (15421B)
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 "anastasis_api_redux_state.h" 28 29 30 /** 31 * Maximum number of *optional* identity attributes we are willing to 32 * enumerate subsets of in #ANASTASIS_policy_discovery_start(). The mask 33 * loop there is 2^n, so this bounds the work at 65536 combinations; it also 34 * keeps `1LLU << opt_cnt` well-defined (it is undefined for n >= 64) and the 35 * 64-bit loop counter from wrapping. 36 */ 37 #define MAX_OPTIONAL_ATTRIBUTES 16 38 39 40 /** 41 * Handle for one request we are doing at a specific provider. 42 */ 43 struct ProviderOperation 44 { 45 /** 46 * Kept in a DLL. 47 */ 48 struct ProviderOperation *next; 49 50 /** 51 * Kept in a DLL. 52 */ 53 struct ProviderOperation *prev; 54 55 /** 56 * Base URL of the provider. 57 */ 58 char *provider_url; 59 60 /** 61 * Handle to the version check operation we are performing. 62 */ 63 struct ANASTASIS_VersionCheck *vc; 64 65 /** 66 * Handle discovery operation we this is a part of. 67 */ 68 struct ANASTASIS_PolicyDiscovery *pd; 69 70 /** 71 * Attribute mask applied to the identity attributes 72 * for this operation. 73 */ 74 struct ANASTASIS_AttributeMask attribute_mask; 75 }; 76 77 78 /** 79 * Handle for a discovery operation. 80 */ 81 struct ANASTASIS_PolicyDiscovery 82 { 83 /** 84 * Head of HTTP requests, kept in a DLL. 85 */ 86 struct ProviderOperation *po_head; 87 88 /** 89 * Tail of HTTP requests, kept in a DLL. 90 */ 91 struct ProviderOperation *po_tail; 92 93 /** 94 * Function to call with results. 95 */ 96 ANASTASIS_PolicyDiscoveryCallback cb; 97 98 /** 99 * Closure for @e cb. 100 */ 101 void *cb_cls; 102 103 /** 104 * Map for duplicate detection, maps hashes of policies we 105 * have already seen to a json_array with all providers 106 * and versions corresponding to this policy hash. 107 */ 108 struct GNUNET_CONTAINER_MultiHashMap *dd_map; 109 110 /** 111 * State we are operating on. 112 */ 113 struct ANASTASIS_ReduxState *rs; 114 115 /** 116 * Number of optional fields in our identity attributes. 117 */ 118 unsigned int opt_cnt; 119 }; 120 121 122 /** 123 * Return a copy of @a master_id with the optional attributes selected 124 * by @a mask removed. The internal, typed counterpart of 125 * #ANASTASIS_mask_id_data(). 126 * 127 * @param common state describing which attributes exist 128 * @param master_id the user's identity attributes 129 * @param mask which of the optional attributes to drop 130 * @return NULL if @a master_id does not match @a common 131 */ 132 static json_t * 133 mask_id_data (const struct ANASTASIS_ReduxCommon *common, 134 const json_t *master_id, 135 struct ANASTASIS_AttributeMask mask) 136 { 137 json_t *ret = json_deep_copy (master_id); 138 unsigned int bit = 0; 139 140 GNUNET_assert (NULL != ret); 141 for (unsigned int i = 0; i < common->required_attributes_len; i++) 142 { 143 const struct ANASTASIS_ReduxAttributeSpec *a 144 = &common->required_attributes[i]; 145 bool present; 146 147 present = (NULL != 148 json_object_get (master_id, 149 a->name)); 150 if ( (! present) && (! a->optional) ) 151 { 152 GNUNET_break (0); 153 json_decref (ret); 154 return NULL; 155 } 156 if (present && a->optional) 157 { 158 if (0 != ((1LLU << bit) & mask.mask)) 159 { 160 GNUNET_assert (0 == 161 json_object_del (ret, 162 a->name)); 163 } 164 bit++; 165 } 166 } 167 return ret; 168 } 169 170 171 /** 172 * Callback which passes back meta data about one of the 173 * recovery documents available at the provider. 174 * 175 * @param cls our `struct ProviderOperation *` 176 * @param version version number of the policy document, 177 * 0 for the end of the list 178 * @param server_time time of the backup at the provider 179 * @param recdoc_id hash of the compressed recovery document, uniquely 180 * identifies the document; NULL for the end of the list 181 * @param secret_name name of the secret as chosen by the user, 182 * or NULL if the user did not provide a name 183 */ 184 static void 185 meta_cb (void *cls, 186 uint32_t version, 187 struct GNUNET_TIME_Timestamp server_time, 188 const struct GNUNET_HashCode *recdoc_id, 189 const char *secret_name) 190 { 191 struct ProviderOperation *po = cls; 192 struct ANASTASIS_PolicyDiscovery *pd = po->pd; 193 json_t *pa; 194 json_t *pe; 195 196 if (NULL == recdoc_id) 197 { 198 po->vc = NULL; 199 GNUNET_CONTAINER_DLL_remove (pd->po_head, 200 pd->po_tail, 201 po); 202 GNUNET_free (po->provider_url); 203 GNUNET_free (po); 204 return; 205 } 206 pe = GNUNET_JSON_PACK ( 207 GNUNET_JSON_pack_uint64 ("version", 208 version), 209 GNUNET_JSON_pack_string ("url", 210 po->provider_url)); 211 212 pa = GNUNET_CONTAINER_multihashmap_get (pd->dd_map, 213 recdoc_id); 214 if (NULL != pa) 215 { 216 GNUNET_break (0 == 217 json_array_append_new (pa, 218 pe)); 219 return; 220 } 221 pa = json_array (); 222 GNUNET_assert (NULL != pa); 223 GNUNET_break (0 == 224 json_array_append_new (pa, 225 pe)); 226 GNUNET_assert ( 227 GNUNET_OK == 228 GNUNET_CONTAINER_multihashmap_put ( 229 pd->dd_map, 230 recdoc_id, 231 pa, 232 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); 233 pd->cb (pd->cb_cls, 234 recdoc_id, 235 po->provider_url, 236 version, 237 (json_int_t) po->attribute_mask.mask, 238 server_time, 239 secret_name, 240 pa); 241 } 242 243 244 /** 245 * Start policy operation for @a pd using identity @a id_data 246 * at provider @a provider_url. 247 * 248 * @param pd policy discovery operation 249 * @param id_data our identity data, derived using @a mask 250 * @param mask the mask describing which optional attributes were removed 251 * @param provider_url which provider to query 252 * @param cursor cursor telling us from where to query 253 */ 254 static void 255 start_po (struct ANASTASIS_PolicyDiscovery *pd, 256 const json_t *id_data, 257 struct ANASTASIS_AttributeMask mask, 258 const char *provider_url, 259 const json_t *cursor) 260 { 261 struct ProviderOperation *po; 262 uint32_t max_version = UINT32_MAX; 263 struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt; 264 265 if (NULL != cursor) 266 { 267 size_t i; 268 json_t *obj; 269 270 json_array_foreach ((json_t *) cursor, i, obj) 271 { 272 const char *url; 273 uint64_t cmask; 274 uint32_t mv; 275 struct GNUNET_JSON_Specification spec[] = { 276 GNUNET_JSON_spec_string ("provider_url", 277 &url), 278 GNUNET_JSON_spec_uint64 ("mask", 279 &cmask), 280 GNUNET_JSON_spec_uint32 ("max_version", 281 &mv), 282 GNUNET_JSON_spec_end () 283 }; 284 285 if (GNUNET_OK != 286 GNUNET_JSON_parse (obj, 287 spec, 288 NULL, NULL)) 289 { 290 /* cursor invalid */ 291 GNUNET_break (0); 292 json_dumpf (obj, 293 stderr, 294 JSON_INDENT (2)); 295 return; 296 } 297 if ( (cmask == mask.mask) && 298 (0 == strcmp (url, 299 provider_url)) ) 300 { 301 max_version = mv; 302 break; 303 } 304 } 305 } 306 307 if (GNUNET_OK != 308 ANASTASIS_REDUX_lookup_salt_ (&pd->rs->common, 309 provider_url, 310 &provider_salt)) 311 { 312 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 313 "No /config for `%s', skipping provider\n", 314 provider_url); 315 return; 316 } 317 po = GNUNET_new (struct ProviderOperation); 318 po->pd = pd; 319 po->attribute_mask = mask; 320 po->provider_url = GNUNET_strdup (provider_url); 321 po->vc = ANASTASIS_recovery_get_versions (ANASTASIS_REDUX_ctx_, 322 id_data, 323 max_version, 324 provider_url, 325 &provider_salt, 326 &meta_cb, 327 po); 328 if (NULL == po->vc) 329 { 330 GNUNET_free (po); 331 } 332 else 333 { 334 GNUNET_CONTAINER_DLL_insert (pd->po_head, 335 pd->po_tail, 336 po); 337 } 338 } 339 340 341 struct ANASTASIS_PolicyDiscovery * 342 ANASTASIS_policy_discovery_start (const json_t *state, 343 const json_t *cursor, 344 ANASTASIS_PolicyDiscoveryCallback cb, 345 void *cb_cls) 346 { 347 struct ANASTASIS_PolicyDiscovery *pd; 348 struct ANASTASIS_ReduxState *rs; 349 const json_t *master_id; 350 unsigned int opt_cnt; 351 352 { 353 enum TALER_ErrorCode ec; 354 const char *detail; 355 356 rs = ANASTASIS_REDUX_state_parse_ (state, 357 &ec, 358 &detail); 359 if (NULL == rs) 360 { 361 GNUNET_break (0); 362 json_dumpf ((json_t *) state, 363 stderr, 364 JSON_INDENT (2)); 365 return NULL; 366 } 367 } 368 master_id = rs->common.identity_attributes; 369 if ( (NULL == master_id) || 370 (! rs->common.have_providers) || 371 (! rs->common.have_required_attributes) ) 372 { 373 GNUNET_break (0); 374 json_dumpf ((json_t *) state, 375 stderr, 376 JSON_INDENT (2)); 377 ANASTASIS_REDUX_state_free_ (rs); 378 return NULL; 379 } 380 381 /* count optional attributes present in 'master_id' */ 382 opt_cnt = 0; 383 for (unsigned int i = 0; i < rs->common.required_attributes_len; i++) 384 { 385 const struct ANASTASIS_ReduxAttributeSpec *a 386 = &rs->common.required_attributes[i]; 387 bool present; 388 389 present = (NULL != 390 json_object_get (master_id, 391 a->name)); 392 if ((! present) && (! a->optional)) 393 { 394 GNUNET_break (0); 395 json_dumpf ((json_t *) master_id, 396 stderr, 397 JSON_INDENT (2)); 398 ANASTASIS_REDUX_state_free_ (rs); 399 return NULL; 400 } 401 if (present && a->optional) 402 opt_cnt++; 403 } 404 /* `opt_cnt` drives the 2^opt_cnt mask loop below; both it and 405 `identity_attributes` come from the untrusted `state`, so a crafted 406 state with ~40 optional attributes would queue ~10^12 provider 407 downloads. Fail closed instead. */ 408 if (opt_cnt > MAX_OPTIONAL_ATTRIBUTES) 409 { 410 GNUNET_break_op (0); 411 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 412 "Refusing policy discovery over %u optional attributes (max %u)\n", 413 opt_cnt, 414 (unsigned int) MAX_OPTIONAL_ATTRIBUTES); 415 ANASTASIS_REDUX_state_free_ (rs); 416 return NULL; 417 } 418 419 pd = GNUNET_new (struct ANASTASIS_PolicyDiscovery); 420 pd->dd_map = GNUNET_CONTAINER_multihashmap_create (128, 421 GNUNET_NO); 422 pd->cb = cb; 423 pd->cb_cls = cb_cls; 424 pd->opt_cnt = opt_cnt; 425 pd->rs = rs; 426 427 /* Compute 'id_data' for all possible masks, and then 428 start downloads at all providers for 'id_data' */ 429 for (uint64_t m = 0; m < (1LLU << opt_cnt); m++) 430 { 431 struct ANASTASIS_AttributeMask mask = { 432 .mask = m 433 }; 434 json_t *id_data = mask_id_data (&rs->common, 435 master_id, 436 mask); 437 438 for (unsigned int i = 0; i < rs->common.providers_len; i++) 439 start_po (pd, 440 id_data, 441 mask, 442 rs->common.providers[i].url.url, 443 cursor); 444 json_decref (id_data); 445 } 446 return pd; 447 } 448 449 450 void 451 ANASTASIS_policy_discovery_more (struct ANASTASIS_PolicyDiscovery *pd, 452 const char *provider_url, 453 json_t *provider_state) 454 { 455 const json_t *master_id = pd->rs->common.identity_attributes; 456 const char *detail; 457 458 GNUNET_assert (NULL != master_id); 459 if (GNUNET_OK != 460 ANASTASIS_REDUX_provider_set_ (&pd->rs->common, 461 provider_url, 462 provider_state, 463 &detail)) 464 { 465 GNUNET_break_op (0); 466 return; 467 } 468 /* Compute 'id_data' for all possible masks, and then 469 start downloads at provider for 'id_data' */ 470 for (uint64_t m = 0; m < (1LLU << pd->opt_cnt); m++) 471 { 472 struct ANASTASIS_AttributeMask mask = { 473 .mask = m 474 }; 475 json_t *id_data = mask_id_data (&pd->rs->common, 476 master_id, 477 mask); 478 479 start_po (pd, 480 id_data, 481 mask, 482 provider_url, 483 NULL); 484 json_decref (id_data); 485 } 486 } 487 488 489 /** 490 * Free JSON Arrays from our hash map. 491 * 492 * @param cls NULL 493 * @param key ignored 494 * @param value `json_t *` to free 495 * @return #GNUNET_OK 496 */ 497 static enum GNUNET_GenericReturnValue 498 free_dd_json (void *cls, 499 const struct GNUNET_HashCode *key, 500 void *value) 501 { 502 json_t *j = value; 503 504 (void) cls; 505 (void) key; 506 json_decref (j); 507 return GNUNET_OK; 508 } 509 510 511 void 512 ANASTASIS_policy_discovery_stop (struct ANASTASIS_PolicyDiscovery *pd) 513 { 514 struct ProviderOperation *po; 515 516 while (NULL != (po = pd->po_head)) 517 { 518 GNUNET_CONTAINER_DLL_remove (pd->po_head, 519 pd->po_tail, 520 po); 521 ANASTASIS_recovery_get_versions_cancel (po->vc); 522 GNUNET_free (po->provider_url); 523 GNUNET_free (po); 524 } 525 GNUNET_CONTAINER_multihashmap_iterate (pd->dd_map, 526 &free_dd_json, 527 NULL); 528 GNUNET_CONTAINER_multihashmap_destroy (pd->dd_map); 529 ANASTASIS_REDUX_state_free_ (pd->rs); 530 GNUNET_free (pd); 531 } 532 533 534 json_t * 535 ANASTASIS_mask_id_data (const json_t *state, 536 const json_t *master_id, 537 json_int_t mask) 538 { 539 struct ANASTASIS_ReduxState *rs; 540 struct ANASTASIS_AttributeMask m = { 541 .mask = (uint64_t) mask 542 }; 543 json_t *ret; 544 545 { 546 enum TALER_ErrorCode ec; 547 const char *detail; 548 549 rs = ANASTASIS_REDUX_state_parse_ (state, 550 &ec, 551 &detail); 552 if (NULL == rs) 553 { 554 GNUNET_break (0); 555 return NULL; 556 } 557 } 558 if (! rs->common.have_required_attributes) 559 { 560 GNUNET_break (0); 561 ANASTASIS_REDUX_state_free_ (rs); 562 return NULL; 563 } 564 ret = mask_id_data (&rs->common, 565 master_id, 566 m); 567 ANASTASIS_REDUX_state_free_ (rs); 568 return ret; 569 }