taler-auditor-offline.c (40221B)
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 "taler/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/taler-exchange/get-keys.h" 29 30 struct DenominationAddRequest; 31 #define TALER_EXCHANGE_POST_AUDITORS_RESULT_CLOSURE \ 32 struct DenominationAddRequest 33 #include "taler/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 * Show 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 wire 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 "Darning: 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 denomination revocation 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 (0); 674 test_shutdown (); 675 global_ret = EXIT_FAILURE; 676 return; 677 } 678 break; 679 default: 680 fprintf (stderr, 681 "Failed to download keys: %s (HTTP status: %u/%u)\n", 682 kr->hr.hint, 683 kr->hr.http_status, 684 (unsigned int) kr->hr.ec); 685 test_shutdown (); 686 global_ret = EXIT_FAILURE; 687 return; 688 } 689 in = GNUNET_JSON_PACK ( 690 GNUNET_JSON_pack_string ("operation", 691 OP_INPUT_KEYS), 692 GNUNET_JSON_pack_object_incref ("arguments", 693 (json_t *) kr->hr.reply)); 694 if (NULL == args[0]) 695 { 696 json_dumpf (in, 697 stdout, 698 JSON_INDENT (2)); 699 json_decref (in); 700 in = NULL; 701 } 702 next (args); 703 TALER_EXCHANGE_keys_decref (keys); 704 } 705 706 707 /** 708 * Download future keys. 709 * 710 * @param args the array of command-line arguments to process next 711 */ 712 static void 713 do_download (char *const *args) 714 { 715 char *exchange_url; 716 717 if (GNUNET_OK != 718 GNUNET_CONFIGURATION_get_value_string (kcfg, 719 "exchange", 720 "BASE_URL", 721 &exchange_url)) 722 { 723 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 724 "exchange", 725 "BASE_URL"); 726 test_shutdown (); 727 global_ret = EXIT_NOTCONFIGURED; 728 return; 729 } 730 exchange = TALER_EXCHANGE_get_keys_create (ctx, 731 exchange_url); 732 TALER_EXCHANGE_get_keys_start (exchange, 733 &keys_cb, 734 args); 735 GNUNET_free (exchange_url); 736 } 737 738 739 /** 740 * Output @a denomkeys for human consumption. 741 * 742 * @param denomkeys keys to output 743 * @return #GNUNET_OK on success 744 */ 745 static enum GNUNET_GenericReturnValue 746 show_denomkeys (const json_t *denomkeys) 747 { 748 size_t index; 749 json_t *value; 750 751 json_array_foreach (denomkeys, index, value) { 752 struct TALER_DenominationGroup group; 753 const json_t *denoms; 754 const char *err_name; 755 unsigned int err_line; 756 struct GNUNET_JSON_Specification spec[] = { 757 TALER_JSON_spec_denomination_group (NULL, 758 currency, 759 &group), 760 GNUNET_JSON_spec_array_const ("denoms", 761 &denoms), 762 GNUNET_JSON_spec_end () 763 }; 764 size_t index2; 765 json_t *value2; 766 767 if (GNUNET_OK != 768 GNUNET_JSON_parse (value, 769 spec, 770 &err_name, 771 &err_line)) 772 { 773 fprintf (stderr, 774 "Invalid input for denomination key to 'show': %s#%u at %u (skipping)\n", 775 err_name, 776 err_line, 777 (unsigned int) index); 778 GNUNET_JSON_parse_free (spec); 779 global_ret = EXIT_FAILURE; 780 test_shutdown (); 781 return GNUNET_SYSERR; 782 } 783 json_array_foreach (denoms, index2, value2) { 784 struct GNUNET_TIME_Timestamp stamp_start; 785 struct GNUNET_TIME_Timestamp stamp_expire_withdraw; 786 struct GNUNET_TIME_Timestamp stamp_expire_deposit; 787 struct GNUNET_TIME_Timestamp stamp_expire_legal; 788 struct TALER_DenominationPublicKey denom_pub; 789 struct TALER_MasterSignatureP master_sig; 790 struct GNUNET_JSON_Specification ispec[] = { 791 TALER_JSON_spec_denom_pub_cipher (NULL, 792 group.cipher, 793 &denom_pub), 794 GNUNET_JSON_spec_timestamp ("stamp_start", 795 &stamp_start), 796 GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw", 797 &stamp_expire_withdraw), 798 GNUNET_JSON_spec_timestamp ("stamp_expire_deposit", 799 &stamp_expire_deposit), 800 GNUNET_JSON_spec_timestamp ("stamp_expire_legal", 801 &stamp_expire_legal), 802 GNUNET_JSON_spec_fixed_auto ("master_sig", 803 &master_sig), 804 GNUNET_JSON_spec_end () 805 }; 806 struct GNUNET_TIME_Relative duration; 807 struct TALER_DenominationHashP h_denom_pub; 808 809 if (GNUNET_OK != 810 GNUNET_JSON_parse (value2, 811 ispec, 812 &err_name, 813 &err_line)) 814 { 815 fprintf (stderr, 816 "Invalid input for denomination key to 'show': %s#%u at %u/%u (skipping)\n", 817 err_name, 818 err_line, 819 (unsigned int) index, 820 (unsigned int) index2); 821 GNUNET_JSON_parse_free (spec); 822 global_ret = EXIT_FAILURE; 823 test_shutdown (); 824 return GNUNET_SYSERR; 825 } 826 duration = GNUNET_TIME_absolute_get_difference ( 827 stamp_start.abs_time, 828 stamp_expire_withdraw.abs_time); 829 TALER_denom_pub_hash (&denom_pub, 830 &h_denom_pub); 831 if (GNUNET_OK != 832 TALER_exchange_offline_denom_validity_verify ( 833 &h_denom_pub, 834 stamp_start, 835 stamp_expire_withdraw, 836 stamp_expire_deposit, 837 stamp_expire_legal, 838 &group.value, 839 &group.fees, 840 &master_pub, 841 &master_sig)) 842 { 843 fprintf (stderr, 844 "Invalid master signature for key %s (aborting)\n", 845 TALER_B2S (&h_denom_pub)); 846 global_ret = EXIT_FAILURE; 847 GNUNET_JSON_parse_free (ispec); 848 GNUNET_JSON_parse_free (spec); 849 test_shutdown (); 850 return GNUNET_SYSERR; 851 } 852 853 { 854 char *withdraw_fee_s; 855 char *deposit_fee_s; 856 char *refresh_fee_s; 857 char *refund_fee_s; 858 char *deposit_s; 859 char *legal_s; 860 861 withdraw_fee_s = TALER_amount_to_string (&group.fees.withdraw); 862 deposit_fee_s = TALER_amount_to_string (&group.fees.deposit); 863 refresh_fee_s = TALER_amount_to_string (&group.fees.refresh); 864 refund_fee_s = TALER_amount_to_string (&group.fees.refund); 865 deposit_s = GNUNET_strdup ( 866 GNUNET_TIME_timestamp2s (stamp_expire_deposit)); 867 legal_s = GNUNET_strdup ( 868 GNUNET_TIME_timestamp2s (stamp_expire_legal)); 869 870 printf ( 871 "DENOMINATION-KEY %s of value %s starting at %s " 872 "(used for: %s, deposit until: %s legal end: %s) with fees %s/%s/%s/%s\n", 873 TALER_B2S (&h_denom_pub), 874 TALER_amount2s (&group.value), 875 GNUNET_TIME_timestamp2s (stamp_start), 876 GNUNET_TIME_relative2s (duration, 877 false), 878 deposit_s, 879 legal_s, 880 withdraw_fee_s, 881 deposit_fee_s, 882 refresh_fee_s, 883 refund_fee_s); 884 GNUNET_free (withdraw_fee_s); 885 GNUNET_free (deposit_fee_s); 886 GNUNET_free (refresh_fee_s); 887 GNUNET_free (refund_fee_s); 888 GNUNET_free (deposit_s); 889 GNUNET_free (legal_s); 890 } 891 GNUNET_JSON_parse_free (ispec); 892 } 893 GNUNET_JSON_parse_free (spec); 894 } 895 return GNUNET_OK; 896 } 897 898 899 /** 900 * Parse the '/keys' input for operation called @a command_name. 901 * 902 * @param command_name name of the command, for logging errors 903 * @return NULL if the input is malformed 904 */ 905 static json_t * 906 parse_keys (const char *command_name) 907 { 908 json_t *keys; 909 const char *op_str; 910 struct GNUNET_JSON_Specification spec[] = { 911 GNUNET_JSON_spec_json ("arguments", 912 &keys), 913 GNUNET_JSON_spec_string ("operation", 914 &op_str), 915 GNUNET_JSON_spec_end () 916 }; 917 const char *err_name; 918 unsigned int err_line; 919 920 if (NULL == in) 921 { 922 json_error_t err; 923 924 in = json_loadf (stdin, 925 JSON_REJECT_DUPLICATES, 926 &err); 927 if (NULL == in) 928 { 929 fprintf (stderr, 930 "Failed to read JSON input: %s at %d:%s (offset: %d)\n", 931 err.text, 932 err.line, 933 err.source, 934 err.position); 935 global_ret = EXIT_FAILURE; 936 test_shutdown (); 937 return NULL; 938 } 939 } 940 if (GNUNET_OK != 941 GNUNET_JSON_parse (in, 942 spec, 943 &err_name, 944 &err_line)) 945 { 946 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 947 "Invalid input to '%s': %s#%u (skipping)\n", 948 command_name, 949 err_name, 950 err_line); 951 json_dumpf (in, 952 stderr, 953 JSON_INDENT (2)); 954 global_ret = EXIT_FAILURE; 955 test_shutdown (); 956 return NULL; 957 } 958 if (0 != strcmp (op_str, 959 OP_INPUT_KEYS)) 960 { 961 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 962 "Invalid input to '%s' : operation is `%s', expected `%s'\n", 963 command_name, 964 op_str, 965 OP_INPUT_KEYS); 966 GNUNET_JSON_parse_free (spec); 967 return NULL; 968 } 969 json_decref (in); 970 in = NULL; 971 return keys; 972 } 973 974 975 /** 976 * Show exchange denomination keys. 977 * 978 * @param args the array of command-line arguments to process next 979 */ 980 static void 981 do_show (char *const *args) 982 { 983 json_t *keys; 984 const char *err_name; 985 unsigned int err_line; 986 const json_t *denomkeys; 987 struct TALER_MasterPublicKeyP mpub; 988 struct GNUNET_JSON_Specification spec[] = { 989 GNUNET_JSON_spec_array_const ("denominations", 990 &denomkeys), 991 GNUNET_JSON_spec_fixed_auto ("master_public_key", 992 &mpub), 993 GNUNET_JSON_spec_end () 994 }; 995 996 keys = parse_keys ("show"); 997 if (NULL == keys) 998 { 999 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1000 "Showing failed: no valid input\n"); 1001 return; 1002 } 1003 if (GNUNET_OK != 1004 GNUNET_JSON_parse (keys, 1005 spec, 1006 &err_name, 1007 &err_line)) 1008 { 1009 fprintf (stderr, 1010 "Invalid input to 'show': %s#%u (skipping)\n", 1011 err_name, 1012 err_line); 1013 global_ret = EXIT_FAILURE; 1014 test_shutdown (); 1015 json_decref (keys); 1016 return; 1017 } 1018 if (0 != 1019 GNUNET_memcmp (&mpub, 1020 &master_pub)) 1021 { 1022 fprintf (stderr, 1023 "Exchange master public key does not match key we have configured (aborting)\n"); 1024 global_ret = EXIT_FAILURE; 1025 test_shutdown (); 1026 json_decref (keys); 1027 return; 1028 } 1029 if (GNUNET_OK != 1030 show_denomkeys (denomkeys)) 1031 { 1032 global_ret = EXIT_FAILURE; 1033 test_shutdown (); 1034 json_decref (keys); 1035 return; 1036 } 1037 json_decref (keys); 1038 /* do NOT consume input if next argument is '-' */ 1039 if ( (NULL != args[0]) && 1040 (0 == strcmp ("-", 1041 args[0])) ) 1042 { 1043 next (args + 1); 1044 return; 1045 } 1046 next (args); 1047 } 1048 1049 1050 /** 1051 * Sign @a denomkeys with offline key. 1052 * 1053 * @param denomkeys keys to output 1054 * @return #GNUNET_OK on success 1055 */ 1056 static enum GNUNET_GenericReturnValue 1057 sign_denomkeys (const json_t *denomkeys) 1058 { 1059 size_t group_idx; 1060 json_t *value; 1061 1062 json_array_foreach (denomkeys, group_idx, value) { 1063 struct TALER_DenominationGroup group = { 0 }; 1064 const json_t *denom_keys_array; 1065 const char *err_name; 1066 unsigned int err_line; 1067 struct GNUNET_JSON_Specification spec[] = { 1068 TALER_JSON_spec_denomination_group (NULL, 1069 currency, 1070 &group), 1071 GNUNET_JSON_spec_array_const ("denoms", 1072 &denom_keys_array), 1073 GNUNET_JSON_spec_end () 1074 }; 1075 size_t index; 1076 json_t *denom_key_obj; 1077 1078 if (GNUNET_OK != 1079 GNUNET_JSON_parse (value, 1080 spec, 1081 &err_name, 1082 &err_line)) 1083 { 1084 fprintf (stderr, 1085 "Invalid input for denomination key to 'sign': %s#%u at %u (skipping)\n", 1086 err_name, 1087 err_line, 1088 (unsigned int) group_idx); 1089 GNUNET_JSON_parse_free (spec); 1090 global_ret = EXIT_FAILURE; 1091 test_shutdown (); 1092 return GNUNET_SYSERR; 1093 } 1094 json_array_foreach (denom_keys_array, index, denom_key_obj) { 1095 struct GNUNET_TIME_Timestamp stamp_start; 1096 struct GNUNET_TIME_Timestamp stamp_expire_withdraw; 1097 struct GNUNET_TIME_Timestamp stamp_expire_deposit; 1098 struct GNUNET_TIME_Timestamp stamp_expire_legal; 1099 struct TALER_DenominationPublicKey denom_pub = { 1100 .age_mask = group.age_mask 1101 }; 1102 struct TALER_MasterSignatureP master_sig; 1103 struct GNUNET_JSON_Specification ispec[] = { 1104 TALER_JSON_spec_denom_pub_cipher (NULL, 1105 group.cipher, 1106 &denom_pub), 1107 GNUNET_JSON_spec_timestamp ("stamp_start", 1108 &stamp_start), 1109 GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw", 1110 &stamp_expire_withdraw), 1111 GNUNET_JSON_spec_timestamp ("stamp_expire_deposit", 1112 &stamp_expire_deposit), 1113 GNUNET_JSON_spec_timestamp ("stamp_expire_legal", 1114 &stamp_expire_legal), 1115 GNUNET_JSON_spec_fixed_auto ("master_sig", 1116 &master_sig), 1117 GNUNET_JSON_spec_end () 1118 }; 1119 struct TALER_DenominationHashP h_denom_pub; 1120 1121 if (GNUNET_OK != 1122 GNUNET_JSON_parse (denom_key_obj, 1123 ispec, 1124 &err_name, 1125 &err_line)) 1126 { 1127 fprintf (stderr, 1128 "Invalid input for denomination key to 'show': %s#%u at %u/%u (skipping)\n", 1129 err_name, 1130 err_line, 1131 (unsigned int) group_idx, 1132 (unsigned int) index); 1133 GNUNET_JSON_parse_free (spec); 1134 global_ret = EXIT_FAILURE; 1135 test_shutdown (); 1136 return GNUNET_SYSERR; 1137 } 1138 TALER_denom_pub_hash (&denom_pub, 1139 &h_denom_pub); 1140 if (GNUNET_OK != 1141 TALER_exchange_offline_denom_validity_verify ( 1142 &h_denom_pub, 1143 stamp_start, 1144 stamp_expire_withdraw, 1145 stamp_expire_deposit, 1146 stamp_expire_legal, 1147 &group.value, 1148 &group.fees, 1149 &master_pub, 1150 &master_sig)) 1151 { 1152 fprintf (stderr, 1153 "Invalid master signature for key %s (aborting)\n", 1154 TALER_B2S (&h_denom_pub)); 1155 global_ret = EXIT_FAILURE; 1156 test_shutdown (); 1157 return GNUNET_SYSERR; 1158 } 1159 1160 { 1161 struct TALER_AuditorSignatureP auditor_sig; 1162 1163 TALER_auditor_denom_validity_sign (auditor_url, 1164 &h_denom_pub, 1165 &master_pub, 1166 stamp_start, 1167 stamp_expire_withdraw, 1168 stamp_expire_deposit, 1169 stamp_expire_legal, 1170 &group.value, 1171 &group.fees, 1172 &auditor_priv, 1173 &auditor_sig); 1174 output_operation (OP_SIGN_DENOMINATION, 1175 GNUNET_JSON_PACK ( 1176 GNUNET_JSON_pack_data_auto ("h_denom_pub", 1177 &h_denom_pub), 1178 GNUNET_JSON_pack_data_auto ("auditor_sig", 1179 &auditor_sig))); 1180 } 1181 GNUNET_JSON_parse_free (ispec); 1182 } 1183 GNUNET_JSON_parse_free (spec); 1184 } 1185 return GNUNET_OK; 1186 } 1187 1188 1189 /** 1190 * Sign denomination keys. 1191 * 1192 * @param args the array of command-line arguments to process next 1193 */ 1194 static void 1195 do_sign (char *const *args) 1196 { 1197 json_t *keys; 1198 const char *err_name; 1199 unsigned int err_line; 1200 struct TALER_MasterPublicKeyP mpub; 1201 const json_t *denomkeys; 1202 struct GNUNET_JSON_Specification spec[] = { 1203 GNUNET_JSON_spec_array_const ("denominations", 1204 &denomkeys), 1205 GNUNET_JSON_spec_fixed_auto ("master_public_key", 1206 &mpub), 1207 GNUNET_JSON_spec_end () 1208 }; 1209 1210 keys = parse_keys ("sign"); 1211 if (NULL == keys) 1212 { 1213 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1214 "Signing failed: no valid input\n"); 1215 return; 1216 } 1217 if (GNUNET_OK != 1218 load_offline_key (GNUNET_NO)) 1219 { 1220 json_decref (keys); 1221 return; 1222 } 1223 if (GNUNET_OK != 1224 GNUNET_JSON_parse (keys, 1225 spec, 1226 &err_name, 1227 &err_line)) 1228 { 1229 fprintf (stderr, 1230 "Invalid input to 'sign': %s#%u (skipping)\n", 1231 err_name, 1232 err_line); 1233 global_ret = EXIT_FAILURE; 1234 test_shutdown (); 1235 json_decref (keys); 1236 return; 1237 } 1238 if (0 != 1239 GNUNET_memcmp (&mpub, 1240 &master_pub)) 1241 { 1242 fprintf (stderr, 1243 "Exchange master public key does not match key we have configured (aborting)\n"); 1244 global_ret = EXIT_FAILURE; 1245 test_shutdown (); 1246 json_decref (keys); 1247 return; 1248 } 1249 if (NULL == out) 1250 { 1251 out = json_array (); 1252 GNUNET_assert (NULL != out); 1253 } 1254 if (GNUNET_OK != 1255 sign_denomkeys (denomkeys)) 1256 { 1257 global_ret = EXIT_FAILURE; 1258 test_shutdown (); 1259 json_decref (keys); 1260 return; 1261 } 1262 json_decref (keys); 1263 next (args); 1264 } 1265 1266 1267 /** 1268 * Setup and output offline signing key. 1269 * 1270 * @param args the array of command-line arguments to process next 1271 */ 1272 static void 1273 do_setup (char *const *args) 1274 { 1275 if (GNUNET_OK != 1276 load_offline_key (GNUNET_YES)) 1277 { 1278 global_ret = EXIT_FAILURE; 1279 return; 1280 } 1281 if (NULL != *args) 1282 { 1283 if (NULL == out) 1284 { 1285 out = json_array (); 1286 GNUNET_assert (NULL != out); 1287 } 1288 output_operation (OP_SETUP, 1289 GNUNET_JSON_PACK ( 1290 GNUNET_JSON_pack_data_auto ("auditor_pub", 1291 &auditor_pub))); 1292 } 1293 1294 else 1295 { 1296 char *pub_s; 1297 1298 pub_s = GNUNET_STRINGS_data_to_string_alloc (&auditor_pub, 1299 sizeof (auditor_pub)); 1300 fprintf (stdout, 1301 "%s\n", 1302 pub_s); 1303 GNUNET_free (pub_s); 1304 } 1305 if ( (NULL != *args) && 1306 (0 == strcmp (*args, 1307 "-")) ) 1308 args++; 1309 next (args); 1310 } 1311 1312 1313 static void 1314 work (void *cls) 1315 { 1316 char *const *args = cls; 1317 struct SubCommand cmds[] = { 1318 { 1319 .name = "setup", 1320 .help = 1321 "setup auditor offline private key and show the public key", 1322 .cb = &do_setup 1323 }, 1324 { 1325 .name = "download", 1326 .help = 1327 "obtain keys from exchange (to be performed online!)", 1328 .cb = &do_download 1329 }, 1330 { 1331 .name = "show", 1332 .help = 1333 "display keys from exchange for human review (pass '-' as argument to disable consuming input)", 1334 .cb = &do_show 1335 }, 1336 { 1337 .name = "sign", 1338 .help = 1339 "sing all denomination keys from the input", 1340 .cb = &do_sign 1341 }, 1342 { 1343 .name = "upload", 1344 .help = 1345 "upload operation result to exchange (to be performed online!)", 1346 .cb = &do_upload 1347 }, 1348 /* list terminator */ 1349 { 1350 .name = NULL, 1351 } 1352 }; 1353 (void) cls; 1354 1355 nxt = NULL; 1356 for (unsigned int i = 0; NULL != cmds[i].name; i++) 1357 { 1358 if (0 == strcasecmp (cmds[i].name, 1359 args[0])) 1360 { 1361 cmds[i].cb (&args[1]); 1362 return; 1363 } 1364 } 1365 1366 if (0 != strcasecmp ("help", 1367 args[0])) 1368 { 1369 fprintf (stderr, 1370 "Unexpected command `%s'\n", 1371 args[0]); 1372 global_ret = EXIT_INVALIDARGUMENT; 1373 } 1374 fprintf (stderr, 1375 "Supported subcommands:\n"); 1376 for (unsigned int i = 0; NULL != cmds[i].name; i++) 1377 { 1378 fprintf (stderr, 1379 "\t%s - %s\n", 1380 cmds[i].name, 1381 cmds[i].help); 1382 } 1383 } 1384 1385 1386 /** 1387 * Main function that will be run. 1388 * 1389 * @param cls closure 1390 * @param args remaining command-line arguments 1391 * @param cfgfile name of the configuration file used (for saving, can be NULL!) 1392 * @param cfg configuration 1393 */ 1394 static void 1395 run (void *cls, 1396 char *const *args, 1397 const char *cfgfile, 1398 const struct GNUNET_CONFIGURATION_Handle *cfg) 1399 { 1400 (void) cls; 1401 (void) cfgfile; 1402 kcfg = cfg; 1403 if (GNUNET_OK != 1404 TALER_config_get_currency (kcfg, 1405 "exchange", 1406 ¤cy)) 1407 { 1408 global_ret = EXIT_NOTCONFIGURED; 1409 return; 1410 } 1411 if (GNUNET_OK != 1412 GNUNET_CONFIGURATION_get_value_string (kcfg, 1413 "auditor", 1414 "BASE_URL", 1415 &auditor_url)) 1416 { 1417 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 1418 "auditor", 1419 "BASE_URL"); 1420 global_ret = EXIT_NOTCONFIGURED; 1421 return; 1422 } 1423 { 1424 char *master_public_key_str; 1425 1426 if (GNUNET_OK != 1427 GNUNET_CONFIGURATION_get_value_string (cfg, 1428 "exchange", 1429 "MASTER_PUBLIC_KEY", 1430 &master_public_key_str)) 1431 { 1432 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 1433 "exchange", 1434 "MASTER_PUBLIC_KEY"); 1435 global_ret = EXIT_NOTCONFIGURED; 1436 return; 1437 } 1438 if (GNUNET_OK != 1439 GNUNET_CRYPTO_eddsa_public_key_from_string ( 1440 master_public_key_str, 1441 strlen (master_public_key_str), 1442 &master_pub.eddsa_pub)) 1443 { 1444 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1445 "Invalid master public key given in exchange configuration."); 1446 GNUNET_free (master_public_key_str); 1447 global_ret = EXIT_NOTCONFIGURED; 1448 return; 1449 } 1450 GNUNET_free (master_public_key_str); 1451 } 1452 ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, 1453 &rc); 1454 rc = GNUNET_CURL_gnunet_rc_create (ctx); 1455 GNUNET_SCHEDULER_add_shutdown (&do_shutdown, 1456 NULL); 1457 next (args); 1458 } 1459 1460 1461 /** 1462 * The main function of the taler-auditor-offline tool. This tool is used to 1463 * sign denomination keys with the auditor's key. It uses the long-term 1464 * offline private key of the auditor and generates signatures with it. It 1465 * also supports online operations with the exchange to download its input 1466 * data and to upload its results. Those online operations should be performed 1467 * on another machine in production! 1468 * 1469 * @param argc number of arguments from the command line 1470 * @param argv command line arguments 1471 * @return 0 ok, 1 on error 1472 */ 1473 int 1474 main (int argc, 1475 char *const *argv) 1476 { 1477 struct GNUNET_GETOPT_CommandLineOption options[] = { 1478 GNUNET_GETOPT_OPTION_END 1479 }; 1480 enum GNUNET_GenericReturnValue ret; 1481 1482 ret = GNUNET_PROGRAM_run ( 1483 TALER_AUDITOR_project_data (), 1484 argc, argv, 1485 "taler-auditor-offline", 1486 gettext_noop ("Operations for offline signing for a Taler exchange"), 1487 options, 1488 &run, NULL); 1489 if (GNUNET_SYSERR == ret) 1490 return EXIT_INVALIDARGUMENT; 1491 if (GNUNET_NO == ret) 1492 return EXIT_SUCCESS; 1493 return global_ret; 1494 } 1495 1496 1497 /* end of taler-auditor-offline.c */