taler-auditor-offline.c (40484B)
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 global_ret = EXIT_NOTCONFIGURED; 367 test_shutdown (); 368 return GNUNET_SYSERR; 369 } 370 ret = GNUNET_CRYPTO_eddsa_key_from_file (fn, 371 do_create, 372 &auditor_priv.eddsa_priv); 373 if (GNUNET_SYSERR == ret) 374 { 375 if (do_create) 376 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 377 "Failed to initialize auditor key at `%s': %s\n", 378 fn, 379 "could not create file"); 380 else 381 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 382 "Failed to load auditor key from file `%s': try running `taler-auditor-offline setup'?\n", 383 fn); 384 GNUNET_free (fn); 385 global_ret = EXIT_FAILURE; 386 test_shutdown (); 387 return GNUNET_SYSERR; 388 } 389 GNUNET_free (fn); 390 GNUNET_CRYPTO_eddsa_key_get_public (&auditor_priv.eddsa_priv, 391 &auditor_pub.eddsa_pub); 392 done = true; 393 return GNUNET_OK; 394 } 395 396 397 /** 398 * Function called with information about the post denomination (signature) 399 * add operation result. 400 * 401 * @param dar the add request 402 * @param adr response data 403 */ 404 static void 405 denomination_add_cb ( 406 struct DenominationAddRequest *dar, 407 const struct TALER_EXCHANGE_PostAuditorsResponse *adr) 408 { 409 const struct TALER_EXCHANGE_HttpResponse *hr = &adr->hr; 410 411 if (MHD_HTTP_NO_CONTENT != hr->http_status) 412 { 413 fprintf (stderr, 414 "Upload failed for command #%u with status %u: %s (%s)\n", 415 (unsigned int) dar->idx, 416 hr->http_status, 417 TALER_ErrorCode_get_hint (hr->ec), 418 NULL != hr->hint 419 ? hr->hint 420 : "no hint provided"); 421 global_ret = EXIT_FAILURE; 422 } 423 GNUNET_CONTAINER_DLL_remove (dar_head, 424 dar_tail, 425 dar); 426 GNUNET_free (dar); 427 test_shutdown (); 428 } 429 430 431 /** 432 * Upload denomination add data. 433 * 434 * @param exchange_url base URL of the exchange 435 * @param idx index of the operation we are performing (for logging) 436 * @param value arguments for adding denominations 437 */ 438 static void 439 upload_denomination_add (const char *exchange_url, 440 size_t idx, 441 const json_t *value) 442 { 443 struct TALER_AuditorSignatureP auditor_sig; 444 struct TALER_DenominationHashP h_denom_pub; 445 struct DenominationAddRequest *dar; 446 const char *err_name; 447 unsigned int err_line; 448 struct GNUNET_JSON_Specification spec[] = { 449 GNUNET_JSON_spec_fixed_auto ("h_denom_pub", 450 &h_denom_pub), 451 GNUNET_JSON_spec_fixed_auto ("auditor_sig", 452 &auditor_sig), 453 GNUNET_JSON_spec_end () 454 }; 455 456 if (GNUNET_OK != 457 GNUNET_JSON_parse (value, 458 spec, 459 &err_name, 460 &err_line)) 461 { 462 fprintf (stderr, 463 "Invalid input for adding denomination: %s#%u at %u (skipping)\n", 464 err_name, 465 err_line, 466 (unsigned int) idx); 467 global_ret = EXIT_FAILURE; 468 test_shutdown (); 469 return; 470 } 471 dar = GNUNET_new (struct DenominationAddRequest); 472 dar->idx = idx; 473 dar->h = TALER_EXCHANGE_post_auditors_create (ctx, 474 exchange_url, 475 &h_denom_pub, 476 &auditor_pub, 477 &auditor_sig); 478 GNUNET_assert (NULL != dar->h); 479 GNUNET_assert (TALER_EC_NONE == 480 TALER_EXCHANGE_post_auditors_start (dar->h, 481 &denomination_add_cb, 482 dar)); 483 GNUNET_CONTAINER_DLL_insert (dar_head, 484 dar_tail, 485 dar); 486 } 487 488 489 /** 490 * Perform uploads based on the JSON in #out. 491 * 492 * @param exchange_url base URL of the exchange to use 493 */ 494 static void 495 trigger_upload (const char *exchange_url) 496 { 497 struct UploadHandler uhs[] = { 498 { 499 .key = OP_SIGN_DENOMINATION, 500 .cb = &upload_denomination_add 501 }, 502 /* array termination */ 503 { 504 .key = NULL 505 } 506 }; 507 size_t index; 508 json_t *obj; 509 510 json_array_foreach (out, index, obj) { 511 bool found = false; 512 const char *key; 513 const json_t *value; 514 515 key = json_string_value (json_object_get (obj, "operation")); 516 value = json_object_get (obj, "arguments"); 517 if (NULL == key) 518 { 519 fprintf (stderr, 520 "Malformed JSON input\n"); 521 global_ret = EXIT_FAILURE; 522 test_shutdown (); 523 return; 524 } 525 /* block of code that uses key and value */ 526 for (unsigned int i = 0; NULL != uhs[i].key; i++) 527 { 528 if (0 == strcasecmp (key, 529 uhs[i].key)) 530 { 531 found = true; 532 uhs[i].cb (exchange_url, 533 index, 534 value); 535 break; 536 } 537 } 538 if (! found) 539 { 540 fprintf (stderr, 541 "Upload does not know how to handle `%s'\n", 542 key); 543 global_ret = EXIT_FAILURE; 544 test_shutdown (); 545 return; 546 } 547 } 548 /* test here, in case no upload was triggered (i.e. empty input) */ 549 test_shutdown (); 550 } 551 552 553 /** 554 * Upload operation result (signatures) to exchange. 555 * 556 * @param args the array of command-line arguments to process next 557 */ 558 static void 559 do_upload (char *const *args) 560 { 561 char *exchange_url; 562 563 (void) args; 564 if (GNUNET_YES == GNUNET_is_zero (&auditor_pub)) 565 { 566 /* private key not available, try configuration for public key */ 567 char *auditor_public_key_str; 568 569 if (GNUNET_OK != 570 GNUNET_CONFIGURATION_get_value_string (kcfg, 571 "auditor", 572 "PUBLIC_KEY", 573 &auditor_public_key_str)) 574 { 575 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 576 "auditor", 577 "PUBLIC_KEY"); 578 global_ret = EXIT_NOTCONFIGURED; 579 test_shutdown (); 580 return; 581 } 582 if (GNUNET_OK != 583 GNUNET_CRYPTO_eddsa_public_key_from_string ( 584 auditor_public_key_str, 585 strlen (auditor_public_key_str), 586 &auditor_pub.eddsa_pub)) 587 { 588 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, 589 "auditor", 590 "PUBLIC_KEY", 591 "invalid key"); 592 GNUNET_free (auditor_public_key_str); 593 global_ret = EXIT_NOTCONFIGURED; 594 test_shutdown (); 595 return; 596 } 597 GNUNET_free (auditor_public_key_str); 598 } 599 if (NULL != in) 600 { 601 fprintf (stderr, 602 "Downloaded data was not consumed, refusing upload\n"); 603 test_shutdown (); 604 global_ret = EXIT_FAILURE; 605 return; 606 } 607 if (NULL == out) 608 { 609 json_error_t err; 610 611 out = json_loadf (stdin, 612 JSON_REJECT_DUPLICATES, 613 &err); 614 if (NULL == out) 615 { 616 fprintf (stderr, 617 "Failed to read JSON input: %s at %d:%s (offset: %d)\n", 618 err.text, 619 err.line, 620 err.source, 621 err.position); 622 test_shutdown (); 623 global_ret = EXIT_FAILURE; 624 return; 625 } 626 } 627 if (! json_is_array (out)) 628 { 629 fprintf (stderr, 630 "Error: expected JSON array for `upload` command\n"); 631 test_shutdown (); 632 global_ret = EXIT_FAILURE; 633 return; 634 } 635 if (GNUNET_OK != 636 GNUNET_CONFIGURATION_get_value_string (kcfg, 637 "exchange", 638 "BASE_URL", 639 &exchange_url)) 640 { 641 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 642 "exchange", 643 "BASE_URL"); 644 global_ret = EXIT_NOTCONFIGURED; 645 test_shutdown (); 646 return; 647 } 648 trigger_upload (exchange_url); 649 json_decref (out); 650 out = NULL; 651 GNUNET_free (exchange_url); 652 } 653 654 655 /** 656 * Function called with information about who is auditing 657 * a particular exchange and what keys the exchange is using. 658 * 659 * @param args the remaining args 660 * @param kr response data 661 * @param keys key data from the exchange 662 */ 663 static void 664 keys_cb ( 665 char *const *args, 666 const struct TALER_EXCHANGE_KeysResponse *kr, 667 struct TALER_EXCHANGE_Keys *keys) 668 { 669 exchange = NULL; 670 switch (kr->hr.http_status) 671 { 672 case MHD_HTTP_OK: 673 if (NULL == kr->hr.reply) 674 { 675 GNUNET_break_op (0); 676 test_shutdown (); 677 global_ret = EXIT_FAILURE; 678 if (NULL != keys) 679 TALER_EXCHANGE_keys_decref (keys); 680 return; 681 } 682 break; 683 default: 684 fprintf (stderr, 685 "Failed to download keys: %s (HTTP status: %u/EC: %u)\n", 686 kr->hr.hint, 687 kr->hr.http_status, 688 (unsigned int) kr->hr.ec); 689 test_shutdown (); 690 global_ret = EXIT_FAILURE; 691 return; 692 } 693 in = GNUNET_JSON_PACK ( 694 GNUNET_JSON_pack_string ("operation", 695 OP_INPUT_KEYS), 696 GNUNET_JSON_pack_object_incref ("arguments", 697 (json_t *) kr->hr.reply)); 698 if (NULL == args[0]) 699 { 700 json_dumpf (in, 701 stdout, 702 JSON_INDENT (2)); 703 json_decref (in); 704 in = NULL; 705 } 706 next (args); 707 TALER_EXCHANGE_keys_decref (keys); 708 } 709 710 711 /** 712 * Download future keys. 713 * 714 * @param args the array of command-line arguments to process next 715 */ 716 static void 717 do_download (char *const *args) 718 { 719 char *exchange_url; 720 721 if (GNUNET_OK != 722 GNUNET_CONFIGURATION_get_value_string (kcfg, 723 "exchange", 724 "BASE_URL", 725 &exchange_url)) 726 { 727 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 728 "exchange", 729 "BASE_URL"); 730 test_shutdown (); 731 global_ret = EXIT_NOTCONFIGURED; 732 return; 733 } 734 exchange = TALER_EXCHANGE_get_keys_create (ctx, 735 exchange_url); 736 GNUNET_assert (TALER_EC_NONE == 737 TALER_EXCHANGE_get_keys_start (exchange, 738 &keys_cb, 739 args)); 740 GNUNET_free (exchange_url); 741 } 742 743 744 /** 745 * Output @a denomkeys for human consumption. 746 * 747 * @param denomkeys keys to output 748 * @return #GNUNET_OK on success 749 */ 750 static enum GNUNET_GenericReturnValue 751 show_denomkeys (const json_t *denomkeys) 752 { 753 size_t index; 754 json_t *value; 755 756 json_array_foreach (denomkeys, index, value) { 757 struct TALER_DenominationGroup group; 758 const json_t *denoms; 759 const char *err_name; 760 unsigned int err_line; 761 struct GNUNET_JSON_Specification spec[] = { 762 TALER_JSON_spec_denomination_group (NULL, 763 currency, 764 &group), 765 GNUNET_JSON_spec_array_const ("denoms", 766 &denoms), 767 GNUNET_JSON_spec_end () 768 }; 769 size_t index2; 770 json_t *value2; 771 772 if (GNUNET_OK != 773 GNUNET_JSON_parse (value, 774 spec, 775 &err_name, 776 &err_line)) 777 { 778 fprintf (stderr, 779 "Invalid input for denomination key to 'show': %s#%u at %u (skipping)\n", 780 err_name, 781 err_line, 782 (unsigned int) index); 783 GNUNET_JSON_parse_free (spec); 784 global_ret = EXIT_FAILURE; 785 test_shutdown (); 786 return GNUNET_SYSERR; 787 } 788 json_array_foreach (denoms, index2, value2) { 789 struct GNUNET_TIME_Timestamp stamp_start; 790 struct GNUNET_TIME_Timestamp stamp_expire_withdraw; 791 struct GNUNET_TIME_Timestamp stamp_expire_deposit; 792 struct GNUNET_TIME_Timestamp stamp_expire_legal; 793 struct TALER_DenominationPublicKey denom_pub; 794 struct TALER_MasterSignatureP master_sig; 795 struct GNUNET_JSON_Specification ispec[] = { 796 TALER_JSON_spec_denom_pub_cipher (NULL, 797 group.cipher, 798 &denom_pub), 799 GNUNET_JSON_spec_timestamp ("stamp_start", 800 &stamp_start), 801 GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw", 802 &stamp_expire_withdraw), 803 GNUNET_JSON_spec_timestamp ("stamp_expire_deposit", 804 &stamp_expire_deposit), 805 GNUNET_JSON_spec_timestamp ("stamp_expire_legal", 806 &stamp_expire_legal), 807 GNUNET_JSON_spec_fixed_auto ("master_sig", 808 &master_sig), 809 GNUNET_JSON_spec_end () 810 }; 811 struct GNUNET_TIME_Relative duration; 812 struct TALER_DenominationHashP h_denom_pub; 813 814 if (GNUNET_OK != 815 GNUNET_JSON_parse (value2, 816 ispec, 817 &err_name, 818 &err_line)) 819 { 820 fprintf (stderr, 821 "Invalid input for denomination key to 'show': %s#%u at %u/%u (skipping)\n", 822 err_name, 823 err_line, 824 (unsigned int) index, 825 (unsigned int) index2); 826 GNUNET_JSON_parse_free (spec); 827 global_ret = EXIT_FAILURE; 828 test_shutdown (); 829 return GNUNET_SYSERR; 830 } 831 duration = GNUNET_TIME_absolute_get_difference ( 832 stamp_start.abs_time, 833 stamp_expire_withdraw.abs_time); 834 TALER_denom_pub_hash (&denom_pub, 835 &h_denom_pub); 836 if (GNUNET_OK != 837 TALER_exchange_offline_denom_validity_verify ( 838 &h_denom_pub, 839 stamp_start, 840 stamp_expire_withdraw, 841 stamp_expire_deposit, 842 stamp_expire_legal, 843 &group.value, 844 &group.fees, 845 &master_pub, 846 &master_sig)) 847 { 848 fprintf (stderr, 849 "Invalid master signature for key %s (aborting)\n", 850 TALER_B2S (&h_denom_pub)); 851 global_ret = EXIT_FAILURE; 852 GNUNET_JSON_parse_free (ispec); 853 GNUNET_JSON_parse_free (spec); 854 test_shutdown (); 855 return GNUNET_SYSERR; 856 } 857 858 { 859 char *withdraw_fee_s; 860 char *deposit_fee_s; 861 char *refresh_fee_s; 862 char *refund_fee_s; 863 char *deposit_s; 864 char *legal_s; 865 866 withdraw_fee_s = TALER_amount_to_string (&group.fees.withdraw); 867 deposit_fee_s = TALER_amount_to_string (&group.fees.deposit); 868 refresh_fee_s = TALER_amount_to_string (&group.fees.refresh); 869 refund_fee_s = TALER_amount_to_string (&group.fees.refund); 870 deposit_s = GNUNET_strdup ( 871 GNUNET_TIME_timestamp2s (stamp_expire_deposit)); 872 legal_s = GNUNET_strdup ( 873 GNUNET_TIME_timestamp2s (stamp_expire_legal)); 874 875 printf ( 876 "DENOMINATION-KEY %s of value %s starting at %s " 877 "(used for: %s, deposit until: %s legal end: %s) with fees %s/%s/%s/%s\n", 878 TALER_B2S (&h_denom_pub), 879 TALER_amount2s (&group.value), 880 GNUNET_TIME_timestamp2s (stamp_start), 881 GNUNET_TIME_relative2s (duration, 882 false), 883 deposit_s, 884 legal_s, 885 withdraw_fee_s, 886 deposit_fee_s, 887 refresh_fee_s, 888 refund_fee_s); 889 GNUNET_free (withdraw_fee_s); 890 GNUNET_free (deposit_fee_s); 891 GNUNET_free (refresh_fee_s); 892 GNUNET_free (refund_fee_s); 893 GNUNET_free (deposit_s); 894 GNUNET_free (legal_s); 895 } 896 GNUNET_JSON_parse_free (ispec); 897 } 898 GNUNET_JSON_parse_free (spec); 899 } 900 return GNUNET_OK; 901 } 902 903 904 /** 905 * Parse the '/keys' input for operation called @a command_name. 906 * 907 * @param command_name name of the command, for logging errors 908 * @return NULL if the input is malformed 909 */ 910 static json_t * 911 parse_keys (const char *command_name) 912 { 913 json_t *keys; 914 const char *op_str; 915 struct GNUNET_JSON_Specification spec[] = { 916 GNUNET_JSON_spec_json ("arguments", 917 &keys), 918 GNUNET_JSON_spec_string ("operation", 919 &op_str), 920 GNUNET_JSON_spec_end () 921 }; 922 const char *err_name; 923 unsigned int err_line; 924 925 if (NULL == in) 926 { 927 json_error_t err; 928 929 in = json_loadf (stdin, 930 JSON_REJECT_DUPLICATES, 931 &err); 932 if (NULL == in) 933 { 934 fprintf (stderr, 935 "Failed to read JSON input: %s at %d:%s (offset: %d)\n", 936 err.text, 937 err.line, 938 err.source, 939 err.position); 940 global_ret = EXIT_FAILURE; 941 test_shutdown (); 942 return NULL; 943 } 944 } 945 if (GNUNET_OK != 946 GNUNET_JSON_parse (in, 947 spec, 948 &err_name, 949 &err_line)) 950 { 951 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 952 "Invalid input to '%s': %s#%u (skipping)\n", 953 command_name, 954 err_name, 955 err_line); 956 json_dumpf (in, 957 stderr, 958 JSON_INDENT (2)); 959 global_ret = EXIT_FAILURE; 960 test_shutdown (); 961 return NULL; 962 } 963 if (0 != strcmp (op_str, 964 OP_INPUT_KEYS)) 965 { 966 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 967 "Invalid input to '%s' : operation is `%s', expected `%s'\n", 968 command_name, 969 op_str, 970 OP_INPUT_KEYS); 971 GNUNET_JSON_parse_free (spec); 972 global_ret = EXIT_FAILURE; 973 test_shutdown (); 974 return NULL; 975 } 976 json_decref (in); 977 in = NULL; 978 return keys; 979 } 980 981 982 /** 983 * Show exchange denomination keys. 984 * 985 * @param args the array of command-line arguments to process next 986 */ 987 static void 988 do_show (char *const *args) 989 { 990 json_t *keys; 991 const char *err_name; 992 unsigned int err_line; 993 const json_t *denomkeys; 994 struct TALER_MasterPublicKeyP mpub; 995 struct GNUNET_JSON_Specification spec[] = { 996 GNUNET_JSON_spec_array_const ("denominations", 997 &denomkeys), 998 GNUNET_JSON_spec_fixed_auto ("master_public_key", 999 &mpub), 1000 GNUNET_JSON_spec_end () 1001 }; 1002 1003 keys = parse_keys ("show"); 1004 if (NULL == keys) 1005 { 1006 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1007 "Showing failed: no valid input\n"); 1008 return; 1009 } 1010 if (GNUNET_OK != 1011 GNUNET_JSON_parse (keys, 1012 spec, 1013 &err_name, 1014 &err_line)) 1015 { 1016 fprintf (stderr, 1017 "Invalid input to 'show': %s#%u (skipping)\n", 1018 err_name, 1019 err_line); 1020 global_ret = EXIT_FAILURE; 1021 test_shutdown (); 1022 json_decref (keys); 1023 return; 1024 } 1025 if (0 != 1026 GNUNET_memcmp (&mpub, 1027 &master_pub)) 1028 { 1029 fprintf (stderr, 1030 "Exchange master public key does not match key we have configured (aborting)\n"); 1031 global_ret = EXIT_FAILURE; 1032 test_shutdown (); 1033 json_decref (keys); 1034 return; 1035 } 1036 if (GNUNET_OK != 1037 show_denomkeys (denomkeys)) 1038 { 1039 global_ret = EXIT_FAILURE; 1040 test_shutdown (); 1041 json_decref (keys); 1042 return; 1043 } 1044 json_decref (keys); 1045 /* do NOT consume input if next argument is '-' */ 1046 if ( (NULL != args[0]) && 1047 (0 == strcmp ("-", 1048 args[0])) ) 1049 { 1050 next (args + 1); 1051 return; 1052 } 1053 next (args); 1054 } 1055 1056 1057 /** 1058 * Sign @a denomkeys with offline key. 1059 * 1060 * @param denomkeys keys to output 1061 * @return #GNUNET_OK on success 1062 */ 1063 static enum GNUNET_GenericReturnValue 1064 sign_denomkeys (const json_t *denomkeys) 1065 { 1066 size_t group_idx; 1067 json_t *value; 1068 1069 json_array_foreach (denomkeys, group_idx, value) { 1070 struct TALER_DenominationGroup group = { 0 }; 1071 const json_t *denom_keys_array; 1072 const char *err_name; 1073 unsigned int err_line; 1074 struct GNUNET_JSON_Specification spec[] = { 1075 TALER_JSON_spec_denomination_group (NULL, 1076 currency, 1077 &group), 1078 GNUNET_JSON_spec_array_const ("denoms", 1079 &denom_keys_array), 1080 GNUNET_JSON_spec_end () 1081 }; 1082 size_t index; 1083 json_t *denom_key_obj; 1084 1085 if (GNUNET_OK != 1086 GNUNET_JSON_parse (value, 1087 spec, 1088 &err_name, 1089 &err_line)) 1090 { 1091 fprintf (stderr, 1092 "Invalid input for denomination key to 'sign': %s#%u at %u (skipping)\n", 1093 err_name, 1094 err_line, 1095 (unsigned int) group_idx); 1096 GNUNET_JSON_parse_free (spec); 1097 global_ret = EXIT_FAILURE; 1098 test_shutdown (); 1099 return GNUNET_SYSERR; 1100 } 1101 json_array_foreach (denom_keys_array, index, denom_key_obj) { 1102 struct GNUNET_TIME_Timestamp stamp_start; 1103 struct GNUNET_TIME_Timestamp stamp_expire_withdraw; 1104 struct GNUNET_TIME_Timestamp stamp_expire_deposit; 1105 struct GNUNET_TIME_Timestamp stamp_expire_legal; 1106 struct TALER_DenominationPublicKey denom_pub = { 1107 .age_mask = group.age_mask 1108 }; 1109 struct TALER_MasterSignatureP master_sig; 1110 struct GNUNET_JSON_Specification ispec[] = { 1111 TALER_JSON_spec_denom_pub_cipher (NULL, 1112 group.cipher, 1113 &denom_pub), 1114 GNUNET_JSON_spec_timestamp ("stamp_start", 1115 &stamp_start), 1116 GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw", 1117 &stamp_expire_withdraw), 1118 GNUNET_JSON_spec_timestamp ("stamp_expire_deposit", 1119 &stamp_expire_deposit), 1120 GNUNET_JSON_spec_timestamp ("stamp_expire_legal", 1121 &stamp_expire_legal), 1122 GNUNET_JSON_spec_fixed_auto ("master_sig", 1123 &master_sig), 1124 GNUNET_JSON_spec_end () 1125 }; 1126 struct TALER_DenominationHashP h_denom_pub; 1127 1128 if (GNUNET_OK != 1129 GNUNET_JSON_parse (denom_key_obj, 1130 ispec, 1131 &err_name, 1132 &err_line)) 1133 { 1134 fprintf (stderr, 1135 "Invalid input for denomination key to 'sign': %s#%u at %u/%u (skipping)\n", 1136 err_name, 1137 err_line, 1138 (unsigned int) group_idx, 1139 (unsigned int) index); 1140 GNUNET_JSON_parse_free (spec); 1141 global_ret = EXIT_FAILURE; 1142 test_shutdown (); 1143 return GNUNET_SYSERR; 1144 } 1145 TALER_denom_pub_hash (&denom_pub, 1146 &h_denom_pub); 1147 if (GNUNET_OK != 1148 TALER_exchange_offline_denom_validity_verify ( 1149 &h_denom_pub, 1150 stamp_start, 1151 stamp_expire_withdraw, 1152 stamp_expire_deposit, 1153 stamp_expire_legal, 1154 &group.value, 1155 &group.fees, 1156 &master_pub, 1157 &master_sig)) 1158 { 1159 fprintf (stderr, 1160 "Invalid master signature for key %s (aborting)\n", 1161 TALER_B2S (&h_denom_pub)); 1162 global_ret = EXIT_FAILURE; 1163 test_shutdown (); 1164 return GNUNET_SYSERR; 1165 } 1166 1167 { 1168 struct TALER_AuditorSignatureP auditor_sig; 1169 1170 TALER_auditor_denom_validity_sign (auditor_url, 1171 &h_denom_pub, 1172 &master_pub, 1173 stamp_start, 1174 stamp_expire_withdraw, 1175 stamp_expire_deposit, 1176 stamp_expire_legal, 1177 &group.value, 1178 &group.fees, 1179 &auditor_priv, 1180 &auditor_sig); 1181 output_operation (OP_SIGN_DENOMINATION, 1182 GNUNET_JSON_PACK ( 1183 GNUNET_JSON_pack_data_auto ("h_denom_pub", 1184 &h_denom_pub), 1185 GNUNET_JSON_pack_data_auto ("auditor_sig", 1186 &auditor_sig))); 1187 } 1188 GNUNET_JSON_parse_free (ispec); 1189 } 1190 GNUNET_JSON_parse_free (spec); 1191 } 1192 return GNUNET_OK; 1193 } 1194 1195 1196 /** 1197 * Sign denomination keys. 1198 * 1199 * @param args the array of command-line arguments to process next 1200 */ 1201 static void 1202 do_sign (char *const *args) 1203 { 1204 json_t *keys; 1205 const char *err_name; 1206 unsigned int err_line; 1207 struct TALER_MasterPublicKeyP mpub; 1208 const json_t *denomkeys; 1209 struct GNUNET_JSON_Specification spec[] = { 1210 GNUNET_JSON_spec_array_const ("denominations", 1211 &denomkeys), 1212 GNUNET_JSON_spec_fixed_auto ("master_public_key", 1213 &mpub), 1214 GNUNET_JSON_spec_end () 1215 }; 1216 1217 keys = parse_keys ("sign"); 1218 if (NULL == keys) 1219 { 1220 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1221 "Signing failed: no valid input\n"); 1222 return; 1223 } 1224 if (GNUNET_OK != 1225 load_offline_key (GNUNET_NO)) 1226 { 1227 json_decref (keys); 1228 return; 1229 } 1230 if (GNUNET_OK != 1231 GNUNET_JSON_parse (keys, 1232 spec, 1233 &err_name, 1234 &err_line)) 1235 { 1236 fprintf (stderr, 1237 "Invalid input to 'sign': %s#%u (skipping)\n", 1238 err_name, 1239 err_line); 1240 global_ret = EXIT_FAILURE; 1241 test_shutdown (); 1242 json_decref (keys); 1243 return; 1244 } 1245 if (0 != 1246 GNUNET_memcmp (&mpub, 1247 &master_pub)) 1248 { 1249 fprintf (stderr, 1250 "Exchange master public key does not match key we have configured (aborting)\n"); 1251 global_ret = EXIT_FAILURE; 1252 test_shutdown (); 1253 json_decref (keys); 1254 return; 1255 } 1256 if (NULL == out) 1257 { 1258 out = json_array (); 1259 GNUNET_assert (NULL != out); 1260 } 1261 if (GNUNET_OK != 1262 sign_denomkeys (denomkeys)) 1263 { 1264 global_ret = EXIT_FAILURE; 1265 test_shutdown (); 1266 json_decref (keys); 1267 return; 1268 } 1269 json_decref (keys); 1270 next (args); 1271 } 1272 1273 1274 /** 1275 * Setup and output offline signing key. 1276 * 1277 * @param args the array of command-line arguments to process next 1278 */ 1279 static void 1280 do_setup (char *const *args) 1281 { 1282 if (GNUNET_OK != 1283 load_offline_key (GNUNET_YES)) 1284 { 1285 global_ret = EXIT_FAILURE; 1286 return; 1287 } 1288 if (NULL != *args) 1289 { 1290 if (NULL == out) 1291 { 1292 out = json_array (); 1293 GNUNET_assert (NULL != out); 1294 } 1295 output_operation (OP_SETUP, 1296 GNUNET_JSON_PACK ( 1297 GNUNET_JSON_pack_data_auto ("auditor_pub", 1298 &auditor_pub))); 1299 } 1300 1301 else 1302 { 1303 char *pub_s; 1304 1305 pub_s = GNUNET_STRINGS_data_to_string_alloc (&auditor_pub, 1306 sizeof (auditor_pub)); 1307 fprintf (stdout, 1308 "%s\n", 1309 pub_s); 1310 GNUNET_free (pub_s); 1311 } 1312 if ( (NULL != *args) && 1313 (0 == strcmp (*args, 1314 "-")) ) 1315 args++; 1316 next (args); 1317 } 1318 1319 1320 static void 1321 work (void *cls) 1322 { 1323 char *const *args = cls; 1324 struct SubCommand cmds[] = { 1325 { 1326 .name = "setup", 1327 .help = 1328 "setup auditor offline private key and show the public key", 1329 .cb = &do_setup 1330 }, 1331 { 1332 .name = "download", 1333 .help = 1334 "obtain keys from exchange (to be performed online!)", 1335 .cb = &do_download 1336 }, 1337 { 1338 .name = "show", 1339 .help = 1340 "display keys from exchange for human review (pass '-' as argument to disable consuming input)", 1341 .cb = &do_show 1342 }, 1343 { 1344 .name = "sign", 1345 .help = 1346 "sign all denomination keys from the input", 1347 .cb = &do_sign 1348 }, 1349 { 1350 .name = "upload", 1351 .help = 1352 "upload operation result to exchange (to be performed online!)", 1353 .cb = &do_upload 1354 }, 1355 /* list terminator */ 1356 { 1357 .name = NULL, 1358 } 1359 }; 1360 (void) cls; 1361 1362 nxt = NULL; 1363 for (unsigned int i = 0; NULL != cmds[i].name; i++) 1364 { 1365 if (0 == strcasecmp (cmds[i].name, 1366 args[0])) 1367 { 1368 cmds[i].cb (&args[1]); 1369 return; 1370 } 1371 } 1372 1373 if (0 != strcasecmp ("help", 1374 args[0])) 1375 { 1376 fprintf (stderr, 1377 "Unexpected command `%s'\n", 1378 args[0]); 1379 global_ret = EXIT_INVALIDARGUMENT; 1380 } 1381 fprintf (stderr, 1382 "Supported subcommands:\n"); 1383 for (unsigned int i = 0; NULL != cmds[i].name; i++) 1384 { 1385 fprintf (stderr, 1386 "\t%s - %s\n", 1387 cmds[i].name, 1388 cmds[i].help); 1389 } 1390 } 1391 1392 1393 /** 1394 * Main function that will be run. 1395 * 1396 * @param cls closure 1397 * @param args remaining command-line arguments 1398 * @param cfgfile name of the configuration file used (for saving, can be NULL!) 1399 * @param cfg configuration 1400 */ 1401 static void 1402 run (void *cls, 1403 char *const *args, 1404 const char *cfgfile, 1405 const struct GNUNET_CONFIGURATION_Handle *cfg) 1406 { 1407 (void) cls; 1408 (void) cfgfile; 1409 kcfg = cfg; 1410 if (GNUNET_OK != 1411 TALER_config_get_currency (kcfg, 1412 "exchange", 1413 ¤cy)) 1414 { 1415 global_ret = EXIT_NOTCONFIGURED; 1416 return; 1417 } 1418 if (GNUNET_OK != 1419 GNUNET_CONFIGURATION_get_value_string (kcfg, 1420 "auditor", 1421 "BASE_URL", 1422 &auditor_url)) 1423 { 1424 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 1425 "auditor", 1426 "BASE_URL"); 1427 global_ret = EXIT_NOTCONFIGURED; 1428 return; 1429 } 1430 { 1431 char *master_public_key_str; 1432 1433 if (GNUNET_OK != 1434 GNUNET_CONFIGURATION_get_value_string (cfg, 1435 "exchange", 1436 "MASTER_PUBLIC_KEY", 1437 &master_public_key_str)) 1438 { 1439 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 1440 "exchange", 1441 "MASTER_PUBLIC_KEY"); 1442 global_ret = EXIT_NOTCONFIGURED; 1443 return; 1444 } 1445 if (GNUNET_OK != 1446 GNUNET_CRYPTO_eddsa_public_key_from_string ( 1447 master_public_key_str, 1448 strlen (master_public_key_str), 1449 &master_pub.eddsa_pub)) 1450 { 1451 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1452 "Invalid master public key given in exchange configuration."); 1453 GNUNET_free (master_public_key_str); 1454 global_ret = EXIT_NOTCONFIGURED; 1455 return; 1456 } 1457 GNUNET_free (master_public_key_str); 1458 } 1459 ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, 1460 &rc); 1461 rc = GNUNET_CURL_gnunet_rc_create (ctx); 1462 GNUNET_SCHEDULER_add_shutdown (&do_shutdown, 1463 NULL); 1464 next (args); 1465 } 1466 1467 1468 /** 1469 * The main function of the taler-auditor-offline tool. This tool is used to 1470 * sign denomination keys with the auditor's key. It uses the long-term 1471 * offline private key of the auditor and generates signatures with it. It 1472 * also supports online operations with the exchange to download its input 1473 * data and to upload its results. Those online operations should be performed 1474 * on another machine in production! 1475 * 1476 * @param argc number of arguments from the command line 1477 * @param argv command line arguments 1478 * @return 0 ok, 1 on error 1479 */ 1480 int 1481 main (int argc, 1482 char *const *argv) 1483 { 1484 struct GNUNET_GETOPT_CommandLineOption options[] = { 1485 GNUNET_GETOPT_OPTION_END 1486 }; 1487 enum GNUNET_GenericReturnValue ret; 1488 1489 ret = GNUNET_PROGRAM_run ( 1490 TALER_AUDITOR_project_data (), 1491 argc, argv, 1492 "taler-auditor-offline", 1493 gettext_noop ("Operations for offline signing for a Taler exchange"), 1494 options, 1495 &run, NULL); 1496 if (GNUNET_SYSERR == ret) 1497 return EXIT_INVALIDARGUMENT; 1498 if (GNUNET_NO == ret) 1499 return EXIT_SUCCESS; 1500 return global_ret; 1501 } 1502 1503 1504 /* end of taler-auditor-offline.c */