taler-auditor-offline.c (40256B)
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 * 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 global_ret = EXIT_FAILURE; 968 test_shutdown (); 969 return NULL; 970 } 971 json_decref (in); 972 in = NULL; 973 return keys; 974 } 975 976 977 /** 978 * Show exchange denomination keys. 979 * 980 * @param args the array of command-line arguments to process next 981 */ 982 static void 983 do_show (char *const *args) 984 { 985 json_t *keys; 986 const char *err_name; 987 unsigned int err_line; 988 const json_t *denomkeys; 989 struct TALER_MasterPublicKeyP mpub; 990 struct GNUNET_JSON_Specification spec[] = { 991 GNUNET_JSON_spec_array_const ("denominations", 992 &denomkeys), 993 GNUNET_JSON_spec_fixed_auto ("master_public_key", 994 &mpub), 995 GNUNET_JSON_spec_end () 996 }; 997 998 keys = parse_keys ("show"); 999 if (NULL == keys) 1000 { 1001 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1002 "Showing failed: no valid input\n"); 1003 return; 1004 } 1005 if (GNUNET_OK != 1006 GNUNET_JSON_parse (keys, 1007 spec, 1008 &err_name, 1009 &err_line)) 1010 { 1011 fprintf (stderr, 1012 "Invalid input to 'show': %s#%u (skipping)\n", 1013 err_name, 1014 err_line); 1015 global_ret = EXIT_FAILURE; 1016 test_shutdown (); 1017 json_decref (keys); 1018 return; 1019 } 1020 if (0 != 1021 GNUNET_memcmp (&mpub, 1022 &master_pub)) 1023 { 1024 fprintf (stderr, 1025 "Exchange master public key does not match key we have configured (aborting)\n"); 1026 global_ret = EXIT_FAILURE; 1027 test_shutdown (); 1028 json_decref (keys); 1029 return; 1030 } 1031 if (GNUNET_OK != 1032 show_denomkeys (denomkeys)) 1033 { 1034 global_ret = EXIT_FAILURE; 1035 test_shutdown (); 1036 json_decref (keys); 1037 return; 1038 } 1039 json_decref (keys); 1040 /* do NOT consume input if next argument is '-' */ 1041 if ( (NULL != args[0]) && 1042 (0 == strcmp ("-", 1043 args[0])) ) 1044 { 1045 next (args + 1); 1046 return; 1047 } 1048 next (args); 1049 } 1050 1051 1052 /** 1053 * Sign @a denomkeys with offline key. 1054 * 1055 * @param denomkeys keys to output 1056 * @return #GNUNET_OK on success 1057 */ 1058 static enum GNUNET_GenericReturnValue 1059 sign_denomkeys (const json_t *denomkeys) 1060 { 1061 size_t group_idx; 1062 json_t *value; 1063 1064 json_array_foreach (denomkeys, group_idx, value) { 1065 struct TALER_DenominationGroup group = { 0 }; 1066 const json_t *denom_keys_array; 1067 const char *err_name; 1068 unsigned int err_line; 1069 struct GNUNET_JSON_Specification spec[] = { 1070 TALER_JSON_spec_denomination_group (NULL, 1071 currency, 1072 &group), 1073 GNUNET_JSON_spec_array_const ("denoms", 1074 &denom_keys_array), 1075 GNUNET_JSON_spec_end () 1076 }; 1077 size_t index; 1078 json_t *denom_key_obj; 1079 1080 if (GNUNET_OK != 1081 GNUNET_JSON_parse (value, 1082 spec, 1083 &err_name, 1084 &err_line)) 1085 { 1086 fprintf (stderr, 1087 "Invalid input for denomination key to 'sign': %s#%u at %u (skipping)\n", 1088 err_name, 1089 err_line, 1090 (unsigned int) group_idx); 1091 GNUNET_JSON_parse_free (spec); 1092 global_ret = EXIT_FAILURE; 1093 test_shutdown (); 1094 return GNUNET_SYSERR; 1095 } 1096 json_array_foreach (denom_keys_array, index, denom_key_obj) { 1097 struct GNUNET_TIME_Timestamp stamp_start; 1098 struct GNUNET_TIME_Timestamp stamp_expire_withdraw; 1099 struct GNUNET_TIME_Timestamp stamp_expire_deposit; 1100 struct GNUNET_TIME_Timestamp stamp_expire_legal; 1101 struct TALER_DenominationPublicKey denom_pub = { 1102 .age_mask = group.age_mask 1103 }; 1104 struct TALER_MasterSignatureP master_sig; 1105 struct GNUNET_JSON_Specification ispec[] = { 1106 TALER_JSON_spec_denom_pub_cipher (NULL, 1107 group.cipher, 1108 &denom_pub), 1109 GNUNET_JSON_spec_timestamp ("stamp_start", 1110 &stamp_start), 1111 GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw", 1112 &stamp_expire_withdraw), 1113 GNUNET_JSON_spec_timestamp ("stamp_expire_deposit", 1114 &stamp_expire_deposit), 1115 GNUNET_JSON_spec_timestamp ("stamp_expire_legal", 1116 &stamp_expire_legal), 1117 GNUNET_JSON_spec_fixed_auto ("master_sig", 1118 &master_sig), 1119 GNUNET_JSON_spec_end () 1120 }; 1121 struct TALER_DenominationHashP h_denom_pub; 1122 1123 if (GNUNET_OK != 1124 GNUNET_JSON_parse (denom_key_obj, 1125 ispec, 1126 &err_name, 1127 &err_line)) 1128 { 1129 fprintf (stderr, 1130 "Invalid input for denomination key to 'show': %s#%u at %u/%u (skipping)\n", 1131 err_name, 1132 err_line, 1133 (unsigned int) group_idx, 1134 (unsigned int) index); 1135 GNUNET_JSON_parse_free (spec); 1136 global_ret = EXIT_FAILURE; 1137 test_shutdown (); 1138 return GNUNET_SYSERR; 1139 } 1140 TALER_denom_pub_hash (&denom_pub, 1141 &h_denom_pub); 1142 if (GNUNET_OK != 1143 TALER_exchange_offline_denom_validity_verify ( 1144 &h_denom_pub, 1145 stamp_start, 1146 stamp_expire_withdraw, 1147 stamp_expire_deposit, 1148 stamp_expire_legal, 1149 &group.value, 1150 &group.fees, 1151 &master_pub, 1152 &master_sig)) 1153 { 1154 fprintf (stderr, 1155 "Invalid master signature for key %s (aborting)\n", 1156 TALER_B2S (&h_denom_pub)); 1157 global_ret = EXIT_FAILURE; 1158 test_shutdown (); 1159 return GNUNET_SYSERR; 1160 } 1161 1162 { 1163 struct TALER_AuditorSignatureP auditor_sig; 1164 1165 TALER_auditor_denom_validity_sign (auditor_url, 1166 &h_denom_pub, 1167 &master_pub, 1168 stamp_start, 1169 stamp_expire_withdraw, 1170 stamp_expire_deposit, 1171 stamp_expire_legal, 1172 &group.value, 1173 &group.fees, 1174 &auditor_priv, 1175 &auditor_sig); 1176 output_operation (OP_SIGN_DENOMINATION, 1177 GNUNET_JSON_PACK ( 1178 GNUNET_JSON_pack_data_auto ("h_denom_pub", 1179 &h_denom_pub), 1180 GNUNET_JSON_pack_data_auto ("auditor_sig", 1181 &auditor_sig))); 1182 } 1183 GNUNET_JSON_parse_free (ispec); 1184 } 1185 GNUNET_JSON_parse_free (spec); 1186 } 1187 return GNUNET_OK; 1188 } 1189 1190 1191 /** 1192 * Sign denomination keys. 1193 * 1194 * @param args the array of command-line arguments to process next 1195 */ 1196 static void 1197 do_sign (char *const *args) 1198 { 1199 json_t *keys; 1200 const char *err_name; 1201 unsigned int err_line; 1202 struct TALER_MasterPublicKeyP mpub; 1203 const json_t *denomkeys; 1204 struct GNUNET_JSON_Specification spec[] = { 1205 GNUNET_JSON_spec_array_const ("denominations", 1206 &denomkeys), 1207 GNUNET_JSON_spec_fixed_auto ("master_public_key", 1208 &mpub), 1209 GNUNET_JSON_spec_end () 1210 }; 1211 1212 keys = parse_keys ("sign"); 1213 if (NULL == keys) 1214 { 1215 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1216 "Signing failed: no valid input\n"); 1217 return; 1218 } 1219 if (GNUNET_OK != 1220 load_offline_key (GNUNET_NO)) 1221 { 1222 json_decref (keys); 1223 return; 1224 } 1225 if (GNUNET_OK != 1226 GNUNET_JSON_parse (keys, 1227 spec, 1228 &err_name, 1229 &err_line)) 1230 { 1231 fprintf (stderr, 1232 "Invalid input to 'sign': %s#%u (skipping)\n", 1233 err_name, 1234 err_line); 1235 global_ret = EXIT_FAILURE; 1236 test_shutdown (); 1237 json_decref (keys); 1238 return; 1239 } 1240 if (0 != 1241 GNUNET_memcmp (&mpub, 1242 &master_pub)) 1243 { 1244 fprintf (stderr, 1245 "Exchange master public key does not match key we have configured (aborting)\n"); 1246 global_ret = EXIT_FAILURE; 1247 test_shutdown (); 1248 json_decref (keys); 1249 return; 1250 } 1251 if (NULL == out) 1252 { 1253 out = json_array (); 1254 GNUNET_assert (NULL != out); 1255 } 1256 if (GNUNET_OK != 1257 sign_denomkeys (denomkeys)) 1258 { 1259 global_ret = EXIT_FAILURE; 1260 test_shutdown (); 1261 json_decref (keys); 1262 return; 1263 } 1264 json_decref (keys); 1265 next (args); 1266 } 1267 1268 1269 /** 1270 * Setup and output offline signing key. 1271 * 1272 * @param args the array of command-line arguments to process next 1273 */ 1274 static void 1275 do_setup (char *const *args) 1276 { 1277 if (GNUNET_OK != 1278 load_offline_key (GNUNET_YES)) 1279 { 1280 global_ret = EXIT_FAILURE; 1281 return; 1282 } 1283 if (NULL != *args) 1284 { 1285 if (NULL == out) 1286 { 1287 out = json_array (); 1288 GNUNET_assert (NULL != out); 1289 } 1290 output_operation (OP_SETUP, 1291 GNUNET_JSON_PACK ( 1292 GNUNET_JSON_pack_data_auto ("auditor_pub", 1293 &auditor_pub))); 1294 } 1295 1296 else 1297 { 1298 char *pub_s; 1299 1300 pub_s = GNUNET_STRINGS_data_to_string_alloc (&auditor_pub, 1301 sizeof (auditor_pub)); 1302 fprintf (stdout, 1303 "%s\n", 1304 pub_s); 1305 GNUNET_free (pub_s); 1306 } 1307 if ( (NULL != *args) && 1308 (0 == strcmp (*args, 1309 "-")) ) 1310 args++; 1311 next (args); 1312 } 1313 1314 1315 static void 1316 work (void *cls) 1317 { 1318 char *const *args = cls; 1319 struct SubCommand cmds[] = { 1320 { 1321 .name = "setup", 1322 .help = 1323 "setup auditor offline private key and show the public key", 1324 .cb = &do_setup 1325 }, 1326 { 1327 .name = "download", 1328 .help = 1329 "obtain keys from exchange (to be performed online!)", 1330 .cb = &do_download 1331 }, 1332 { 1333 .name = "show", 1334 .help = 1335 "display keys from exchange for human review (pass '-' as argument to disable consuming input)", 1336 .cb = &do_show 1337 }, 1338 { 1339 .name = "sign", 1340 .help = 1341 "sing all denomination keys from the input", 1342 .cb = &do_sign 1343 }, 1344 { 1345 .name = "upload", 1346 .help = 1347 "upload operation result to exchange (to be performed online!)", 1348 .cb = &do_upload 1349 }, 1350 /* list terminator */ 1351 { 1352 .name = NULL, 1353 } 1354 }; 1355 (void) cls; 1356 1357 nxt = NULL; 1358 for (unsigned int i = 0; NULL != cmds[i].name; i++) 1359 { 1360 if (0 == strcasecmp (cmds[i].name, 1361 args[0])) 1362 { 1363 cmds[i].cb (&args[1]); 1364 return; 1365 } 1366 } 1367 1368 if (0 != strcasecmp ("help", 1369 args[0])) 1370 { 1371 fprintf (stderr, 1372 "Unexpected command `%s'\n", 1373 args[0]); 1374 global_ret = EXIT_INVALIDARGUMENT; 1375 } 1376 fprintf (stderr, 1377 "Supported subcommands:\n"); 1378 for (unsigned int i = 0; NULL != cmds[i].name; i++) 1379 { 1380 fprintf (stderr, 1381 "\t%s - %s\n", 1382 cmds[i].name, 1383 cmds[i].help); 1384 } 1385 } 1386 1387 1388 /** 1389 * Main function that will be run. 1390 * 1391 * @param cls closure 1392 * @param args remaining command-line arguments 1393 * @param cfgfile name of the configuration file used (for saving, can be NULL!) 1394 * @param cfg configuration 1395 */ 1396 static void 1397 run (void *cls, 1398 char *const *args, 1399 const char *cfgfile, 1400 const struct GNUNET_CONFIGURATION_Handle *cfg) 1401 { 1402 (void) cls; 1403 (void) cfgfile; 1404 kcfg = cfg; 1405 if (GNUNET_OK != 1406 TALER_config_get_currency (kcfg, 1407 "exchange", 1408 ¤cy)) 1409 { 1410 global_ret = EXIT_NOTCONFIGURED; 1411 return; 1412 } 1413 if (GNUNET_OK != 1414 GNUNET_CONFIGURATION_get_value_string (kcfg, 1415 "auditor", 1416 "BASE_URL", 1417 &auditor_url)) 1418 { 1419 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 1420 "auditor", 1421 "BASE_URL"); 1422 global_ret = EXIT_NOTCONFIGURED; 1423 return; 1424 } 1425 { 1426 char *master_public_key_str; 1427 1428 if (GNUNET_OK != 1429 GNUNET_CONFIGURATION_get_value_string (cfg, 1430 "exchange", 1431 "MASTER_PUBLIC_KEY", 1432 &master_public_key_str)) 1433 { 1434 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 1435 "exchange", 1436 "MASTER_PUBLIC_KEY"); 1437 global_ret = EXIT_NOTCONFIGURED; 1438 return; 1439 } 1440 if (GNUNET_OK != 1441 GNUNET_CRYPTO_eddsa_public_key_from_string ( 1442 master_public_key_str, 1443 strlen (master_public_key_str), 1444 &master_pub.eddsa_pub)) 1445 { 1446 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1447 "Invalid master public key given in exchange configuration."); 1448 GNUNET_free (master_public_key_str); 1449 global_ret = EXIT_NOTCONFIGURED; 1450 return; 1451 } 1452 GNUNET_free (master_public_key_str); 1453 } 1454 ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, 1455 &rc); 1456 rc = GNUNET_CURL_gnunet_rc_create (ctx); 1457 GNUNET_SCHEDULER_add_shutdown (&do_shutdown, 1458 NULL); 1459 next (args); 1460 } 1461 1462 1463 /** 1464 * The main function of the taler-auditor-offline tool. This tool is used to 1465 * sign denomination keys with the auditor's key. It uses the long-term 1466 * offline private key of the auditor and generates signatures with it. It 1467 * also supports online operations with the exchange to download its input 1468 * data and to upload its results. Those online operations should be performed 1469 * on another machine in production! 1470 * 1471 * @param argc number of arguments from the command line 1472 * @param argv command line arguments 1473 * @return 0 ok, 1 on error 1474 */ 1475 int 1476 main (int argc, 1477 char *const *argv) 1478 { 1479 struct GNUNET_GETOPT_CommandLineOption options[] = { 1480 GNUNET_GETOPT_OPTION_END 1481 }; 1482 enum GNUNET_GenericReturnValue ret; 1483 1484 ret = GNUNET_PROGRAM_run ( 1485 TALER_AUDITOR_project_data (), 1486 argc, argv, 1487 "taler-auditor-offline", 1488 gettext_noop ("Operations for offline signing for a Taler exchange"), 1489 options, 1490 &run, NULL); 1491 if (GNUNET_SYSERR == ret) 1492 return EXIT_INVALIDARGUMENT; 1493 if (GNUNET_NO == ret) 1494 return EXIT_SUCCESS; 1495 return global_ret; 1496 } 1497 1498 1499 /* end of taler-auditor-offline.c */