taler-merchant-httpd_token-keys.c (29817B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2024-2026 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Affero General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 TALER 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 Affero General Public License for more details. 12 13 You should have received a copy of the GNU Affero General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file src/backend/taler-merchant-httpd_token-keys.c 18 * @brief shared token family key lookup, lifetime extension and creation 19 * for orders and fountains; also provides fountain key windows 20 * @author Christian Blättler 21 * @author Bohdan Potuzhnyi 22 */ 23 #include "platform.h" 24 #include "taler-merchant-httpd_token-keys.h" 25 #include "merchant-database/insert_token_family_key.h" 26 #include "merchant-database/update_token_family_key_expiration.h" 27 #include "merchant-database/start.h" 28 29 30 /** 31 * Get rounded time interval. @a start is calculated by rounding 32 * @a ts down to the nearest multiple of @a precision. 33 * 34 * @param precision rounding precision. 35 * year, month, day, hour, minute are supported. 36 * @param ts timestamp to round 37 * @param[out] start start of the interval 38 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error 39 */ 40 static enum GNUNET_GenericReturnValue 41 get_rounded_time_interval_down (struct GNUNET_TIME_Relative precision, 42 struct GNUNET_TIME_Timestamp ts, 43 struct GNUNET_TIME_Timestamp *start) 44 { 45 enum GNUNET_TIME_RounderInterval ri; 46 47 ri = GNUNET_TIME_relative_to_round_interval (precision); 48 if ( (GNUNET_TIME_RI_NONE == ri) && 49 (! GNUNET_TIME_relative_is_zero (precision)) ) 50 { 51 *start = ts; 52 return GNUNET_SYSERR; 53 } 54 *start = GNUNET_TIME_absolute_to_timestamp ( 55 GNUNET_TIME_round_down (ts.abs_time, 56 ri)); 57 return GNUNET_OK; 58 } 59 60 61 /** 62 * Get rounded time interval. @a start is calculated by rounding 63 * @a ts up to the nearest multiple of @a precision. 64 * 65 * @param precision rounding precision. 66 * year, month, day, hour, minute are supported. 67 * @param ts timestamp to round 68 * @param[out] start start of the interval 69 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error 70 */ 71 static enum GNUNET_GenericReturnValue 72 get_rounded_time_interval_up (struct GNUNET_TIME_Relative precision, 73 struct GNUNET_TIME_Timestamp ts, 74 struct GNUNET_TIME_Timestamp *start) 75 { 76 enum GNUNET_TIME_RounderInterval ri; 77 78 ri = GNUNET_TIME_relative_to_round_interval (precision); 79 if ( (GNUNET_TIME_RI_NONE == ri) && 80 (! GNUNET_TIME_relative_is_zero (precision)) ) 81 { 82 *start = ts; 83 return GNUNET_SYSERR; 84 } 85 *start = GNUNET_TIME_absolute_to_timestamp ( 86 GNUNET_TIME_round_up (ts.abs_time, 87 ri)); 88 return GNUNET_OK; 89 } 90 91 92 /** 93 * Create fresh key pair based on @a cipher_spec. 94 * 95 * @param cipher_spec which kind of key pair should we generate 96 * @param[out] priv set to new private key 97 * @param[out] pub set to new public key 98 * @return #GNUNET_OK on success 99 */ 100 static enum GNUNET_GenericReturnValue 101 create_key (const char *cipher_spec, 102 struct TALER_TokenIssuePrivateKey *priv, 103 struct TALER_TokenIssuePublicKey *pub) 104 { 105 unsigned int len; 106 char dummy; 107 108 if (0 == strcmp ("cs", 109 cipher_spec)) 110 { 111 GNUNET_CRYPTO_blind_sign_keys_create ( 112 &priv->private_key, 113 &pub->public_key, 114 GNUNET_CRYPTO_BSA_CS); 115 return GNUNET_OK; 116 } 117 if (1 == 118 sscanf (cipher_spec, 119 "rsa(%u)%c", 120 &len, 121 &dummy)) 122 { 123 GNUNET_CRYPTO_blind_sign_keys_create ( 124 &priv->private_key, 125 &pub->public_key, 126 GNUNET_CRYPTO_BSA_RSA, 127 len); 128 return GNUNET_OK; 129 } 130 return GNUNET_SYSERR; 131 } 132 133 134 void 135 TMH_token_key_details_free ( 136 struct TALER_MERCHANTDB_TokenFamilyKeyDetails *key_details) 137 { 138 GNUNET_free (key_details->token_family.slug); 139 GNUNET_free (key_details->token_family.name); 140 GNUNET_free (key_details->token_family.description); 141 json_decref (key_details->token_family.description_i18n); 142 key_details->token_family.description_i18n = NULL; 143 json_decref (key_details->token_family.extra_data); 144 key_details->token_family.extra_data = NULL; 145 GNUNET_free (key_details->token_family.cipher_spec); 146 if (NULL != key_details->pub.public_key) 147 { 148 GNUNET_CRYPTO_blind_sign_pub_decref (key_details->pub.public_key); 149 key_details->pub.public_key = NULL; 150 } 151 if (NULL != key_details->priv.private_key) 152 { 153 GNUNET_CRYPTO_blind_sign_priv_decref (key_details->priv.private_key); 154 key_details->priv.private_key = NULL; 155 } 156 } 157 158 159 /** 160 * Create, store and return a fresh issue key covering @a valid_at 161 * for the token family described by @a key_details. 162 * 163 * @param connection connection to report errors on 164 * @param instance_id instance owning the token family 165 * @param slug slug of the token family 166 * @param valid_at time the new key must cover 167 * @param sign_until how long the private key must remain usable 168 * @param require_coverage if true, leave the key unset when the existing 169 * validity rules cannot cover @a valid_at within the family bounds 170 * @param[in,out] key_details token family details on entry; the new 171 * key and its validity bounds are added on success 172 * @return #GNUNET_OK on success, #GNUNET_NO if an error response was 173 * queued, #GNUNET_SYSERR on hard failure 174 */ 175 static enum GNUNET_GenericReturnValue 176 mint_key (struct MHD_Connection *connection, 177 const char *instance_id, 178 const char *slug, 179 struct GNUNET_TIME_Timestamp valid_at, 180 struct GNUNET_TIME_Timestamp sign_until, 181 bool require_coverage, 182 struct TALER_MERCHANTDB_TokenFamilyKeyDetails *key_details) 183 { 184 struct TALER_MERCHANT_ContractTokenFamilyKey key; 185 enum GNUNET_DB_QueryStatus iqs; 186 struct TALER_TokenIssuePrivateKey token_priv; 187 struct GNUNET_TIME_Timestamp key_expires; 188 struct GNUNET_TIME_Timestamp round_start; 189 190 /* Offset the rounded period once. Subtracting the offset before rounding 191 as well can select an already expired period, even when duration is at 192 least granularity + offset. Existing covering keys are reused by lookup. */ 193 if (GNUNET_OK != 194 get_rounded_time_interval_down ( 195 key_details->token_family.validity_granularity, 196 valid_at, 197 &round_start)) 198 { 199 GNUNET_break (0); 200 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 201 "Unsupported validity granularity interval %s found in database for token family %s!\n", 202 GNUNET_TIME_relative2s ( 203 key_details->token_family.validity_granularity, 204 false), 205 slug); 206 TMH_token_key_details_free (key_details); 207 return (MHD_YES == 208 TALER_MHD_reply_with_error (connection, 209 MHD_HTTP_INTERNAL_SERVER_ERROR, 210 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 211 "get_rounded_time_interval_down failed")) 212 ? GNUNET_NO 213 : GNUNET_SYSERR; 214 } 215 if (GNUNET_TIME_relative_cmp ( 216 key_details->token_family.duration, 217 <, 218 GNUNET_TIME_relative_add ( 219 key_details->token_family.validity_granularity, 220 key_details->token_family.start_offset))) 221 { 222 GNUNET_break (0); 223 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 224 "Inconsistent duration %s found in database for token family %s (below validity granularity plus start_offset)!\n", 225 GNUNET_TIME_relative2s (key_details->token_family.duration, 226 false), 227 slug); 228 TMH_token_key_details_free (key_details); 229 return (MHD_YES == 230 TALER_MHD_reply_with_error (connection, 231 MHD_HTTP_INTERNAL_SERVER_ERROR, 232 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 233 "duration, validity_granularity and start_offset inconsistent for token family")) 234 ? GNUNET_NO 235 : GNUNET_SYSERR; 236 } 237 key.valid_after 238 = GNUNET_TIME_timestamp_max ( 239 GNUNET_TIME_absolute_to_timestamp ( 240 GNUNET_TIME_absolute_subtract ( 241 round_start.abs_time, 242 key_details->token_family.start_offset)), 243 key_details->token_family.valid_after); 244 key.valid_before 245 = GNUNET_TIME_timestamp_min ( 246 GNUNET_TIME_absolute_to_timestamp ( 247 GNUNET_TIME_absolute_add ( 248 key.valid_after.abs_time, 249 key_details->token_family.duration)), 250 key_details->token_family.valid_before); 251 GNUNET_assert (GNUNET_OK == 252 get_rounded_time_interval_down ( 253 key_details->token_family.validity_granularity, 254 key.valid_before, 255 &key_expires)); 256 /* Make sure key never expires before @a sign_until */ 257 key_expires = GNUNET_TIME_timestamp_max ( 258 sign_until, 259 key_expires); 260 if (GNUNET_TIME_timestamp_cmp ( 261 key_expires, 262 ==, 263 round_start)) 264 { 265 /* valid_before does not actually end after the 266 next rounded validity period would start; 267 determine next rounded validity period 268 start point and extend valid_before to cover 269 the full validity period */ 270 GNUNET_assert ( 271 GNUNET_OK == 272 get_rounded_time_interval_up ( 273 key_details->token_family.validity_granularity, 274 key.valid_before, 275 &key_expires)); 276 /* This should basically always end up being key_expires */ 277 key.valid_before = GNUNET_TIME_timestamp_max (key.valid_before, 278 key_expires); 279 } 280 /* Fountains must not advertise a key that fails to advance the 281 coverage chain. Check before generating or storing anything. The 282 order path retains its existing validity semantics. */ 283 if (require_coverage && 284 (GNUNET_TIME_timestamp_cmp (key.valid_after, >, valid_at) || 285 GNUNET_TIME_timestamp_cmp (key.valid_before, <, valid_at) || 286 GNUNET_TIME_timestamp_cmp (key.valid_before, 287 >, 288 key_details->token_family.valid_before))) 289 return GNUNET_OK; 290 if (GNUNET_OK != 291 create_key (key_details->token_family.cipher_spec, 292 &token_priv, 293 &key.pub)) 294 { 295 GNUNET_break (0); 296 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 297 "Unsupported cipher family %s found in database for token family %s!\n", 298 key_details->token_family.cipher_spec, 299 slug); 300 TMH_token_key_details_free (key_details); 301 return (MHD_YES == 302 TALER_MHD_reply_with_error (connection, 303 MHD_HTTP_INTERNAL_SERVER_ERROR, 304 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 305 "invalid cipher stored in local database for token family")) 306 ? GNUNET_NO 307 : GNUNET_SYSERR; 308 } 309 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 310 "Storing new key for slug %s of %s\n", 311 slug, 312 instance_id); 313 iqs = TALER_MERCHANTDB_insert_token_family_key (TMH_db, 314 instance_id, 315 slug, 316 &key.pub, 317 &token_priv, 318 key_expires, 319 key.valid_after, 320 key.valid_before); 321 switch (iqs) 322 { 323 case GNUNET_DB_STATUS_HARD_ERROR: 324 GNUNET_break (0); 325 GNUNET_CRYPTO_blind_sign_priv_decref (token_priv.private_key); 326 GNUNET_CRYPTO_blind_sign_pub_decref (key.pub.public_key); 327 TMH_token_key_details_free (key_details); 328 return (MHD_YES == 329 TALER_MHD_reply_with_error (connection, 330 MHD_HTTP_INTERNAL_SERVER_ERROR, 331 TALER_EC_GENERIC_DB_STORE_FAILED, 332 NULL)) 333 ? GNUNET_NO 334 : GNUNET_SYSERR; 335 case GNUNET_DB_STATUS_SOFT_ERROR: 336 /* Report the conflict without advertising an uncommitted key. */ 337 GNUNET_break (0); 338 GNUNET_CRYPTO_blind_sign_priv_decref (token_priv.private_key); 339 GNUNET_CRYPTO_blind_sign_pub_decref (key.pub.public_key); 340 TMH_token_key_details_free (key_details); 341 return (MHD_YES == 342 TALER_MHD_reply_with_error (connection, 343 MHD_HTTP_INTERNAL_SERVER_ERROR, 344 TALER_EC_GENERIC_DB_SOFT_FAILURE, 345 NULL)) 346 ? GNUNET_NO 347 : GNUNET_SYSERR; 348 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 349 GNUNET_break (0); 350 GNUNET_CRYPTO_blind_sign_priv_decref (token_priv.private_key); 351 GNUNET_CRYPTO_blind_sign_pub_decref (key.pub.public_key); 352 TMH_token_key_details_free (key_details); 353 return (MHD_YES == 354 TALER_MHD_reply_with_error (connection, 355 MHD_HTTP_INTERNAL_SERVER_ERROR, 356 TALER_EC_GENERIC_DB_STORE_FAILED, 357 NULL)) 358 ? GNUNET_NO 359 : GNUNET_SYSERR; 360 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 361 break; 362 } 363 key_details->pub = key.pub; 364 key_details->priv = token_priv; 365 key_details->signature_validity_start = key.valid_after; 366 key_details->signature_validity_end = key.valid_before; 367 key_details->private_key_deleted_at = key_expires; 368 return GNUNET_OK; 369 } 370 371 372 enum GNUNET_GenericReturnValue 373 TMH_token_key_extend ( 374 struct MHD_Connection *connection, 375 const char *instance_id, 376 const char *slug, 377 struct GNUNET_TIME_Timestamp valid_at, 378 struct GNUNET_TIME_Timestamp sign_until) 379 { 380 enum GNUNET_DB_QueryStatus qs; 381 382 /* We are about to promise a token of this family, so the private key 383 covering @a valid_at must survive until we sign at @a sign_until. If 384 an existing key covers the validity period but was minted for an order 385 with an earlier pay deadline, extend its lifetime instead of minting a 386 second key for the very same validity period: the key lookup below 387 would otherwise consider that key missing and we would end up with two 388 keys for one validity period. */ 389 qs = TALER_MERCHANTDB_update_token_family_key_expiration ( 390 TMH_db, 391 instance_id, 392 slug, 393 valid_at, 394 sign_until); 395 switch (qs) 396 { 397 case GNUNET_DB_STATUS_HARD_ERROR: 398 GNUNET_break (0); 399 return (MHD_YES == 400 TALER_MHD_reply_with_error (connection, 401 MHD_HTTP_INTERNAL_SERVER_ERROR, 402 TALER_EC_GENERIC_DB_STORE_FAILED, 403 "update_token_family_key_expiration")) 404 ? GNUNET_NO 405 : GNUNET_SYSERR; 406 case GNUNET_DB_STATUS_SOFT_ERROR: 407 /* Report the conflict without advertising an uncommitted key. */ 408 GNUNET_break (0); 409 return (MHD_YES == 410 TALER_MHD_reply_with_error (connection, 411 MHD_HTTP_INTERNAL_SERVER_ERROR, 412 TALER_EC_GENERIC_DB_SOFT_FAILURE, 413 "update_token_family_key_expiration")) 414 ? GNUNET_NO 415 : GNUNET_SYSERR; 416 default: 417 /* No key needed extending, or one/more were extended; either is fine. */ 418 break; 419 } 420 return GNUNET_OK; 421 } 422 423 424 /** 425 * Implementation of #TMH_token_key_ensure(). With @a require_coverage, 426 * success may leave the key unset when rounding would fail to cover the 427 * requested time. This allows fountains to stop without storing unusable 428 * keys; the caller must still free the returned family details. The caller 429 * must hold the token family lock throughout this operation. 430 */ 431 static enum GNUNET_GenericReturnValue 432 ensure_key_locked ( 433 struct MHD_Connection *connection, 434 const char *instance_id, 435 const char *slug, 436 struct GNUNET_TIME_Timestamp valid_at, 437 struct GNUNET_TIME_Timestamp sign_until, 438 bool require_coverage, 439 struct TALER_MERCHANTDB_TokenFamilyKeyDetails *key_details, 440 bool *minted) 441 { 442 enum GNUNET_DB_QueryStatus qs; 443 enum GNUNET_GenericReturnValue res; 444 445 *minted = false; 446 res = TMH_token_key_extend (connection, 447 instance_id, 448 slug, 449 valid_at, 450 sign_until); 451 if (GNUNET_OK != res) 452 return res; 453 qs = TALER_MERCHANTDB_get_token_family_key ( 454 TMH_db, 455 instance_id, 456 slug, 457 valid_at, 458 sign_until, 459 key_details); 460 switch (qs) 461 { 462 case GNUNET_DB_STATUS_HARD_ERROR: 463 GNUNET_break (0); 464 return (MHD_YES == 465 TALER_MHD_reply_with_error (connection, 466 MHD_HTTP_INTERNAL_SERVER_ERROR, 467 TALER_EC_GENERIC_DB_FETCH_FAILED, 468 "get_token_family_key")) 469 ? GNUNET_NO 470 : GNUNET_SYSERR; 471 case GNUNET_DB_STATUS_SOFT_ERROR: 472 /* Report the conflict without advertising an uncommitted key. */ 473 GNUNET_break (0); 474 return (MHD_YES == 475 TALER_MHD_reply_with_error (connection, 476 MHD_HTTP_INTERNAL_SERVER_ERROR, 477 TALER_EC_GENERIC_DB_SOFT_FAILURE, 478 "get_token_family_key")) 479 ? GNUNET_NO 480 : GNUNET_SYSERR; 481 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 482 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 483 "Token family slug %s unknown at %llu for %llu for instance %s\n", 484 slug, 485 (unsigned long long) valid_at.abs_time.abs_value_us, 486 (unsigned long long) sign_until.abs_time.abs_value_us, 487 instance_id); 488 return (MHD_YES == 489 TALER_MHD_reply_with_error (connection, 490 MHD_HTTP_NOT_FOUND, 491 TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_TOKEN_FAMILY_SLUG_UNKNOWN, 492 slug)) 493 ? GNUNET_NO 494 : GNUNET_SYSERR; 495 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 496 break; 497 } 498 499 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 500 "Lookup of token family %s at %llu yielded %s\n", 501 slug, 502 (unsigned long long) valid_at.abs_time.abs_value_us, 503 NULL == key_details->pub.public_key ? "no key" : "a key"); 504 505 if (NULL != key_details->pub.public_key) 506 return GNUNET_OK; 507 508 /* No suitable key exists, create one! */ 509 { 510 res = mint_key (connection, 511 instance_id, 512 slug, 513 valid_at, 514 sign_until, 515 require_coverage, 516 key_details); 517 if (GNUNET_OK != res) 518 return res; 519 *minted = (NULL != key_details->pub.public_key); 520 } 521 return GNUNET_OK; 522 } 523 524 525 /** 526 * Hold the family lock across the entire lookup-and-mint operation. Only 527 * commit transactions we started; withdrawal keeps the lock until its quota 528 * and signatures are committed together. 529 */ 530 static enum GNUNET_GenericReturnValue 531 ensure_key ( 532 struct MHD_Connection *connection, 533 const char *instance_id, 534 const char *slug, 535 struct GNUNET_TIME_Timestamp valid_at, 536 struct GNUNET_TIME_Timestamp sign_until, 537 bool require_coverage, 538 struct TALER_MERCHANTDB_TokenFamilyKeyDetails *key_details, 539 bool *minted) 540 { 541 bool started_transaction; 542 enum GNUNET_DB_QueryStatus qs; 543 enum GNUNET_GenericReturnValue res; 544 545 *minted = false; 546 qs = TALER_MERCHANTDB_lock_token_family (TMH_db, 547 slug, 548 &started_transaction); 549 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 550 { 551 if (started_transaction) 552 TALER_MERCHANTDB_rollback (TMH_db); 553 return (MHD_YES == 554 TALER_MHD_reply_with_error ( 555 connection, 556 MHD_HTTP_NOT_FOUND, 557 TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_TOKEN_FAMILY_SLUG_UNKNOWN, 558 slug)) 559 ? GNUNET_NO 560 : GNUNET_SYSERR; 561 } 562 if (qs < 0) 563 { 564 if (started_transaction) 565 TALER_MERCHANTDB_rollback (TMH_db); 566 return (MHD_YES == 567 TALER_MHD_reply_with_error ( 568 connection, 569 MHD_HTTP_INTERNAL_SERVER_ERROR, 570 GNUNET_DB_STATUS_SOFT_ERROR == qs 571 ? TALER_EC_GENERIC_DB_SOFT_FAILURE 572 : TALER_EC_GENERIC_DB_STORE_FAILED, 573 "lock_token_family")) 574 ? GNUNET_NO 575 : GNUNET_SYSERR; 576 } 577 res = ensure_key_locked (connection, 578 instance_id, 579 slug, 580 valid_at, 581 sign_until, 582 require_coverage, 583 key_details, 584 minted); 585 if (! started_transaction) 586 return res; 587 if (GNUNET_OK != res) 588 { 589 TALER_MERCHANTDB_rollback (TMH_db); 590 return res; 591 } 592 qs = TALER_MERCHANTDB_commit (TMH_db); 593 if (qs >= 0) 594 return GNUNET_OK; 595 TMH_token_key_details_free (key_details); 596 *minted = false; 597 return (MHD_YES == 598 TALER_MHD_reply_with_error ( 599 connection, 600 MHD_HTTP_INTERNAL_SERVER_ERROR, 601 GNUNET_DB_STATUS_SOFT_ERROR == qs 602 ? TALER_EC_GENERIC_DB_SOFT_FAILURE 603 : TALER_EC_GENERIC_DB_COMMIT_FAILED, 604 "ensure token family key")) 605 ? GNUNET_NO 606 : GNUNET_SYSERR; 607 } 608 609 610 enum GNUNET_GenericReturnValue 611 TMH_token_key_ensure ( 612 struct MHD_Connection *connection, 613 const char *instance_id, 614 const char *slug, 615 struct GNUNET_TIME_Timestamp valid_at, 616 struct GNUNET_TIME_Timestamp sign_until, 617 struct TALER_MERCHANTDB_TokenFamilyKeyDetails *key_details, 618 bool *minted) 619 { 620 return ensure_key (connection, 621 instance_id, 622 slug, 623 valid_at, 624 sign_until, 625 false, 626 key_details, 627 minted); 628 } 629 630 631 void 632 TMH_token_key_window_free (struct TMH_TokenKeyWindow *window) 633 { 634 for (unsigned int i = 0; i < window->keys_len; i++) 635 TMH_token_key_details_free (&window->keys[i]); 636 GNUNET_array_grow (window->keys, 637 window->keys_len, 638 0); 639 } 640 641 642 enum GNUNET_GenericReturnValue 643 TMH_token_key_window_get ( 644 struct MHD_Connection *connection, 645 const char *instance_id, 646 const struct TALER_MERCHANTDB_TokenFamilyDetails *tf, 647 struct GNUNET_TIME_Timestamp now, 648 unsigned int key_window_size, 649 struct TMH_TokenKeyWindow *window) 650 { 651 struct GNUNET_TIME_Timestamp cursor; 652 struct GNUNET_TIME_Timestamp latest_start; 653 654 *window = (struct TMH_TokenKeyWindow) {0}; 655 GNUNET_assert (key_window_size <= TMH_MAX_FOUNTAIN_KEY_WINDOW); 656 latest_start = GNUNET_TIME_absolute_to_timestamp ( 657 GNUNET_TIME_absolute_add ( 658 now.abs_time, 659 GNUNET_TIME_relative_multiply (tf->duration, 660 key_window_size))); 661 cursor = GNUNET_TIME_timestamp_max (now, 662 tf->valid_after); 663 /* A future family must fit the horizon measured from now; its start 664 must never move that horizon forward. */ 665 if (GNUNET_TIME_timestamp_cmp (cursor, >, latest_start)) 666 return GNUNET_OK; 667 for (unsigned int slot = 0; slot <= key_window_size; slot++) 668 { 669 struct TALER_MERCHANTDB_TokenFamilyKeyDetails kd; 670 struct GNUNET_TIME_Timestamp next; 671 enum GNUNET_GenericReturnValue res; 672 bool minted; 673 674 if (GNUNET_TIME_timestamp_cmp (cursor, >=, tf->valid_before)) 675 break; 676 res = ensure_key (connection, 677 instance_id, 678 tf->slug, 679 cursor, 680 now, 681 true, 682 &kd, 683 &minted); 684 if (GNUNET_OK != res) 685 { 686 TMH_token_key_window_free (window); 687 return res; 688 } 689 if ( (NULL == kd.pub.public_key) || 690 GNUNET_TIME_timestamp_cmp (kd.signature_validity_start, >, latest_start) || 691 GNUNET_TIME_timestamp_cmp (kd.signature_validity_start, >, cursor) || 692 GNUNET_TIME_timestamp_cmp (kd.signature_validity_end, <, cursor) || 693 GNUNET_TIME_timestamp_cmp (kd.signature_validity_end, 694 >, 695 tf->valid_before) ) 696 { 697 /* Rounding/offsets may not yield further coverage. Do not count 698 a repeated or unusable period as another future key. */ 699 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 700 "Token family %s cannot extend fountain coverage at %llu\n", 701 tf->slug, 702 (unsigned long long) cursor.abs_time.abs_value_us); 703 TMH_token_key_details_free (&kd); 704 break; 705 } 706 GNUNET_array_append (window->keys, 707 window->keys_len, 708 kd); 709 /* Lookup includes the expiry instant. Move one whole timestamp 710 tick beyond it, so the previous key cannot be selected again. */ 711 next = GNUNET_TIME_absolute_to_timestamp ( 712 GNUNET_TIME_absolute_add (kd.signature_validity_end.abs_time, 713 GNUNET_TIME_UNIT_SECONDS)); 714 next = GNUNET_TIME_timestamp_min (next, 715 latest_start); 716 if (GNUNET_TIME_timestamp_cmp (next, <=, kd.signature_validity_end)) 717 break; /* reached the horizon, forever or saturated arithmetic */ 718 cursor = next; 719 } 720 return GNUNET_OK; 721 } 722 723 724 enum GNUNET_GenericReturnValue 725 TMH_token_key_window_find ( 726 const struct TMH_TokenKeyWindow *window, 727 struct GNUNET_TIME_Timestamp valid_at, 728 unsigned int *key_index) 729 { 730 /* Wallets can select an advertised key unambiguously by its start, 731 even if an earlier key also covers that instant. */ 732 for (unsigned int i = 0; i < window->keys_len; i++) 733 if (GNUNET_TIME_timestamp_cmp (valid_at, 734 ==, 735 window->keys[i].signature_validity_start)) 736 { 737 *key_index = i; 738 return GNUNET_OK; 739 } 740 /* Otherwise keep the covering-key convention: first matching key. */ 741 for (unsigned int i = 0; i < window->keys_len; i++) 742 if (GNUNET_TIME_timestamp_cmp (valid_at, 743 >=, 744 window->keys[i].signature_validity_start) && 745 GNUNET_TIME_timestamp_cmp (valid_at, 746 <=, 747 window->keys[i].signature_validity_end)) 748 { 749 *key_index = i; 750 return GNUNET_OK; 751 } 752 return GNUNET_NO; 753 } 754 755 756 void 757 TMH_token_family_to_contract ( 758 const struct TALER_MERCHANTDB_TokenFamilyDetails *tf, 759 struct TALER_MERCHANT_ContractTokenFamily *family) 760 { 761 struct TALER_MERCHANT_ContractTokenFamily new_family = { 762 .slug = GNUNET_strdup (tf->slug), 763 .name = GNUNET_strdup (tf->name), 764 .description = GNUNET_strdup (tf->description), 765 .description_i18n = json_incref (tf->description_i18n), 766 }; 767 768 switch (tf->kind) 769 { 770 case TALER_MERCHANTDB_TFK_Subscription: 771 { 772 json_t *tdomains = json_object_get (tf->extra_data, 773 "trusted_domains"); 774 json_t *dom; 775 size_t i; 776 777 new_family.kind = TALER_MERCHANT_CONTRACT_TOKEN_KIND_SUBSCRIPTION; 778 new_family.critical = true; 779 new_family.details.subscription.trusted_domains_len 780 = json_array_size (tdomains); 781 GNUNET_assert (new_family.details.subscription.trusted_domains_len 782 < UINT_MAX); 783 new_family.details.subscription.trusted_domains 784 = GNUNET_new_array ( 785 new_family.details.subscription.trusted_domains_len, 786 char *); 787 json_array_foreach (tdomains, i, dom) 788 { 789 const char *val; 790 791 val = json_string_value (dom); 792 GNUNET_break (NULL != val); 793 if (NULL != val) 794 new_family.details.subscription.trusted_domains[i] 795 = GNUNET_strdup (val); 796 } 797 break; 798 } 799 case TALER_MERCHANTDB_TFK_Discount: 800 { 801 json_t *edomains = json_object_get (tf->extra_data, 802 "expected_domains"); 803 json_t *dom; 804 size_t i; 805 806 new_family.kind = TALER_MERCHANT_CONTRACT_TOKEN_KIND_DISCOUNT; 807 new_family.critical = false; 808 new_family.details.discount.expected_domains_len 809 = json_array_size (edomains); 810 GNUNET_assert (new_family.details.discount.expected_domains_len 811 < UINT_MAX); 812 new_family.details.discount.expected_domains 813 = GNUNET_new_array ( 814 new_family.details.discount.expected_domains_len, 815 char *); 816 json_array_foreach (edomains, i, dom) 817 { 818 const char *val; 819 820 val = json_string_value (dom); 821 GNUNET_break (NULL != val); 822 if (NULL != val) 823 new_family.details.discount.expected_domains[i] 824 = GNUNET_strdup (val); 825 } 826 break; 827 } 828 } 829 *family = new_family; 830 } 831 832 833 /* end of taler-merchant-httpd_token-keys.c */