exchange_api_common.c (20059B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2015-2023 Taler Systems SA 4 5 TALER 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 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 General Public License for more details. 12 13 You should have received a copy of the GNU General Public License along with 14 TALER; see the file COPYING. If not, see 15 <http://www.gnu.org/licenses/> 16 */ 17 /** 18 * @file lib/exchange_api_common.c 19 * @brief common functions for the exchange API 20 * @author Christian Grothoff 21 */ 22 #include "taler/platform.h" 23 #include "taler/taler_json_lib.h" 24 #include <gnunet/gnunet_curl_lib.h> 25 #include "exchange_api_common.h" 26 #include "exchange_api_handle.h" 27 #include "taler/taler_signatures.h" 28 29 30 const struct TALER_EXCHANGE_SigningPublicKey * 31 TALER_EXCHANGE_get_signing_key_info ( 32 const struct TALER_EXCHANGE_Keys *keys, 33 const struct TALER_ExchangePublicKeyP *exchange_pub) 34 { 35 for (unsigned int i = 0; i<keys->num_sign_keys; i++) 36 { 37 const struct TALER_EXCHANGE_SigningPublicKey *spk 38 = &keys->sign_keys[i]; 39 40 if (0 == GNUNET_memcmp (exchange_pub, 41 &spk->key)) 42 return spk; 43 } 44 return NULL; 45 } 46 47 48 enum GNUNET_GenericReturnValue 49 TALER_EXCHANGE_check_purse_create_conflict_ ( 50 const struct TALER_PurseContractSignatureP *cpurse_sig, 51 const struct TALER_PurseContractPublicKeyP *purse_pub, 52 const json_t *proof) 53 { 54 struct TALER_Amount amount; 55 uint32_t min_age; 56 struct GNUNET_TIME_Timestamp purse_expiration; 57 struct TALER_PurseContractSignatureP purse_sig; 58 struct TALER_PrivateContractHashP h_contract_terms; 59 struct TALER_PurseMergePublicKeyP merge_pub; 60 struct GNUNET_JSON_Specification spec[] = { 61 TALER_JSON_spec_amount_any ("amount", 62 &amount), 63 GNUNET_JSON_spec_uint32 ("min_age", 64 &min_age), 65 GNUNET_JSON_spec_timestamp ("purse_expiration", 66 &purse_expiration), 67 GNUNET_JSON_spec_fixed_auto ("purse_sig", 68 &purse_sig), 69 GNUNET_JSON_spec_fixed_auto ("h_contract_terms", 70 &h_contract_terms), 71 GNUNET_JSON_spec_fixed_auto ("merge_pub", 72 &merge_pub), 73 GNUNET_JSON_spec_end () 74 }; 75 76 if (GNUNET_OK != 77 GNUNET_JSON_parse (proof, 78 spec, 79 NULL, NULL)) 80 { 81 GNUNET_break_op (0); 82 return GNUNET_SYSERR; 83 } 84 if (GNUNET_OK != 85 TALER_wallet_purse_create_verify (purse_expiration, 86 &h_contract_terms, 87 &merge_pub, 88 min_age, 89 &amount, 90 purse_pub, 91 &purse_sig)) 92 { 93 GNUNET_break_op (0); 94 return GNUNET_SYSERR; 95 } 96 if (0 == 97 GNUNET_memcmp (&purse_sig, 98 cpurse_sig)) 99 { 100 /* Must be the SAME data, not a conflict! */ 101 GNUNET_break_op (0); 102 return GNUNET_SYSERR; 103 } 104 return GNUNET_OK; 105 } 106 107 108 enum GNUNET_GenericReturnValue 109 TALER_EXCHANGE_check_purse_merge_conflict_ ( 110 const struct TALER_PurseMergeSignatureP *cmerge_sig, 111 const struct TALER_PurseMergePublicKeyP *merge_pub, 112 const struct TALER_PurseContractPublicKeyP *purse_pub, 113 const char *exchange_url, 114 const json_t *proof) 115 { 116 struct TALER_PurseMergeSignatureP merge_sig; 117 struct GNUNET_TIME_Timestamp merge_timestamp; 118 const char *partner_url = NULL; 119 struct TALER_ReservePublicKeyP reserve_pub; 120 struct GNUNET_JSON_Specification spec[] = { 121 GNUNET_JSON_spec_mark_optional ( 122 TALER_JSON_spec_web_url ("partner_url", 123 &partner_url), 124 NULL), 125 GNUNET_JSON_spec_timestamp ("merge_timestamp", 126 &merge_timestamp), 127 GNUNET_JSON_spec_fixed_auto ("merge_sig", 128 &merge_sig), 129 GNUNET_JSON_spec_fixed_auto ("reserve_pub", 130 &reserve_pub), 131 GNUNET_JSON_spec_end () 132 }; 133 struct TALER_NormalizedPayto payto_uri; 134 135 if (GNUNET_OK != 136 GNUNET_JSON_parse (proof, 137 spec, 138 NULL, NULL)) 139 { 140 GNUNET_break_op (0); 141 return GNUNET_SYSERR; 142 } 143 if (NULL == partner_url) 144 partner_url = exchange_url; 145 payto_uri = TALER_reserve_make_payto (partner_url, 146 &reserve_pub); 147 if (GNUNET_OK != 148 TALER_wallet_purse_merge_verify ( 149 payto_uri, 150 merge_timestamp, 151 purse_pub, 152 merge_pub, 153 &merge_sig)) 154 { 155 GNUNET_break_op (0); 156 GNUNET_free (payto_uri.normalized_payto); 157 return GNUNET_SYSERR; 158 } 159 GNUNET_free (payto_uri.normalized_payto); 160 if (0 == 161 GNUNET_memcmp (&merge_sig, 162 cmerge_sig)) 163 { 164 /* Must be the SAME data, not a conflict! */ 165 GNUNET_break_op (0); 166 return GNUNET_SYSERR; 167 } 168 return GNUNET_OK; 169 } 170 171 172 enum GNUNET_GenericReturnValue 173 TALER_EXCHANGE_check_purse_coin_conflict_ ( 174 const struct TALER_PurseContractPublicKeyP *purse_pub, 175 const char *exchange_url, 176 const json_t *proof, 177 struct TALER_DenominationHashP *h_denom_pub, 178 struct TALER_AgeCommitmentHashP *phac, 179 struct TALER_CoinSpendPublicKeyP *coin_pub, 180 struct TALER_CoinSpendSignatureP *coin_sig) 181 { 182 const char *partner_url = NULL; 183 struct TALER_Amount amount; 184 struct GNUNET_JSON_Specification spec[] = { 185 GNUNET_JSON_spec_fixed_auto ("h_denom_pub", 186 h_denom_pub), 187 GNUNET_JSON_spec_fixed_auto ("h_age_commitment", 188 phac), 189 GNUNET_JSON_spec_fixed_auto ("coin_sig", 190 coin_sig), 191 GNUNET_JSON_spec_fixed_auto ("coin_pub", 192 coin_pub), 193 GNUNET_JSON_spec_mark_optional ( 194 TALER_JSON_spec_web_url ("partner_url", 195 &partner_url), 196 NULL), 197 TALER_JSON_spec_amount_any ("amount", 198 &amount), 199 GNUNET_JSON_spec_end () 200 }; 201 202 if (GNUNET_OK != 203 GNUNET_JSON_parse (proof, 204 spec, 205 NULL, NULL)) 206 { 207 GNUNET_break_op (0); 208 return GNUNET_SYSERR; 209 } 210 if (NULL == partner_url) 211 partner_url = exchange_url; 212 if (GNUNET_OK != 213 TALER_wallet_purse_deposit_verify ( 214 partner_url, 215 purse_pub, 216 &amount, 217 h_denom_pub, 218 phac, 219 coin_pub, 220 coin_sig)) 221 { 222 GNUNET_break_op (0); 223 return GNUNET_SYSERR; 224 } 225 return GNUNET_OK; 226 } 227 228 229 enum GNUNET_GenericReturnValue 230 TALER_EXCHANGE_check_purse_econtract_conflict_ ( 231 const struct TALER_PurseContractSignatureP *ccontract_sig, 232 const struct TALER_PurseContractPublicKeyP *purse_pub, 233 const json_t *proof) 234 { 235 struct TALER_ContractDiffiePublicP contract_pub; 236 struct TALER_PurseContractSignatureP contract_sig; 237 struct GNUNET_HashCode h_econtract; 238 struct GNUNET_JSON_Specification spec[] = { 239 GNUNET_JSON_spec_fixed_auto ("h_econtract", 240 &h_econtract), 241 GNUNET_JSON_spec_fixed_auto ("econtract_sig", 242 &contract_sig), 243 GNUNET_JSON_spec_fixed_auto ("contract_pub", 244 &contract_pub), 245 GNUNET_JSON_spec_end () 246 }; 247 248 if (GNUNET_OK != 249 GNUNET_JSON_parse (proof, 250 spec, 251 NULL, NULL)) 252 { 253 GNUNET_break_op (0); 254 return GNUNET_SYSERR; 255 } 256 if (GNUNET_OK != 257 TALER_wallet_econtract_upload_verify2 ( 258 &h_econtract, 259 &contract_pub, 260 purse_pub, 261 &contract_sig)) 262 { 263 GNUNET_break_op (0); 264 return GNUNET_SYSERR; 265 } 266 if (0 == 267 GNUNET_memcmp (&contract_sig, 268 ccontract_sig)) 269 { 270 /* Must be the SAME data, not a conflict! */ 271 GNUNET_break_op (0); 272 return GNUNET_SYSERR; 273 } 274 return GNUNET_OK; 275 } 276 277 278 // FIXME: should be used... - #9422 279 enum GNUNET_GenericReturnValue 280 TALER_EXCHANGE_check_coin_denomination_conflict_ ( 281 const json_t *proof, 282 const struct TALER_DenominationHashP *ch_denom_pub) 283 { 284 struct TALER_DenominationHashP h_denom_pub; 285 struct GNUNET_JSON_Specification spec[] = { 286 GNUNET_JSON_spec_fixed_auto ("h_denom_pub", 287 &h_denom_pub), 288 GNUNET_JSON_spec_end () 289 }; 290 291 if (GNUNET_OK != 292 GNUNET_JSON_parse (proof, 293 spec, 294 NULL, NULL)) 295 { 296 GNUNET_break_op (0); 297 return GNUNET_SYSERR; 298 } 299 if (0 == 300 GNUNET_memcmp (ch_denom_pub, 301 &h_denom_pub)) 302 { 303 GNUNET_break_op (0); 304 return GNUNET_OK; 305 } 306 /* indeed, proof with different denomination key provided */ 307 return GNUNET_OK; 308 } 309 310 311 enum GNUNET_GenericReturnValue 312 TALER_EXCHANGE_get_min_denomination_ ( 313 const struct TALER_EXCHANGE_Keys *keys, 314 struct TALER_Amount *min) 315 { 316 bool have_min = false; 317 for (unsigned int i = 0; i<keys->num_denom_keys; i++) 318 { 319 const struct TALER_EXCHANGE_DenomPublicKey *dk = &keys->denom_keys[i]; 320 321 if (! have_min) 322 { 323 *min = dk->value; 324 have_min = true; 325 continue; 326 } 327 if (1 != TALER_amount_cmp (min, 328 &dk->value)) 329 continue; 330 *min = dk->value; 331 } 332 if (! have_min) 333 { 334 GNUNET_break (0); 335 return GNUNET_SYSERR; 336 } 337 return GNUNET_OK; 338 } 339 340 341 enum GNUNET_GenericReturnValue 342 TALER_EXCHANGE_verify_deposit_signature_ ( 343 const struct TALER_EXCHANGE_DepositContractDetail *dcd, 344 const struct TALER_ExtensionPolicyHashP *ech, 345 const struct TALER_MerchantWireHashP *h_wire, 346 const struct TALER_EXCHANGE_CoinDepositDetail *cdd, 347 const struct TALER_EXCHANGE_DenomPublicKey *dki) 348 { 349 if (GNUNET_OK != 350 TALER_wallet_deposit_verify (&cdd->amount, 351 &dki->fees.deposit, 352 h_wire, 353 &dcd->h_contract_terms, 354 &dcd->wallet_data_hash, 355 &cdd->h_age_commitment, 356 ech, 357 &cdd->h_denom_pub, 358 dcd->wallet_timestamp, 359 &dcd->merchant_pub, 360 dcd->refund_deadline, 361 &cdd->coin_pub, 362 &cdd->coin_sig)) 363 { 364 GNUNET_break_op (0); 365 TALER_LOG_WARNING ("Invalid coin signature on /deposit request!\n"); 366 TALER_LOG_DEBUG ("... amount_with_fee was %s\n", 367 TALER_amount2s (&cdd->amount)); 368 TALER_LOG_DEBUG ("... deposit_fee was %s\n", 369 TALER_amount2s (&dki->fees.deposit)); 370 return GNUNET_SYSERR; 371 } 372 373 /* check coin signature */ 374 { 375 struct TALER_CoinPublicInfo coin_info = { 376 .coin_pub = cdd->coin_pub, 377 .denom_pub_hash = cdd->h_denom_pub, 378 .denom_sig = cdd->denom_sig, 379 .h_age_commitment = cdd->h_age_commitment, 380 }; 381 382 if (GNUNET_YES != 383 TALER_test_coin_valid (&coin_info, 384 &dki->key)) 385 { 386 GNUNET_break_op (0); 387 TALER_LOG_WARNING ("Invalid coin passed for /deposit\n"); 388 return GNUNET_SYSERR; 389 } 390 } 391 392 /* Check coin does make a contribution */ 393 if (0 < TALER_amount_cmp (&dki->fees.deposit, 394 &cdd->amount)) 395 { 396 GNUNET_break_op (0); 397 TALER_LOG_WARNING ("Deposit amount smaller than fee\n"); 398 return GNUNET_SYSERR; 399 } 400 return GNUNET_OK; 401 } 402 403 404 /** 405 * Parse account restriction in @a jrest into @a rest. 406 * 407 * @param jresta array of account restrictions in JSON 408 * @param[out] resta_len set to length of @a resta 409 * @param[out] resta account restriction array to set 410 * @return #GNUNET_OK on success 411 */ 412 static enum GNUNET_GenericReturnValue 413 parse_restrictions (const json_t *jresta, 414 unsigned int *resta_len, 415 struct TALER_EXCHANGE_AccountRestriction **resta) 416 { 417 size_t alen; 418 419 if (! json_is_array (jresta)) 420 { 421 GNUNET_break_op (0); 422 return GNUNET_SYSERR; 423 } 424 alen = json_array_size (jresta); 425 if (0 == alen) 426 { 427 /* no restrictions, perfectly OK */ 428 *resta = NULL; 429 return GNUNET_OK; 430 } 431 *resta_len = (unsigned int) alen; 432 GNUNET_assert (alen == *resta_len); 433 *resta = GNUNET_new_array (*resta_len, 434 struct TALER_EXCHANGE_AccountRestriction); 435 for (unsigned int i = 0; i<*resta_len; i++) 436 { 437 const json_t *jr = json_array_get (jresta, 438 i); 439 struct TALER_EXCHANGE_AccountRestriction *ar = &(*resta)[i]; 440 const char *type = json_string_value (json_object_get (jr, 441 "type")); 442 443 if (NULL == type) 444 { 445 GNUNET_break (0); 446 goto fail; 447 } 448 if (0 == strcmp (type, 449 "deny")) 450 { 451 ar->type = TALER_EXCHANGE_AR_DENY; 452 continue; 453 } 454 if (0 == strcmp (type, 455 "regex")) 456 { 457 const char *regex; 458 const char *hint; 459 struct GNUNET_JSON_Specification spec[] = { 460 GNUNET_JSON_spec_string ( 461 "payto_regex", 462 ®ex), 463 GNUNET_JSON_spec_string ( 464 "human_hint", 465 &hint), 466 GNUNET_JSON_spec_mark_optional ( 467 GNUNET_JSON_spec_json ( 468 "human_hint_i18n", 469 &ar->details.regex.human_hint_i18n), 470 NULL), 471 GNUNET_JSON_spec_end () 472 }; 473 474 if (GNUNET_OK != 475 GNUNET_JSON_parse (jr, 476 spec, 477 NULL, NULL)) 478 { 479 /* bogus reply */ 480 GNUNET_break_op (0); 481 goto fail; 482 } 483 ar->type = TALER_EXCHANGE_AR_REGEX; 484 ar->details.regex.posix_egrep = GNUNET_strdup (regex); 485 ar->details.regex.human_hint = GNUNET_strdup (hint); 486 continue; 487 } 488 /* unsupported type */ 489 GNUNET_break (0); 490 return GNUNET_SYSERR; 491 } 492 return GNUNET_OK; 493 fail: 494 GNUNET_free (*resta); 495 *resta_len = 0; 496 return GNUNET_SYSERR; 497 } 498 499 500 enum GNUNET_GenericReturnValue 501 TALER_EXCHANGE_parse_accounts ( 502 const struct TALER_MasterPublicKeyP *master_pub, 503 const json_t *accounts, 504 unsigned int was_length, 505 struct TALER_EXCHANGE_WireAccount was[static was_length]) 506 { 507 memset (was, 508 0, 509 sizeof (struct TALER_EXCHANGE_WireAccount) * was_length); 510 GNUNET_assert (was_length == 511 json_array_size (accounts)); 512 for (unsigned int i = 0; 513 i<was_length; 514 i++) 515 { 516 struct TALER_EXCHANGE_WireAccount *wa = &was[i]; 517 struct TALER_FullPayto payto_uri; 518 const char *conversion_url = NULL; 519 const char *bank_label = NULL; 520 int64_t priority = 0; 521 const json_t *credit_restrictions; 522 const json_t *debit_restrictions; 523 struct GNUNET_JSON_Specification spec_account[] = { 524 TALER_JSON_spec_full_payto_uri ("payto_uri", 525 &payto_uri), 526 GNUNET_JSON_spec_mark_optional ( 527 TALER_JSON_spec_web_url ("conversion_url", 528 &conversion_url), 529 NULL), 530 GNUNET_JSON_spec_mark_optional ( 531 GNUNET_JSON_spec_int64 ("priority", 532 &priority), 533 NULL), 534 GNUNET_JSON_spec_mark_optional ( 535 GNUNET_JSON_spec_string ("bank_label", 536 &bank_label), 537 NULL), 538 GNUNET_JSON_spec_array_const ("credit_restrictions", 539 &credit_restrictions), 540 GNUNET_JSON_spec_array_const ("debit_restrictions", 541 &debit_restrictions), 542 GNUNET_JSON_spec_fixed_auto ("master_sig", 543 &wa->master_sig), 544 GNUNET_JSON_spec_end () 545 }; 546 json_t *account; 547 548 account = json_array_get (accounts, 549 i); 550 if (GNUNET_OK != 551 GNUNET_JSON_parse (account, 552 spec_account, 553 NULL, NULL)) 554 { 555 /* bogus reply */ 556 GNUNET_break_op (0); 557 return GNUNET_SYSERR; 558 } 559 if ( (NULL != master_pub) && 560 (GNUNET_OK != 561 TALER_exchange_wire_signature_check ( 562 payto_uri, 563 conversion_url, 564 debit_restrictions, 565 credit_restrictions, 566 master_pub, 567 &wa->master_sig)) ) 568 { 569 /* bogus reply */ 570 GNUNET_break_op (0); 571 return GNUNET_SYSERR; 572 } 573 if ( (GNUNET_OK != 574 parse_restrictions (credit_restrictions, 575 &wa->credit_restrictions_length, 576 &wa->credit_restrictions)) || 577 (GNUNET_OK != 578 parse_restrictions (debit_restrictions, 579 &wa->debit_restrictions_length, 580 &wa->debit_restrictions)) ) 581 { 582 /* bogus reply */ 583 GNUNET_break_op (0); 584 return GNUNET_SYSERR; 585 } 586 wa->fpayto_uri.full_payto 587 = GNUNET_strdup (payto_uri.full_payto); 588 wa->priority = priority; 589 if (NULL != conversion_url) 590 wa->conversion_url = GNUNET_strdup (conversion_url); 591 if (NULL != bank_label) 592 wa->bank_label = GNUNET_strdup (bank_label); 593 } /* end 'for all accounts */ 594 return GNUNET_OK; 595 } 596 597 598 /** 599 * Free array of account restrictions. 600 * 601 * @param ar_len length of @a ar 602 * @param[in] ar array to free contents of (but not @a ar itself) 603 */ 604 static void 605 free_restrictions (unsigned int ar_len, 606 struct TALER_EXCHANGE_AccountRestriction ar[static ar_len]) 607 { 608 for (unsigned int i = 0; i<ar_len; i++) 609 { 610 struct TALER_EXCHANGE_AccountRestriction *a = &ar[i]; 611 switch (a->type) 612 { 613 case TALER_EXCHANGE_AR_INVALID: 614 GNUNET_break (0); 615 break; 616 case TALER_EXCHANGE_AR_DENY: 617 break; 618 case TALER_EXCHANGE_AR_REGEX: 619 GNUNET_free (ar->details.regex.posix_egrep); 620 GNUNET_free (ar->details.regex.human_hint); 621 json_decref (ar->details.regex.human_hint_i18n); 622 break; 623 } 624 } 625 } 626 627 628 void 629 TALER_EXCHANGE_free_accounts ( 630 unsigned int was_len, 631 struct TALER_EXCHANGE_WireAccount was[static was_len]) 632 { 633 for (unsigned int i = 0; i<was_len; i++) 634 { 635 struct TALER_EXCHANGE_WireAccount *wa = &was[i]; 636 637 GNUNET_free (wa->fpayto_uri.full_payto); 638 GNUNET_free (wa->conversion_url); 639 GNUNET_free (wa->bank_label); 640 free_restrictions (wa->credit_restrictions_length, 641 wa->credit_restrictions); 642 GNUNET_array_grow (wa->credit_restrictions, 643 wa->credit_restrictions_length, 644 0); 645 free_restrictions (wa->debit_restrictions_length, 646 wa->debit_restrictions); 647 GNUNET_array_grow (wa->debit_restrictions, 648 wa->debit_restrictions_length, 649 0); 650 } 651 } 652 653 654 enum GNUNET_GenericReturnValue 655 TALER_EXCHANGE_keys_test_account_allowed ( 656 const struct TALER_EXCHANGE_Keys *keys, 657 bool check_credit, 658 const struct TALER_NormalizedPayto payto_uri) 659 { 660 /* For all accounts of the exchange */ 661 for (unsigned int i = 0; i<keys->accounts_len; i++) 662 { 663 const struct TALER_EXCHANGE_WireAccount *account 664 = &keys->accounts[i]; 665 666 /* KYC auth transfers are never supported with conversion */ 667 if (NULL != account->conversion_url) 668 continue; 669 /* filter by source account by credit_restrictions */ 670 if (GNUNET_YES != 671 TALER_EXCHANGE_test_account_allowed (account, 672 check_credit, 673 payto_uri)) 674 continue; 675 /* exchange account is allowed, add it */ 676 return true; 677 } 678 return false; 679 } 680 681 682 /* end of exchange_api_common.c */