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