taler-auditor-offline.c (40416B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2020-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 <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file taler-auditor-offline.c 18 * @brief Support for operations involving the auditor's (offline) key. 19 * @author Christian Grothoff 20 */ 21 #include "platform.h" 22 #include <gnunet/gnunet_json_lib.h> 23 #include <microhttpd.h> 24 #include "taler/taler_json_lib.h" 25 26 #define TALER_EXCHANGE_GET_KEYS_RESULT_CLOSURE \ 27 char *const 28 #include "taler/exchange/get-keys.h" 29 30 struct DenominationAddRequest; 31 #define TALER_EXCHANGE_POST_AUDITORS_RESULT_CLOSURE \ 32 struct DenominationAddRequest 33 #include "taler/exchange/post-auditors-AUDITOR_PUB-H_DENOM_PUB.h" 34 35 /** 36 * Name of the input of a denomination key signature for the 'upload' operation. 37 * The "auditor-" prefix ensures that there is no ambiguity between 38 * taler-exchange-offline and taler-auditor-offline JSON formats. 39 * The last component --by convention-- identifies the protocol version 40 * and should be incremented whenever the JSON format of the 'argument' changes. 41 */ 42 #define OP_SIGN_DENOMINATION "auditor-sign-denomination-0" 43 44 /** 45 * Name of the input for the 'sign' and 'show' operations. 46 * The "auditor-" prefix ensures that there is no ambiguity between 47 * taler-exchange-offline and taler-auditor-offline JSON formats. 48 * The last component --by convention-- identifies the protocol version 49 * and should be incremented whenever the JSON format of the 'argument' changes. 50 */ 51 #define OP_INPUT_KEYS "auditor-keys-0" 52 53 /** 54 * Setup the offline signing key. 55 * The last component --by convention-- identifies the protocol version 56 * and should be incremented whenever the JSON format of the 'argument' changes. 57 */ 58 #define OP_SETUP "auditor-setup-0" 59 60 /** 61 * Our private key, initialized in #load_offline_key(). 62 */ 63 static struct TALER_AuditorPrivateKeyP auditor_priv; 64 65 /** 66 * Our private key, initialized in #load_offline_key(). 67 */ 68 static struct TALER_AuditorPublicKeyP auditor_pub; 69 70 /** 71 * Base URL of this auditor's REST endpoint. 72 */ 73 static char *auditor_url; 74 75 /** 76 * Exchange's master public key. 77 */ 78 static struct TALER_MasterPublicKeyP master_pub; 79 80 /** 81 * Our context for making HTTP requests. 82 */ 83 static struct GNUNET_CURL_Context *ctx; 84 85 /** 86 * Reschedule context for #ctx. 87 */ 88 static struct GNUNET_CURL_RescheduleContext *rc; 89 90 /** 91 * Handle to the exchange's configuration 92 */ 93 static const struct GNUNET_CONFIGURATION_Handle *kcfg; 94 95 /** 96 * Return value from main(). 97 */ 98 static int global_ret; 99 100 /** 101 * Input to consume. 102 */ 103 static json_t *in; 104 105 /** 106 * Array of actions to perform. 107 */ 108 static json_t *out; 109 110 /** 111 * Currency supported by this auditor. 112 */ 113 static char *currency; 114 115 116 /** 117 * A subcommand supported by this program. 118 */ 119 struct SubCommand 120 { 121 /** 122 * Name of the command. 123 */ 124 const char *name; 125 126 /** 127 * Help text for the command. 128 */ 129 const char *help; 130 131 /** 132 * Function implementing the command. 133 * 134 * @param args subsequent command line arguments (char **) 135 */ 136 void (*cb)(char *const *args); 137 }; 138 139 140 /** 141 * Data structure for denomination add requests. 142 */ 143 struct DenominationAddRequest 144 { 145 146 /** 147 * Kept in a DLL. 148 */ 149 struct DenominationAddRequest *next; 150 151 /** 152 * Kept in a DLL. 153 */ 154 struct DenominationAddRequest *prev; 155 156 /** 157 * Operation handle. 158 */ 159 struct TALER_EXCHANGE_PostAuditorsHandle *h; 160 161 /** 162 * Array index of the associated command. 163 */ 164 size_t idx; 165 }; 166 167 168 /** 169 * Next work item to perform. 170 */ 171 static struct GNUNET_SCHEDULER_Task *nxt; 172 173 /** 174 * Active denomination add requests. 175 */ 176 static struct DenominationAddRequest *dar_head; 177 178 /** 179 * Active denomination add requests. 180 */ 181 static struct DenominationAddRequest *dar_tail; 182 183 /** 184 * Handle to the exchange, used to request /keys. 185 */ 186 static struct TALER_EXCHANGE_GetKeysHandle *exchange; 187 188 189 /** 190 * Shutdown task. Invoked when the application is being terminated. 191 * 192 * @param cls NULL 193 */ 194 static void 195 do_shutdown (void *cls) 196 { 197 (void) cls; 198 199 { 200 struct DenominationAddRequest *dar; 201 202 while (NULL != (dar = dar_head)) 203 { 204 fprintf (stderr, 205 "Aborting incomplete wire add #%u\n", 206 (unsigned int) dar->idx); 207 TALER_EXCHANGE_post_auditors_cancel (dar->h); 208 GNUNET_CONTAINER_DLL_remove (dar_head, 209 dar_tail, 210 dar); 211 GNUNET_free (dar); 212 } 213 } 214 if (NULL != out) 215 { 216 json_dumpf (out, 217 stdout, 218 JSON_INDENT (2)); 219 json_decref (out); 220 out = NULL; 221 } 222 if (NULL != in) 223 { 224 fprintf (stderr, 225 "Warning: input not consumed!\n"); 226 json_decref (in); 227 in = NULL; 228 } 229 if (NULL != exchange) 230 { 231 TALER_EXCHANGE_get_keys_cancel (exchange); 232 exchange = NULL; 233 } 234 if (NULL != nxt) 235 { 236 GNUNET_SCHEDULER_cancel (nxt); 237 nxt = NULL; 238 } 239 if (NULL != ctx) 240 { 241 GNUNET_CURL_fini (ctx); 242 ctx = NULL; 243 } 244 if (NULL != rc) 245 { 246 GNUNET_CURL_gnunet_rc_destroy (rc); 247 rc = NULL; 248 } 249 } 250 251 252 /** 253 * Test if we should shut down because all tasks are done. 254 */ 255 static void 256 test_shutdown (void) 257 { 258 if ( (NULL == dar_head) && 259 (NULL == exchange) && 260 (NULL == nxt) ) 261 GNUNET_SCHEDULER_shutdown (); 262 } 263 264 265 /** 266 * Function to continue processing the next command. 267 * 268 * @param cls must be a `char *const*` with the array of 269 * command-line arguments to process next 270 */ 271 static void 272 work (void *cls); 273 274 275 /** 276 * Function to schedule job to process the next command. 277 * 278 * @param args the array of command-line arguments to process next 279 */ 280 static void 281 next (char *const *args) 282 { 283 GNUNET_assert (NULL == nxt); 284 if (NULL == args[0]) 285 { 286 test_shutdown (); 287 return; 288 } 289 nxt = GNUNET_SCHEDULER_add_now (&work, 290 (void *) args); 291 } 292 293 294 /** 295 * Add an operation to the #out JSON array for processing later. 296 * 297 * @param op_name name of the operation 298 * @param op_value values for the operation (consumed) 299 */ 300 static void 301 output_operation (const char *op_name, 302 json_t *op_value) 303 { 304 json_t *action; 305 306 GNUNET_assert (NULL != out); 307 action = GNUNET_JSON_PACK ( 308 GNUNET_JSON_pack_string ("operation", 309 op_name), 310 GNUNET_JSON_pack_object_steal ("arguments", 311 op_value)); 312 GNUNET_break (0 == 313 json_array_append_new (out, 314 action)); 315 } 316 317 318 /** 319 * Information about a subroutine for an upload. 320 */ 321 struct UploadHandler 322 { 323 /** 324 * Key to trigger this subroutine. 325 */ 326 const char *key; 327 328 /** 329 * Function implementing an upload. 330 * 331 * @param exchange_url URL of the exchange 332 * @param idx index of the operation we are performing 333 * @param value arguments to drive the upload. 334 */ 335 void (*cb)(const char *exchange_url, 336 size_t idx, 337 const json_t *value); 338 339 }; 340 341 342 /** 343 * Load the offline key (if not yet done). Triggers shutdown on failure. 344 * 345 * @param do_create #GNUNET_YES if the key may be created 346 * @return #GNUNET_OK on success 347 */ 348 static int 349 load_offline_key (int do_create) 350 { 351 static bool done; 352 int ret; 353 char *fn; 354 355 if (done) 356 return GNUNET_OK; 357 if (GNUNET_OK != 358 GNUNET_CONFIGURATION_get_value_filename (kcfg, 359 "auditor", 360 "AUDITOR_PRIV_FILE", 361 &fn)) 362 { 363 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 364 "auditor", 365 "AUDITOR_PRIV_FILE"); 366 test_shutdown (); 367 return GNUNET_SYSERR; 368 } 369 ret = GNUNET_CRYPTO_eddsa_key_from_file (fn, 370 do_create, 371 &auditor_priv.eddsa_priv); 372 if (GNUNET_SYSERR == ret) 373 { 374 if (do_create) 375 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 376 "Failed to initialize auditor key at `%s': %s\n", 377 fn, 378 "could not create file"); 379 else 380 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 381 "Failed to load auditor key from file `%s': try running `taler-auditor-offline setup'?\n", 382 fn); 383 GNUNET_free (fn); 384 test_shutdown (); 385 return GNUNET_SYSERR; 386 } 387 GNUNET_free (fn); 388 GNUNET_CRYPTO_eddsa_key_get_public (&auditor_priv.eddsa_priv, 389 &auditor_pub.eddsa_pub); 390 done = true; 391 return GNUNET_OK; 392 } 393 394 395 /** 396 * Function called with information about the post denomination (signature) 397 * add operation result. 398 * 399 * @param dar the add request 400 * @param adr response data 401 */ 402 static void 403 denomination_add_cb ( 404 struct DenominationAddRequest *dar, 405 const struct TALER_EXCHANGE_PostAuditorsResponse *adr) 406 { 407 const struct TALER_EXCHANGE_HttpResponse *hr = &adr->hr; 408 409 if (MHD_HTTP_NO_CONTENT != hr->http_status) 410 { 411 fprintf (stderr, 412 "Upload failed for command #%u with status %u: %s (%s)\n", 413 (unsigned int) dar->idx, 414 hr->http_status, 415 TALER_ErrorCode_get_hint (hr->ec), 416 NULL != hr->hint 417 ? hr->hint 418 : "no hint provided"); 419 global_ret = EXIT_FAILURE; 420 } 421 GNUNET_CONTAINER_DLL_remove (dar_head, 422 dar_tail, 423 dar); 424 GNUNET_free (dar); 425 test_shutdown (); 426 } 427 428 429 /** 430 * Upload denomination add data. 431 * 432 * @param exchange_url base URL of the exchange 433 * @param idx index of the operation we are performing (for logging) 434 * @param value arguments for adding denominations 435 */ 436 static void 437 upload_denomination_add (const char *exchange_url, 438 size_t idx, 439 const json_t *value) 440 { 441 struct TALER_AuditorSignatureP auditor_sig; 442 struct TALER_DenominationHashP h_denom_pub; 443 struct DenominationAddRequest *dar; 444 const char *err_name; 445 unsigned int err_line; 446 struct GNUNET_JSON_Specification spec[] = { 447 GNUNET_JSON_spec_fixed_auto ("h_denom_pub", 448 &h_denom_pub), 449 GNUNET_JSON_spec_fixed_auto ("auditor_sig", 450 &auditor_sig), 451 GNUNET_JSON_spec_end () 452 }; 453 454 if (GNUNET_OK != 455 GNUNET_JSON_parse (value, 456 spec, 457 &err_name, 458 &err_line)) 459 { 460 fprintf (stderr, 461 "Invalid input for adding denomination: %s#%u at %u (skipping)\n", 462 err_name, 463 err_line, 464 (unsigned int) idx); 465 global_ret = EXIT_FAILURE; 466 test_shutdown (); 467 return; 468 } 469 dar = GNUNET_new (struct DenominationAddRequest); 470 dar->idx = idx; 471 dar->h = TALER_EXCHANGE_post_auditors_create (ctx, 472 exchange_url, 473 &h_denom_pub, 474 &auditor_pub, 475 &auditor_sig); 476 GNUNET_assert (NULL != dar->h); 477 GNUNET_assert (TALER_EC_NONE == 478 TALER_EXCHANGE_post_auditors_start (dar->h, 479 &denomination_add_cb, 480 dar)); 481 GNUNET_CONTAINER_DLL_insert (dar_head, 482 dar_tail, 483 dar); 484 } 485 486 487 /** 488 * Perform uploads based on the JSON in #out. 489 * 490 * @param exchange_url base URL of the exchange to use 491 */ 492 static void 493 trigger_upload (const char *exchange_url) 494 { 495 struct UploadHandler uhs[] = { 496 { 497 .key = OP_SIGN_DENOMINATION, 498 .cb = &upload_denomination_add 499 }, 500 /* array termination */ 501 { 502 .key = NULL 503 } 504 }; 505 size_t index; 506 json_t *obj; 507 508 json_array_foreach (out, index, obj) { 509 bool found = false; 510 const char *key; 511 const json_t *value; 512 513 key = json_string_value (json_object_get (obj, "operation")); 514 value = json_object_get (obj, "arguments"); 515 if (NULL == key) 516 { 517 fprintf (stderr, 518 "Malformed JSON input\n"); 519 global_ret = EXIT_FAILURE; 520 test_shutdown (); 521 return; 522 } 523 /* block of code that uses key and value */ 524 for (unsigned int i = 0; NULL != uhs[i].key; i++) 525 { 526 if (0 == strcasecmp (key, 527 uhs[i].key)) 528 { 529 found = true; 530 uhs[i].cb (exchange_url, 531 index, 532 value); 533 break; 534 } 535 } 536 if (! found) 537 { 538 fprintf (stderr, 539 "Upload does not know how to handle `%s'\n", 540 key); 541 global_ret = EXIT_FAILURE; 542 test_shutdown (); 543 return; 544 } 545 } 546 /* test here, in case no upload was triggered (i.e. empty input) */ 547 test_shutdown (); 548 } 549 550 551 /** 552 * Upload operation result (signatures) to exchange. 553 * 554 * @param args the array of command-line arguments to process next 555 */ 556 static void 557 do_upload (char *const *args) 558 { 559 char *exchange_url; 560 561 (void) args; 562 if (GNUNET_YES == GNUNET_is_zero (&auditor_pub)) 563 { 564 /* private key not available, try configuration for public key */ 565 char *auditor_public_key_str; 566 567 if (GNUNET_OK != 568 GNUNET_CONFIGURATION_get_value_string (kcfg, 569 "auditor", 570 "PUBLIC_KEY", 571 &auditor_public_key_str)) 572 { 573 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 574 "auditor", 575 "PUBLIC_KEY"); 576 global_ret = EXIT_NOTCONFIGURED; 577 test_shutdown (); 578 return; 579 } 580 if (GNUNET_OK != 581 GNUNET_CRYPTO_eddsa_public_key_from_string ( 582 auditor_public_key_str, 583 strlen (auditor_public_key_str), 584 &auditor_pub.eddsa_pub)) 585 { 586 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, 587 "auditor", 588 "PUBLIC_KEY", 589 "invalid key"); 590 GNUNET_free (auditor_public_key_str); 591 global_ret = EXIT_NOTCONFIGURED; 592 test_shutdown (); 593 return; 594 } 595 GNUNET_free (auditor_public_key_str); 596 } 597 if (NULL != in) 598 { 599 fprintf (stderr, 600 "Downloaded data was not consumed, refusing upload\n"); 601 test_shutdown (); 602 global_ret = EXIT_FAILURE; 603 return; 604 } 605 if (NULL == out) 606 { 607 json_error_t err; 608 609 out = json_loadf (stdin, 610 JSON_REJECT_DUPLICATES, 611 &err); 612 if (NULL == out) 613 { 614 fprintf (stderr, 615 "Failed to read JSON input: %s at %d:%s (offset: %d)\n", 616 err.text, 617 err.line, 618 err.source, 619 err.position); 620 test_shutdown (); 621 global_ret = EXIT_FAILURE; 622 return; 623 } 624 } 625 if (! json_is_array (out)) 626 { 627 fprintf (stderr, 628 "Error: expected JSON array for `upload` command\n"); 629 test_shutdown (); 630 global_ret = EXIT_FAILURE; 631 return; 632 } 633 if (GNUNET_OK != 634 GNUNET_CONFIGURATION_get_value_string (kcfg, 635 "exchange", 636 "BASE_URL", 637 &exchange_url)) 638 { 639 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 640 "exchange", 641 "BASE_URL"); 642 global_ret = EXIT_NOTCONFIGURED; 643 test_shutdown (); 644 return; 645 } 646 trigger_upload (exchange_url); 647 json_decref (out); 648 out = NULL; 649 GNUNET_free (exchange_url); 650 } 651 652 653 /** 654 * Function called with information about who is auditing 655 * a particular exchange and what keys the exchange is using. 656 * 657 * @param args the remaining args 658 * @param kr response data 659 * @param keys key data from the exchange 660 */ 661 static void 662 keys_cb ( 663 char *const *args, 664 const struct TALER_EXCHANGE_KeysResponse *kr, 665 struct TALER_EXCHANGE_Keys *keys) 666 { 667 exchange = NULL; 668 switch (kr->hr.http_status) 669 { 670 case MHD_HTTP_OK: 671 if (NULL == kr->hr.reply) 672 { 673 GNUNET_break_op (0); 674 test_shutdown (); 675 global_ret = EXIT_FAILURE; 676 if (NULL != keys) 677 TALER_EXCHANGE_keys_decref (keys); 678 return; 679 } 680 break; 681 default: 682 fprintf (stderr, 683 "Failed to download keys: %s (HTTP status: %u/EC: %u)\n", 684 kr->hr.hint, 685 kr->hr.http_status, 686 (unsigned int) kr->hr.ec); 687 test_shutdown (); 688 global_ret = EXIT_FAILURE; 689 return; 690 } 691 in = GNUNET_JSON_PACK ( 692 GNUNET_JSON_pack_string ("operation", 693 OP_INPUT_KEYS), 694 GNUNET_JSON_pack_object_incref ("arguments", 695 (json_t *) kr->hr.reply)); 696 if (NULL == args[0]) 697 { 698 json_dumpf (in, 699 stdout, 700 JSON_INDENT (2)); 701 json_decref (in); 702 in = NULL; 703 } 704 next (args); 705 TALER_EXCHANGE_keys_decref (keys); 706 } 707 708 709 /** 710 * Download future keys. 711 * 712 * @param args the array of command-line arguments to process next 713 */ 714 static void 715 do_download (char *const *args) 716 { 717 char *exchange_url; 718 719 if (GNUNET_OK != 720 GNUNET_CONFIGURATION_get_value_string (kcfg, 721 "exchange", 722 "BASE_URL", 723 &exchange_url)) 724 { 725 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 726 "exchange", 727 "BASE_URL"); 728 test_shutdown (); 729 global_ret = EXIT_NOTCONFIGURED; 730 return; 731 } 732 exchange = TALER_EXCHANGE_get_keys_create (ctx, 733 exchange_url); 734 GNUNET_assert (TALER_EC_NONE == 735 TALER_EXCHANGE_get_keys_start (exchange, 736 &keys_cb, 737 args)); 738 GNUNET_free (exchange_url); 739 } 740 741 742 /** 743 * Output @a denomkeys for human consumption. 744 * 745 * @param denomkeys keys to output 746 * @return #GNUNET_OK on success 747 */ 748 static enum GNUNET_GenericReturnValue 749 show_denomkeys (const json_t *denomkeys) 750 { 751 size_t index; 752 json_t *value; 753 754 json_array_foreach (denomkeys, index, value) { 755 struct TALER_DenominationGroup group; 756 const json_t *denoms; 757 const char *err_name; 758 unsigned int err_line; 759 struct GNUNET_JSON_Specification spec[] = { 760 TALER_JSON_spec_denomination_group (NULL, 761 currency, 762 &group), 763 GNUNET_JSON_spec_array_const ("denoms", 764 &denoms), 765 GNUNET_JSON_spec_end () 766 }; 767 size_t index2; 768 json_t *value2; 769 770 if (GNUNET_OK != 771 GNUNET_JSON_parse (value, 772 spec, 773 &err_name, 774 &err_line)) 775 { 776 fprintf (stderr, 777 "Invalid input for denomination key to 'show': %s#%u at %u (skipping)\n", 778 err_name, 779 err_line, 780 (unsigned int) index); 781 GNUNET_JSON_parse_free (spec); 782 global_ret = EXIT_FAILURE; 783 test_shutdown (); 784 return GNUNET_SYSERR; 785 } 786 json_array_foreach (denoms, index2, value2) { 787 struct GNUNET_TIME_Timestamp stamp_start; 788 struct GNUNET_TIME_Timestamp stamp_expire_withdraw; 789 struct GNUNET_TIME_Timestamp stamp_expire_deposit; 790 struct GNUNET_TIME_Timestamp stamp_expire_legal; 791 struct TALER_DenominationPublicKey denom_pub; 792 struct TALER_MasterSignatureP master_sig; 793 struct GNUNET_JSON_Specification ispec[] = { 794 TALER_JSON_spec_denom_pub_cipher (NULL, 795 group.cipher, 796 &denom_pub), 797 GNUNET_JSON_spec_timestamp ("stamp_start", 798 &stamp_start), 799 GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw", 800 &stamp_expire_withdraw), 801 GNUNET_JSON_spec_timestamp ("stamp_expire_deposit", 802 &stamp_expire_deposit), 803 GNUNET_JSON_spec_timestamp ("stamp_expire_legal", 804 &stamp_expire_legal), 805 GNUNET_JSON_spec_fixed_auto ("master_sig", 806 &master_sig), 807 GNUNET_JSON_spec_end () 808 }; 809 struct GNUNET_TIME_Relative duration; 810 struct TALER_DenominationHashP h_denom_pub; 811 812 if (GNUNET_OK != 813 GNUNET_JSON_parse (value2, 814 ispec, 815 &err_name, 816 &err_line)) 817 { 818 fprintf (stderr, 819 "Invalid input for denomination key to 'show': %s#%u at %u/%u (skipping)\n", 820 err_name, 821 err_line, 822 (unsigned int) index, 823 (unsigned int) index2); 824 GNUNET_JSON_parse_free (spec); 825 global_ret = EXIT_FAILURE; 826 test_shutdown (); 827 return GNUNET_SYSERR; 828 } 829 duration = GNUNET_TIME_absolute_get_difference ( 830 stamp_start.abs_time, 831 stamp_expire_withdraw.abs_time); 832 TALER_denom_pub_hash (&denom_pub, 833 &h_denom_pub); 834 if (GNUNET_OK != 835 TALER_exchange_offline_denom_validity_verify ( 836 &h_denom_pub, 837 stamp_start, 838 stamp_expire_withdraw, 839 stamp_expire_deposit, 840 stamp_expire_legal, 841 &group.value, 842 &group.fees, 843 &master_pub, 844 &master_sig)) 845 { 846 fprintf (stderr, 847 "Invalid master signature for key %s (aborting)\n", 848 TALER_B2S (&h_denom_pub)); 849 global_ret = EXIT_FAILURE; 850 GNUNET_JSON_parse_free (ispec); 851 GNUNET_JSON_parse_free (spec); 852 test_shutdown (); 853 return GNUNET_SYSERR; 854 } 855 856 { 857 char *withdraw_fee_s; 858 char *deposit_fee_s; 859 char *refresh_fee_s; 860 char *refund_fee_s; 861 char *deposit_s; 862 char *legal_s; 863 864 withdraw_fee_s = TALER_amount_to_string (&group.fees.withdraw); 865 deposit_fee_s = TALER_amount_to_string (&group.fees.deposit); 866 refresh_fee_s = TALER_amount_to_string (&group.fees.refresh); 867 refund_fee_s = TALER_amount_to_string (&group.fees.refund); 868 deposit_s = GNUNET_strdup ( 869 GNUNET_TIME_timestamp2s (stamp_expire_deposit)); 870 legal_s = GNUNET_strdup ( 871 GNUNET_TIME_timestamp2s (stamp_expire_legal)); 872 873 printf ( 874 "DENOMINATION-KEY %s of value %s starting at %s " 875 "(used for: %s, deposit until: %s legal end: %s) with fees %s/%s/%s/%s\n", 876 TALER_B2S (&h_denom_pub), 877 TALER_amount2s (&group.value), 878 GNUNET_TIME_timestamp2s (stamp_start), 879 GNUNET_TIME_relative2s (duration, 880 false), 881 deposit_s, 882 legal_s, 883 withdraw_fee_s, 884 deposit_fee_s, 885 refresh_fee_s, 886 refund_fee_s); 887 GNUNET_free (withdraw_fee_s); 888 GNUNET_free (deposit_fee_s); 889 GNUNET_free (refresh_fee_s); 890 GNUNET_free (refund_fee_s); 891 GNUNET_free (deposit_s); 892 GNUNET_free (legal_s); 893 } 894 GNUNET_JSON_parse_free (ispec); 895 } 896 GNUNET_JSON_parse_free (spec); 897 } 898 return GNUNET_OK; 899 } 900 901 902 /** 903 * Parse the '/keys' input for operation called @a command_name. 904 * 905 * @param command_name name of the command, for logging errors 906 * @return NULL if the input is malformed 907 */ 908 static json_t * 909 parse_keys (const char *command_name) 910 { 911 json_t *keys; 912 const char *op_str; 913 struct GNUNET_JSON_Specification spec[] = { 914 GNUNET_JSON_spec_json ("arguments", 915 &keys), 916 GNUNET_JSON_spec_string ("operation", 917 &op_str), 918 GNUNET_JSON_spec_end () 919 }; 920 const char *err_name; 921 unsigned int err_line; 922 923 if (NULL == in) 924 { 925 json_error_t err; 926 927 in = json_loadf (stdin, 928 JSON_REJECT_DUPLICATES, 929 &err); 930 if (NULL == in) 931 { 932 fprintf (stderr, 933 "Failed to read JSON input: %s at %d:%s (offset: %d)\n", 934 err.text, 935 err.line, 936 err.source, 937 err.position); 938 global_ret = EXIT_FAILURE; 939 test_shutdown (); 940 return NULL; 941 } 942 } 943 if (GNUNET_OK != 944 GNUNET_JSON_parse (in, 945 spec, 946 &err_name, 947 &err_line)) 948 { 949 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 950 "Invalid input to '%s': %s#%u (skipping)\n", 951 command_name, 952 err_name, 953 err_line); 954 json_dumpf (in, 955 stderr, 956 JSON_INDENT (2)); 957 global_ret = EXIT_FAILURE; 958 test_shutdown (); 959 return NULL; 960 } 961 if (0 != strcmp (op_str, 962 OP_INPUT_KEYS)) 963 { 964 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 965 "Invalid input to '%s' : operation is `%s', expected `%s'\n", 966 command_name, 967 op_str, 968 OP_INPUT_KEYS); 969 GNUNET_JSON_parse_free (spec); 970 global_ret = EXIT_FAILURE; 971 test_shutdown (); 972 return NULL; 973 } 974 json_decref (in); 975 in = NULL; 976 return keys; 977 } 978 979 980 /** 981 * Show exchange denomination keys. 982 * 983 * @param args the array of command-line arguments to process next 984 */ 985 static void 986 do_show (char *const *args) 987 { 988 json_t *keys; 989 const char *err_name; 990 unsigned int err_line; 991 const json_t *denomkeys; 992 struct TALER_MasterPublicKeyP mpub; 993 struct GNUNET_JSON_Specification spec[] = { 994 GNUNET_JSON_spec_array_const ("denominations", 995 &denomkeys), 996 GNUNET_JSON_spec_fixed_auto ("master_public_key", 997 &mpub), 998 GNUNET_JSON_spec_end () 999 }; 1000 1001 keys = parse_keys ("show"); 1002 if (NULL == keys) 1003 { 1004 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1005 "Showing failed: no valid input\n"); 1006 return; 1007 } 1008 if (GNUNET_OK != 1009 GNUNET_JSON_parse (keys, 1010 spec, 1011 &err_name, 1012 &err_line)) 1013 { 1014 fprintf (stderr, 1015 "Invalid input to 'show': %s#%u (skipping)\n", 1016 err_name, 1017 err_line); 1018 global_ret = EXIT_FAILURE; 1019 test_shutdown (); 1020 json_decref (keys); 1021 return; 1022 } 1023 if (0 != 1024 GNUNET_memcmp (&mpub, 1025 &master_pub)) 1026 { 1027 fprintf (stderr, 1028 "Exchange master public key does not match key we have configured (aborting)\n"); 1029 global_ret = EXIT_FAILURE; 1030 test_shutdown (); 1031 json_decref (keys); 1032 return; 1033 } 1034 if (GNUNET_OK != 1035 show_denomkeys (denomkeys)) 1036 { 1037 global_ret = EXIT_FAILURE; 1038 test_shutdown (); 1039 json_decref (keys); 1040 return; 1041 } 1042 json_decref (keys); 1043 /* do NOT consume input if next argument is '-' */ 1044 if ( (NULL != args[0]) && 1045 (0 == strcmp ("-", 1046 args[0])) ) 1047 { 1048 next (args + 1); 1049 return; 1050 } 1051 next (args); 1052 } 1053 1054 1055 /** 1056 * Sign @a denomkeys with offline key. 1057 * 1058 * @param denomkeys keys to output 1059 * @return #GNUNET_OK on success 1060 */ 1061 static enum GNUNET_GenericReturnValue 1062 sign_denomkeys (const json_t *denomkeys) 1063 { 1064 size_t group_idx; 1065 json_t *value; 1066 1067 json_array_foreach (denomkeys, group_idx, value) { 1068 struct TALER_DenominationGroup group = { 0 }; 1069 const json_t *denom_keys_array; 1070 const char *err_name; 1071 unsigned int err_line; 1072 struct GNUNET_JSON_Specification spec[] = { 1073 TALER_JSON_spec_denomination_group (NULL, 1074 currency, 1075 &group), 1076 GNUNET_JSON_spec_array_const ("denoms", 1077 &denom_keys_array), 1078 GNUNET_JSON_spec_end () 1079 }; 1080 size_t index; 1081 json_t *denom_key_obj; 1082 1083 if (GNUNET_OK != 1084 GNUNET_JSON_parse (value, 1085 spec, 1086 &err_name, 1087 &err_line)) 1088 { 1089 fprintf (stderr, 1090 "Invalid input for denomination key to 'sign': %s#%u at %u (skipping)\n", 1091 err_name, 1092 err_line, 1093 (unsigned int) group_idx); 1094 GNUNET_JSON_parse_free (spec); 1095 global_ret = EXIT_FAILURE; 1096 test_shutdown (); 1097 return GNUNET_SYSERR; 1098 } 1099 json_array_foreach (denom_keys_array, index, denom_key_obj) { 1100 struct GNUNET_TIME_Timestamp stamp_start; 1101 struct GNUNET_TIME_Timestamp stamp_expire_withdraw; 1102 struct GNUNET_TIME_Timestamp stamp_expire_deposit; 1103 struct GNUNET_TIME_Timestamp stamp_expire_legal; 1104 struct TALER_DenominationPublicKey denom_pub = { 1105 .age_mask = group.age_mask 1106 }; 1107 struct TALER_MasterSignatureP master_sig; 1108 struct GNUNET_JSON_Specification ispec[] = { 1109 TALER_JSON_spec_denom_pub_cipher (NULL, 1110 group.cipher, 1111 &denom_pub), 1112 GNUNET_JSON_spec_timestamp ("stamp_start", 1113 &stamp_start), 1114 GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw", 1115 &stamp_expire_withdraw), 1116 GNUNET_JSON_spec_timestamp ("stamp_expire_deposit", 1117 &stamp_expire_deposit), 1118 GNUNET_JSON_spec_timestamp ("stamp_expire_legal", 1119 &stamp_expire_legal), 1120 GNUNET_JSON_spec_fixed_auto ("master_sig", 1121 &master_sig), 1122 GNUNET_JSON_spec_end () 1123 }; 1124 struct TALER_DenominationHashP h_denom_pub; 1125 1126 if (GNUNET_OK != 1127 GNUNET_JSON_parse (denom_key_obj, 1128 ispec, 1129 &err_name, 1130 &err_line)) 1131 { 1132 fprintf (stderr, 1133 "Invalid input for denomination key to 'sign': %s#%u at %u/%u (skipping)\n", 1134 err_name, 1135 err_line, 1136 (unsigned int) group_idx, 1137 (unsigned int) index); 1138 GNUNET_JSON_parse_free (spec); 1139 global_ret = EXIT_FAILURE; 1140 test_shutdown (); 1141 return GNUNET_SYSERR; 1142 } 1143 TALER_denom_pub_hash (&denom_pub, 1144 &h_denom_pub); 1145 if (GNUNET_OK != 1146 TALER_exchange_offline_denom_validity_verify ( 1147 &h_denom_pub, 1148 stamp_start, 1149 stamp_expire_withdraw, 1150 stamp_expire_deposit, 1151 stamp_expire_legal, 1152 &group.value, 1153 &group.fees, 1154 &master_pub, 1155 &master_sig)) 1156 { 1157 fprintf (stderr, 1158 "Invalid master signature for key %s (aborting)\n", 1159 TALER_B2S (&h_denom_pub)); 1160 global_ret = EXIT_FAILURE; 1161 test_shutdown (); 1162 return GNUNET_SYSERR; 1163 } 1164 1165 { 1166 struct TALER_AuditorSignatureP auditor_sig; 1167 1168 TALER_auditor_denom_validity_sign (auditor_url, 1169 &h_denom_pub, 1170 &master_pub, 1171 stamp_start, 1172 stamp_expire_withdraw, 1173 stamp_expire_deposit, 1174 stamp_expire_legal, 1175 &group.value, 1176 &group.fees, 1177 &auditor_priv, 1178 &auditor_sig); 1179 output_operation (OP_SIGN_DENOMINATION, 1180 GNUNET_JSON_PACK ( 1181 GNUNET_JSON_pack_data_auto ("h_denom_pub", 1182 &h_denom_pub), 1183 GNUNET_JSON_pack_data_auto ("auditor_sig", 1184 &auditor_sig))); 1185 } 1186 GNUNET_JSON_parse_free (ispec); 1187 } 1188 GNUNET_JSON_parse_free (spec); 1189 } 1190 return GNUNET_OK; 1191 } 1192 1193 1194 /** 1195 * Sign denomination keys. 1196 * 1197 * @param args the array of command-line arguments to process next 1198 */ 1199 static void 1200 do_sign (char *const *args) 1201 { 1202 json_t *keys; 1203 const char *err_name; 1204 unsigned int err_line; 1205 struct TALER_MasterPublicKeyP mpub; 1206 const json_t *denomkeys; 1207 struct GNUNET_JSON_Specification spec[] = { 1208 GNUNET_JSON_spec_array_const ("denominations", 1209 &denomkeys), 1210 GNUNET_JSON_spec_fixed_auto ("master_public_key", 1211 &mpub), 1212 GNUNET_JSON_spec_end () 1213 }; 1214 1215 keys = parse_keys ("sign"); 1216 if (NULL == keys) 1217 { 1218 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1219 "Signing failed: no valid input\n"); 1220 return; 1221 } 1222 if (GNUNET_OK != 1223 load_offline_key (GNUNET_NO)) 1224 { 1225 json_decref (keys); 1226 return; 1227 } 1228 if (GNUNET_OK != 1229 GNUNET_JSON_parse (keys, 1230 spec, 1231 &err_name, 1232 &err_line)) 1233 { 1234 fprintf (stderr, 1235 "Invalid input to 'sign': %s#%u (skipping)\n", 1236 err_name, 1237 err_line); 1238 global_ret = EXIT_FAILURE; 1239 test_shutdown (); 1240 json_decref (keys); 1241 return; 1242 } 1243 if (0 != 1244 GNUNET_memcmp (&mpub, 1245 &master_pub)) 1246 { 1247 fprintf (stderr, 1248 "Exchange master public key does not match key we have configured (aborting)\n"); 1249 global_ret = EXIT_FAILURE; 1250 test_shutdown (); 1251 json_decref (keys); 1252 return; 1253 } 1254 if (NULL == out) 1255 { 1256 out = json_array (); 1257 GNUNET_assert (NULL != out); 1258 } 1259 if (GNUNET_OK != 1260 sign_denomkeys (denomkeys)) 1261 { 1262 global_ret = EXIT_FAILURE; 1263 test_shutdown (); 1264 json_decref (keys); 1265 return; 1266 } 1267 json_decref (keys); 1268 next (args); 1269 } 1270 1271 1272 /** 1273 * Setup and output offline signing key. 1274 * 1275 * @param args the array of command-line arguments to process next 1276 */ 1277 static void 1278 do_setup (char *const *args) 1279 { 1280 if (GNUNET_OK != 1281 load_offline_key (GNUNET_YES)) 1282 { 1283 global_ret = EXIT_FAILURE; 1284 return; 1285 } 1286 if (NULL != *args) 1287 { 1288 if (NULL == out) 1289 { 1290 out = json_array (); 1291 GNUNET_assert (NULL != out); 1292 } 1293 output_operation (OP_SETUP, 1294 GNUNET_JSON_PACK ( 1295 GNUNET_JSON_pack_data_auto ("auditor_pub", 1296 &auditor_pub))); 1297 } 1298 1299 else 1300 { 1301 char *pub_s; 1302 1303 pub_s = GNUNET_STRINGS_data_to_string_alloc (&auditor_pub, 1304 sizeof (auditor_pub)); 1305 fprintf (stdout, 1306 "%s\n", 1307 pub_s); 1308 GNUNET_free (pub_s); 1309 } 1310 if ( (NULL != *args) && 1311 (0 == strcmp (*args, 1312 "-")) ) 1313 args++; 1314 next (args); 1315 } 1316 1317 1318 static void 1319 work (void *cls) 1320 { 1321 char *const *args = cls; 1322 struct SubCommand cmds[] = { 1323 { 1324 .name = "setup", 1325 .help = 1326 "setup auditor offline private key and show the public key", 1327 .cb = &do_setup 1328 }, 1329 { 1330 .name = "download", 1331 .help = 1332 "obtain keys from exchange (to be performed online!)", 1333 .cb = &do_download 1334 }, 1335 { 1336 .name = "show", 1337 .help = 1338 "display keys from exchange for human review (pass '-' as argument to disable consuming input)", 1339 .cb = &do_show 1340 }, 1341 { 1342 .name = "sign", 1343 .help = 1344 "sign all denomination keys from the input", 1345 .cb = &do_sign 1346 }, 1347 { 1348 .name = "upload", 1349 .help = 1350 "upload operation result to exchange (to be performed online!)", 1351 .cb = &do_upload 1352 }, 1353 /* list terminator */ 1354 { 1355 .name = NULL, 1356 } 1357 }; 1358 (void) cls; 1359 1360 nxt = NULL; 1361 for (unsigned int i = 0; NULL != cmds[i].name; i++) 1362 { 1363 if (0 == strcasecmp (cmds[i].name, 1364 args[0])) 1365 { 1366 cmds[i].cb (&args[1]); 1367 return; 1368 } 1369 } 1370 1371 if (0 != strcasecmp ("help", 1372 args[0])) 1373 { 1374 fprintf (stderr, 1375 "Unexpected command `%s'\n", 1376 args[0]); 1377 global_ret = EXIT_INVALIDARGUMENT; 1378 } 1379 fprintf (stderr, 1380 "Supported subcommands:\n"); 1381 for (unsigned int i = 0; NULL != cmds[i].name; i++) 1382 { 1383 fprintf (stderr, 1384 "\t%s - %s\n", 1385 cmds[i].name, 1386 cmds[i].help); 1387 } 1388 } 1389 1390 1391 /** 1392 * Main function that will be run. 1393 * 1394 * @param cls closure 1395 * @param args remaining command-line arguments 1396 * @param cfgfile name of the configuration file used (for saving, can be NULL!) 1397 * @param cfg configuration 1398 */ 1399 static void 1400 run (void *cls, 1401 char *const *args, 1402 const char *cfgfile, 1403 const struct GNUNET_CONFIGURATION_Handle *cfg) 1404 { 1405 (void) cls; 1406 (void) cfgfile; 1407 kcfg = cfg; 1408 if (GNUNET_OK != 1409 TALER_config_get_currency (kcfg, 1410 "exchange", 1411 ¤cy)) 1412 { 1413 global_ret = EXIT_NOTCONFIGURED; 1414 return; 1415 } 1416 if (GNUNET_OK != 1417 GNUNET_CONFIGURATION_get_value_string (kcfg, 1418 "auditor", 1419 "BASE_URL", 1420 &auditor_url)) 1421 { 1422 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 1423 "auditor", 1424 "BASE_URL"); 1425 global_ret = EXIT_NOTCONFIGURED; 1426 return; 1427 } 1428 { 1429 char *master_public_key_str; 1430 1431 if (GNUNET_OK != 1432 GNUNET_CONFIGURATION_get_value_string (cfg, 1433 "exchange", 1434 "MASTER_PUBLIC_KEY", 1435 &master_public_key_str)) 1436 { 1437 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 1438 "exchange", 1439 "MASTER_PUBLIC_KEY"); 1440 global_ret = EXIT_NOTCONFIGURED; 1441 return; 1442 } 1443 if (GNUNET_OK != 1444 GNUNET_CRYPTO_eddsa_public_key_from_string ( 1445 master_public_key_str, 1446 strlen (master_public_key_str), 1447 &master_pub.eddsa_pub)) 1448 { 1449 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1450 "Invalid master public key given in exchange configuration."); 1451 GNUNET_free (master_public_key_str); 1452 global_ret = EXIT_NOTCONFIGURED; 1453 return; 1454 } 1455 GNUNET_free (master_public_key_str); 1456 } 1457 ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, 1458 &rc); 1459 rc = GNUNET_CURL_gnunet_rc_create (ctx); 1460 GNUNET_SCHEDULER_add_shutdown (&do_shutdown, 1461 NULL); 1462 next (args); 1463 } 1464 1465 1466 /** 1467 * The main function of the taler-auditor-offline tool. This tool is used to 1468 * sign denomination keys with the auditor's key. It uses the long-term 1469 * offline private key of the auditor and generates signatures with it. It 1470 * also supports online operations with the exchange to download its input 1471 * data and to upload its results. Those online operations should be performed 1472 * on another machine in production! 1473 * 1474 * @param argc number of arguments from the command line 1475 * @param argv command line arguments 1476 * @return 0 ok, 1 on error 1477 */ 1478 int 1479 main (int argc, 1480 char *const *argv) 1481 { 1482 struct GNUNET_GETOPT_CommandLineOption options[] = { 1483 GNUNET_GETOPT_OPTION_END 1484 }; 1485 enum GNUNET_GenericReturnValue ret; 1486 1487 ret = GNUNET_PROGRAM_run ( 1488 TALER_AUDITOR_project_data (), 1489 argc, argv, 1490 "taler-auditor-offline", 1491 gettext_noop ("Operations for offline signing for a Taler exchange"), 1492 options, 1493 &run, NULL); 1494 if (GNUNET_SYSERR == ret) 1495 return EXIT_INVALIDARGUMENT; 1496 if (GNUNET_NO == ret) 1497 return EXIT_SUCCESS; 1498 return global_ret; 1499 } 1500 1501 1502 /* end of taler-auditor-offline.c */