From 70b08396445c7d9f709ad020a608acbf812433bd Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sun, 6 Dec 2020 00:05:45 +0100 Subject: implement new taler-auditor-offline tool --- src/exchange-tools/.gitignore | 1 + src/exchange-tools/Makefile.am | 15 + src/exchange-tools/taler-auditor-offline.c | 1267 +++++++++++++++++++++++++++ src/exchange-tools/taler-exchange-offline.c | 3 + 4 files changed, 1286 insertions(+) create mode 100644 src/exchange-tools/taler-auditor-offline.c (limited to 'src/exchange-tools') diff --git a/src/exchange-tools/.gitignore b/src/exchange-tools/.gitignore index af97f4b07..4d26c9c2c 100644 --- a/src/exchange-tools/.gitignore +++ b/src/exchange-tools/.gitignore @@ -2,3 +2,4 @@ test_taler_exchange_httpd_home/.local/share/taler/exchange/live-keys/ test_taler_exchange_httpd_home/.local/share/taler/exchange/wirefees/ test_taler_exchange_httpd_home/.config/taler/account-1.json taler-exchange-offline +taler-auditor-offline diff --git a/src/exchange-tools/Makefile.am b/src/exchange-tools/Makefile.am index 947e6b2b6..e60963035 100644 --- a/src/exchange-tools/Makefile.am +++ b/src/exchange-tools/Makefile.am @@ -13,6 +13,7 @@ if USE_COVERAGE endif bin_PROGRAMS = \ + taler-auditor-offline \ taler-exchange-keyup \ taler-exchange-keycheck \ taler-exchange-offline \ @@ -47,6 +48,20 @@ taler_exchange_offline_LDADD = \ $(XLIB) +taler_auditor_offline_SOURCES = \ + taler-auditor-offline.c +taler_auditor_offline_LDADD = \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/lib/libtalerexchange.la \ + $(top_builddir)/src/json/libtalerjson.la \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetjson \ + -lgnunetcurl \ + -ljansson \ + -lgnunetutil \ + $(XLIB) + + taler_exchange_wire_SOURCES = \ taler-exchange-wire.c taler_exchange_wire_LDADD = \ diff --git a/src/exchange-tools/taler-auditor-offline.c b/src/exchange-tools/taler-auditor-offline.c new file mode 100644 index 000000000..18d7360cc --- /dev/null +++ b/src/exchange-tools/taler-auditor-offline.c @@ -0,0 +1,1267 @@ +/* + This file is part of TALER + Copyright (C) 2020 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see +*/ +/** + * @file taler-auditor-offline.c + * @brief Support for operations involving the auditor's (offline) key. + * @author Christian Grothoff + */ +#include +#include +#include "taler_json_lib.h" +#include "taler_exchange_service.h" + + +/** + * Our private key, initialized in #load_offline_key(). + */ +static struct TALER_AuditorPrivateKeyP auditor_priv; + +/** + * Our private key, initialized in #load_offline_key(). + */ +static struct TALER_AuditorPublicKeyP auditor_pub; + +/** + * Base URL of this auditor's REST endpoint. + */ +static char *auditor_url; + +/** + * Exchange's master public key. + */ +static struct TALER_MasterPublicKeyP master_pub; + +/** + * Our context for making HTTP requests. + */ +static struct GNUNET_CURL_Context *ctx; + +/** + * Reschedule context for #ctx. + */ +static struct GNUNET_CURL_RescheduleContext *rc; + +/** + * Handle to the exchange's configuration + */ +static const struct GNUNET_CONFIGURATION_Handle *kcfg; + +/** + * Return value from main(). + */ +static int global_ret; + +/** + * Input to consume. + */ +static json_t *in; + +/** + * Array of actions to perform. + */ +static json_t *out; + + +/** + * A subcommand supported by this program. + */ +struct SubCommand +{ + /** + * Name of the command. + */ + const char *name; + + /** + * Help text for the command. + */ + const char *help; + + /** + * Function implementing the command. + * + * @param args subsequent command line arguments (char **) + */ + void (*cb)(char *const *args); +}; + + +/** + * Data structure for wire add requests. + */ +struct DenominationAddRequest +{ + + /** + * Kept in a DLL. + */ + struct DenominationAddRequest *next; + + /** + * Kept in a DLL. + */ + struct DenominationAddRequest *prev; + + /** + * Operation handle. + */ + struct TALER_EXCHANGE_AuditorAddDenominationHandle *h; + + /** + * Array index of the associated command. + */ + size_t idx; +}; + + +/** + * Next work item to perform. + */ +static struct GNUNET_SCHEDULER_Task *nxt; + +/** + * Active denomination add requests. + */ +static struct DenominationAddRequest *dar_head; + +/** + * Active denomination add requests. + */ +static struct DenominationAddRequest *dar_tail; + +/** + * Handle to the exchange, used to request /keys. + */ +static struct TALER_EXCHANGE_Handle *exchange; + + +/** + * Shutdown task. Invoked when the application is being terminated. + * + * @param cls NULL + */ +static void +do_shutdown (void *cls) +{ + (void) cls; + + { + struct DenominationAddRequest *dar; + + while (NULL != (dar = dar_head)) + { + fprintf (stderr, + "Aborting incomplete wire add #%u\n", + (unsigned int) dar->idx); + TALER_EXCHANGE_add_auditor_denomination_cancel (dar->h); + GNUNET_CONTAINER_DLL_remove (dar_head, + dar_tail, + dar); + GNUNET_free (dar); + } + } + if (NULL != out) + { + json_dumpf (out, + stdout, + JSON_INDENT (2)); + json_decref (out); + out = NULL; + } + if (NULL != in) + { + fprintf (stderr, + "Darning: input not consumed!\n"); + json_decref (in); + in = NULL; + } + if (NULL != exchange) + { + TALER_EXCHANGE_disconnect (exchange); + exchange = NULL; + } + if (NULL != nxt) + { + GNUNET_SCHEDULER_cancel (nxt); + nxt = NULL; + } + if (NULL != ctx) + { + GNUNET_CURL_fini (ctx); + ctx = NULL; + } + if (NULL != rc) + { + GNUNET_CURL_gnunet_rc_destroy (rc); + rc = NULL; + } +} + + +/** + * Test if we should shut down because all tasks are done. + */ +static void +test_shutdown (void) +{ + if ( (NULL == dar_head) && + (NULL == exchange) && + (NULL == nxt) ) + GNUNET_SCHEDULER_shutdown (); +} + + +/** + * Function to continue processing the next command. + * + * @param cls must be a `char *const*` with the array of + * command-line arguments to process next + */ +static void +work (void *cls); + + +/** + * Function to schedule job to process the next command. + * + * @param args the array of command-line arguments to process next + */ +static void +next (char *const *args) +{ + GNUNET_assert (NULL == nxt); + if (NULL == args[0]) + { + test_shutdown (); + return; + } + nxt = GNUNET_SCHEDULER_add_now (&work, + (void *) args); +} + + +/** + * Add an operation to the #out JSON array for processing later. + * + * @param op_name name of the operation + * @param op_value values for the operation (consumed) + */ +static void +output_operation (const char *op_name, + json_t *op_value) +{ + json_t *action; + + if (NULL == out) + out = json_array (); + action = json_pack ("{ s:s, s:o }", + "operation", + op_name, + "arguments", + op_value); + GNUNET_break (0 == + json_array_append_new (out, + action)); +} + + +/** + * Information about a subroutine for an upload. + */ +struct UploadHandler +{ + /** + * Key to trigger this subroutine. + */ + const char *key; + + /** + * Function implementing an upload. + * + * @param exchange_url URL of the exchange + * @param idx index of the operation we are performing + * @param value arguments to drive the upload. + */ + void (*cb)(const char *exchange_url, + size_t idx, + const json_t *value); + +}; + + +/** + * Load the offline key (if not yet done). Triggers shutdown on failure. + * + * @return #GNUNET_OK on success + */ +static int +load_offline_key (void) +{ + static bool done; + int ret; + char *fn; + + if (done) + return GNUNET_OK; + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_filename (kcfg, + "auditor", + "AUDITOR_PRIV_FILE", + &fn)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "auditor", + "AUDITOR_PRIV_FILE"); + test_shutdown (); + return GNUNET_SYSERR; + } + if (GNUNET_YES != + GNUNET_DISK_file_test (fn)) + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Auditor private key `%s' does not exist yet, creating it!\n", + fn); + ret = GNUNET_CRYPTO_eddsa_key_from_file (fn, + GNUNET_YES, + &auditor_priv.eddsa_priv); + if (GNUNET_SYSERR == ret) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to initialize auditor key from file `%s': %s\n", + fn, + "could not create file"); + GNUNET_free (fn); + test_shutdown (); + return GNUNET_SYSERR; + } + GNUNET_free (fn); + GNUNET_CRYPTO_eddsa_key_get_public (&auditor_priv.eddsa_priv, + &auditor_pub.eddsa_pub); + done = true; + return GNUNET_OK; +} + + +/** + * Function called with information about the post denomination (signature) + * add operation result. + * + * @param cls closure with a `struct DenominationAddRequest` + * @param hr HTTP response data + */ +static void +denomination_add_cb ( + void *cls, + const struct TALER_EXCHANGE_HttpResponse *hr) +{ + struct DenominationAddRequest *dar = cls; + + if (MHD_HTTP_NO_CONTENT != hr->http_status) + { + fprintf (stderr, + "Upload failed for command %u with status %u: %s (%s)\n", + (unsigned int) dar->idx, + hr->http_status, + TALER_ErrorCode_get_hint (hr->ec), + hr->hint); + } + GNUNET_CONTAINER_DLL_remove (dar_head, + dar_tail, + dar); + GNUNET_free (dar); + test_shutdown (); +} + + +/** + * Upload denomination add data. + * + * @param exchange_url base URL of the exchange + * @param idx index of the operation we are performing (for logging) + * @param value argumets for denomination revocation + */ +static void +upload_denomination_add (const char *exchange_url, + size_t idx, + const json_t *value) +{ + struct TALER_AuditorSignatureP auditor_sig; + struct GNUNET_HashCode h_denom_pub; + struct DenominationAddRequest *dar; + const char *err_name; + unsigned int err_line; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("h_denom_pub", + &h_denom_pub), + GNUNET_JSON_spec_fixed_auto ("auditor_sig", + &auditor_sig), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (value, + spec, + &err_name, + &err_line)) + { + fprintf (stderr, + "Invalid input for adding wire account: %s#%u at %u (skipping)\n", + err_name, + err_line, + (unsigned int) idx); + global_ret = 7; + test_shutdown (); + return; + } + dar = GNUNET_new (struct DenominationAddRequest); + dar->idx = idx; + dar->h = + TALER_EXCHANGE_add_auditor_denomination (ctx, + exchange_url, + &h_denom_pub, + &auditor_pub, + &auditor_sig, + &denomination_add_cb, + dar); + GNUNET_CONTAINER_DLL_insert (dar_head, + dar_tail, + dar); +} + + +/** + * Perform uploads based on the JSON in #io. + * + * @param exchange_url base URL of the exchange to use + */ +static void +trigger_upload (const char *exchange_url) +{ + struct UploadHandler uhs[] = { + { + .key = "sign-denomination", + .cb = &upload_denomination_add + }, + /* array termination */ + { + .key = NULL + } + }; + size_t index; + json_t *obj; + + json_array_foreach (out, index, obj) { + bool found = false; + const char *key; + const json_t *value; + + key = json_string_value (json_object_get (obj, "operation")); + value = json_object_get (obj, "arguments"); + if (NULL == key) + { + fprintf (stderr, + "Malformed JSON input\n"); + global_ret = 3; + test_shutdown (); + return; + } + /* block of code that uses key and value */ + for (unsigned int i = 0; NULL != uhs[i].key; i++) + { + if (0 == strcasecmp (key, + uhs[i].key)) + { + found = true; + uhs[i].cb (exchange_url, + index, + value); + break; + } + } + if (! found) + { + fprintf (stderr, + "Upload does not know how to handle `%s'\n", + key); + global_ret = 3; + test_shutdown (); + return; + } + } +} + + +/** + * Upload operation result (signatures) to exchange. + * + * @param args the array of command-line arguments to process next + */ +static void +do_upload (char *const *args) +{ + char *exchange_url; + + if (NULL != in) + { + fprintf (stderr, + "Downloaded data was not consumed, refusing upload\n"); + test_shutdown (); + global_ret = 4; + return; + } + if (NULL == out) + { + json_error_t err; + + out = json_loadf (stdin, + JSON_REJECT_DUPLICATES, + &err); + if (NULL == out) + { + fprintf (stderr, + "Failed to read JSON input: %s at %d:%s (offset: %d)\n", + err.text, + err.line, + err.source, + err.position); + test_shutdown (); + global_ret = 2; + return; + } + } + if (! json_is_array (out)) + { + fprintf (stderr, + "Error: expected JSON array for `upload` command\n"); + test_shutdown (); + global_ret = 2; + return; + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (kcfg, + "exchange", + "BASE_URL", + &exchange_url)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "exchange", + "BASE_URL"); + global_ret = 1; + test_shutdown (); + return; + } + trigger_upload (exchange_url); + json_decref (out); + out = NULL; + GNUNET_free (exchange_url); +} + + +/** + * Function called with information about who is auditing + * a particular exchange and what keys the exchange is using. + * + * @param cls closure with the `char **` remaining args + * @param hr HTTP response data + * @param keys information about the various keys used + * by the exchange, NULL if /keys failed + * @param compat protocol compatibility information + */ +static void +keys_cb ( + void *cls, + const struct TALER_EXCHANGE_HttpResponse *hr, + const struct TALER_EXCHANGE_Keys *keys, + enum TALER_EXCHANGE_VersionCompatibility compat) +{ + char *const *args = cls; + + switch (hr->http_status) + { + case MHD_HTTP_OK: + break; + default: + fprintf (stderr, + "Failed to download keys: %s (HTTP status: %u/%u)\n", + hr->hint, + hr->http_status, + (unsigned int) hr->ec); + TALER_EXCHANGE_disconnect (exchange); + exchange = NULL; + test_shutdown (); + global_ret = 4; + return; + } + if (NULL == args[0]) + { + json_dumpf (hr->reply, + stdout, + JSON_INDENT (2)); + } + else + { + in = json_incref ((json_t*) hr->reply); + } + TALER_EXCHANGE_disconnect (exchange); + exchange = NULL; + next (args); +} + + +/** + * Download future keys. + * + * @param args the array of command-line arguments to process next + */ +static void +do_download (char *const *args) +{ + char *exchange_url; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (kcfg, + "exchange", + "BASE_URL", + &exchange_url)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "exchange", + "BASE_URL"); + test_shutdown (); + global_ret = 1; + return; + } + exchange = TALER_EXCHANGE_connect (ctx, + exchange_url, + &keys_cb, + (void *) args, + TALER_EXCHANGE_OPTION_END); + GNUNET_free (exchange_url); +} + + +/** + * Output @a denomkeys for human consumption. + * + * @param denomkeys keys to output + * @return #GNUNET_OK on success + */ +static int +show_denomkeys (const json_t *denomkeys) +{ + size_t index; + json_t *value; + + json_array_foreach (denomkeys, index, value) { + const char *err_name; + unsigned int err_line; + struct TALER_DenominationPublicKey denom_pub; + struct GNUNET_TIME_Absolute stamp_start; + struct GNUNET_TIME_Absolute stamp_expire_withdraw; + struct GNUNET_TIME_Absolute stamp_expire_deposit; + struct GNUNET_TIME_Absolute stamp_expire_legal; + struct TALER_Amount coin_value; + struct TALER_Amount fee_withdraw; + struct TALER_Amount fee_deposit; + struct TALER_Amount fee_refresh; + struct TALER_Amount fee_refund; + struct TALER_MasterSignatureP master_sig; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_rsa_public_key ("denom_pub", + &denom_pub.rsa_public_key), + TALER_JSON_spec_amount ("value", + &coin_value), + TALER_JSON_spec_amount ("fee_withdraw", + &fee_withdraw), + TALER_JSON_spec_amount ("fee_deposit", + &fee_deposit), + TALER_JSON_spec_amount ("fee_refresh", + &fee_refresh), + TALER_JSON_spec_amount ("fee_refund", + &fee_refund), + GNUNET_JSON_spec_absolute_time ("stamp_start", + &stamp_start), + GNUNET_JSON_spec_absolute_time ("stamp_expire_withdraw", + &stamp_expire_withdraw), + GNUNET_JSON_spec_absolute_time ("stamp_expire_deposit", + &stamp_expire_deposit), + GNUNET_JSON_spec_absolute_time ("stamp_expire_legal", + &stamp_expire_legal), + GNUNET_JSON_spec_fixed_auto ("master_sig", + &master_sig), + GNUNET_JSON_spec_end () + }; + struct GNUNET_TIME_Relative duration; + struct GNUNET_HashCode h_denom_pub; + + if (GNUNET_OK != + GNUNET_JSON_parse (value, + spec, + &err_name, + &err_line)) + { + fprintf (stderr, + "Invalid input for denomination key to 'show': %s#%u at %u (skipping)\n", + err_name, + err_line, + (unsigned int) index); + GNUNET_JSON_parse_free (spec); + global_ret = 7; + test_shutdown (); + return GNUNET_SYSERR; + } + duration = GNUNET_TIME_absolute_get_difference (stamp_start, + stamp_expire_withdraw); + GNUNET_CRYPTO_rsa_public_key_hash (denom_pub.rsa_public_key, + &h_denom_pub); + if (GNUNET_OK != + TALER_exchange_offline_denom_validity_verify ( + &h_denom_pub, + stamp_start, + stamp_expire_withdraw, + stamp_expire_deposit, + stamp_expire_legal, + &coin_value, + &fee_withdraw, + &fee_deposit, + &fee_refresh, + &fee_refund, + &master_pub, + &master_sig)) + { + fprintf (stderr, + "Invalid master signature for key %s (aborting)\n", + TALER_B2S (&h_denom_pub)); + global_ret = 9; + test_shutdown (); + return GNUNET_SYSERR; + } + + { + char *withdraw_fee_s; + char *deposit_fee_s; + char *refresh_fee_s; + char *refund_fee_s; + char *deposit_s; + char *legal_s; + + withdraw_fee_s = TALER_amount_to_string (&fee_withdraw); + deposit_fee_s = TALER_amount_to_string (&fee_deposit); + refresh_fee_s = TALER_amount_to_string (&fee_refresh); + refund_fee_s = TALER_amount_to_string (&fee_refund); + deposit_s = GNUNET_strdup ( + GNUNET_STRINGS_absolute_time_to_string (stamp_expire_deposit)); + legal_s = GNUNET_strdup ( + GNUNET_STRINGS_absolute_time_to_string (stamp_expire_legal)); + + printf ( + "DENOMINATION-KEY %s of value %s starting at %s " + "(used for: %s, deposit until: %s legal end: %s) with fees %s/%s/%s/%s\n", + TALER_B2S (&h_denom_pub), + TALER_amount2s (&coin_value), + GNUNET_STRINGS_absolute_time_to_string (stamp_start), + GNUNET_STRINGS_relative_time_to_string (duration, + GNUNET_NO), + deposit_s, + legal_s, + withdraw_fee_s, + deposit_fee_s, + refresh_fee_s, + refund_fee_s); + GNUNET_free (withdraw_fee_s); + GNUNET_free (deposit_fee_s); + GNUNET_free (refresh_fee_s); + GNUNET_free (refund_fee_s); + GNUNET_free (deposit_s); + GNUNET_free (legal_s); + } + + GNUNET_JSON_parse_free (spec); + } + return GNUNET_OK; +} + + +/** + * Show exchange denomination keys. + * + * @param args the array of command-line arguments to process next + */ +static void +do_show (char *const *args) +{ + if (NULL == in) + { + json_error_t err; + + out = json_loadf (stdin, + JSON_REJECT_DUPLICATES, + &err); + if (NULL == in) + { + fprintf (stderr, + "Failed to read JSON input: %s at %d:%s (offset: %d)\n", + err.text, + err.line, + err.source, + err.position); + global_ret = 2; + test_shutdown (); + return; + } + } + + { + const char *err_name; + unsigned int err_line; + json_t *denomkeys; + struct TALER_MasterPublicKeyP mpub; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_json ("denoms", + &denomkeys), + GNUNET_JSON_spec_fixed_auto ("master_public_key", + &mpub), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (in, + spec, + &err_name, + &err_line)) + { + fprintf (stderr, + "Invalid input to 'show': %s#%u (skipping)\n", + err_name, + err_line); + global_ret = 7; + test_shutdown (); + return; + } + if (0 != + GNUNET_memcmp (&mpub, + &master_pub)) + { + fprintf (stderr, + "Exchange master public key does not match key we have configured (aborting)\n"); + global_ret = 7; + test_shutdown (); + return; + } + if (GNUNET_OK != + show_denomkeys (denomkeys)) + { + global_ret = 8; + test_shutdown (); + GNUNET_JSON_parse_free (spec); + return; + } + GNUNET_JSON_parse_free (spec); + } + /* do NOT consume input if next argument is '-' */ + if ( (NULL != args[0]) && + (0 == strcmp ("-", + args[0])) ) + { + next (args + 1); + return; + } + json_decref (in); + in = NULL; + next (args); +} + + +/** + * Sign @a denomkeys with offline key. + * + * @param denomkeys keys to output + * @return #GNUNET_OK on success + */ +static int +sign_denomkeys (const json_t *denomkeys) +{ + size_t index; + json_t *value; + + json_array_foreach (denomkeys, index, value) { + const char *err_name; + unsigned int err_line; + struct TALER_DenominationPublicKey denom_pub; + struct GNUNET_TIME_Absolute stamp_start; + struct GNUNET_TIME_Absolute stamp_expire_withdraw; + struct GNUNET_TIME_Absolute stamp_expire_deposit; + struct GNUNET_TIME_Absolute stamp_expire_legal; + struct TALER_Amount coin_value; + struct TALER_Amount fee_withdraw; + struct TALER_Amount fee_deposit; + struct TALER_Amount fee_refresh; + struct TALER_Amount fee_refund; + struct TALER_MasterSignatureP master_sig; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_rsa_public_key ("denom_pub", + &denom_pub.rsa_public_key), + TALER_JSON_spec_amount ("value", + &coin_value), + TALER_JSON_spec_amount ("fee_withdraw", + &fee_withdraw), + TALER_JSON_spec_amount ("fee_deposit", + &fee_deposit), + TALER_JSON_spec_amount ("fee_refresh", + &fee_refresh), + TALER_JSON_spec_amount ("fee_refund", + &fee_refund), + GNUNET_JSON_spec_absolute_time ("stamp_start", + &stamp_start), + GNUNET_JSON_spec_absolute_time ("stamp_expire_withdraw", + &stamp_expire_withdraw), + GNUNET_JSON_spec_absolute_time ("stamp_expire_deposit", + &stamp_expire_deposit), + GNUNET_JSON_spec_absolute_time ("stamp_expire_legal", + &stamp_expire_legal), + GNUNET_JSON_spec_fixed_auto ("master_sig", + &master_sig), + GNUNET_JSON_spec_end () + }; + struct GNUNET_HashCode h_denom_pub; + + if (GNUNET_OK != + GNUNET_JSON_parse (value, + spec, + &err_name, + &err_line)) + { + fprintf (stderr, + "Invalid input for denomination key to 'sign': %s#%u at %u (skipping)\n", + err_name, + err_line, + (unsigned int) index); + GNUNET_JSON_parse_free (spec); + global_ret = 7; + test_shutdown (); + return GNUNET_SYSERR; + } + GNUNET_CRYPTO_rsa_public_key_hash (denom_pub.rsa_public_key, + &h_denom_pub); + if (GNUNET_OK != + TALER_exchange_offline_denom_validity_verify ( + &h_denom_pub, + stamp_start, + stamp_expire_withdraw, + stamp_expire_deposit, + stamp_expire_legal, + &coin_value, + &fee_withdraw, + &fee_deposit, + &fee_refresh, + &fee_refund, + &master_pub, + &master_sig)) + { + fprintf (stderr, + "Invalid master signature for key %s (aborting)\n", + TALER_B2S (&h_denom_pub)); + global_ret = 9; + test_shutdown (); + return GNUNET_SYSERR; + } + + { + struct TALER_AuditorSignatureP auditor_sig; + + TALER_auditor_denom_validity_sign (auditor_url, + &h_denom_pub, + &master_pub, + stamp_start, + stamp_expire_withdraw, + stamp_expire_deposit, + stamp_expire_legal, + &coin_value, + &fee_withdraw, + &fee_deposit, + &fee_refresh, + &fee_refund, + &auditor_priv, + &auditor_sig); + output_operation ("sign-denomination", + json_pack ("{s:o,s:o}", + "h_denomn_pub", + GNUNET_JSON_from_data_auto (&h_denom_pub), + "auditor_sig", + GNUNET_JSON_from_data_auto (&auditor_sig))); + } + GNUNET_JSON_parse_free (spec); + } + return GNUNET_OK; +} + + +/** + * Sign denomination keys. + * + * @param args the array of command-line arguments to process next + */ +static void +do_sign (char *const *args) +{ + if (NULL == in) + { + json_error_t err; + + out = json_loadf (stdin, + JSON_REJECT_DUPLICATES, + &err); + if (NULL == in) + { + fprintf (stderr, + "Failed to read JSON input: %s at %d:%s (offset: %d)\n", + err.text, + err.line, + err.source, + err.position); + global_ret = 2; + test_shutdown (); + return; + } + } + if (GNUNET_OK != + load_offline_key ()) + return; + + { + const char *err_name; + unsigned int err_line; + struct TALER_MasterPublicKeyP mpub; + json_t *denomkeys; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_json ("denoms", + &denomkeys), + GNUNET_JSON_spec_fixed_auto ("master_public_key", + &mpub), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (in, + spec, + &err_name, + &err_line)) + { + fprintf (stderr, + "Invalid input to 'sign': %s#%u (skipping)\n", + err_name, + err_line); + global_ret = 7; + test_shutdown (); + return; + } + if (0 != + GNUNET_memcmp (&mpub, + &master_pub)) + { + fprintf (stderr, + "Exchange master public key does not match key we have configured (aborting)\n"); + global_ret = 7; + test_shutdown (); + return; + } + if (GNUNET_OK != + sign_denomkeys (denomkeys)) + { + global_ret = 8; + test_shutdown (); + GNUNET_JSON_parse_free (spec); + return; + } + GNUNET_JSON_parse_free (spec); + } + json_decref (in); + in = NULL; + next (args); +} + + +static void +work (void *cls) +{ + char *const *args = cls; + struct SubCommand cmds[] = { + { + .name = "download", + .help = + "obtain keys from exchange (to be performed online!)", + .cb = &do_download + }, + { + .name = "show", + .help = + "display keys from exchange for human review (pass '-' as argument to disable consuming input)", + .cb = &do_show + }, + { + .name = "sign", + .help = + "sing all denomination keys from the input", + .cb = &do_sign + }, + { + .name = "upload", + .help = + "upload operation result to exchange (to be performed online!)", + .cb = &do_upload + }, + /* list terminator */ + { + .name = NULL, + } + }; + (void) cls; + + nxt = NULL; + for (unsigned int i = 0; NULL != cmds[i].name; i++) + { + if (0 == strcasecmp (cmds[i].name, + args[0])) + { + cmds[i].cb (&args[1]); + return; + } + } + + if (0 != strcasecmp ("help", + args[0])) + { + fprintf (stderr, + "Unexpected command `%s'\n", + args[0]); + global_ret = 3; + } + fprintf (stderr, + "Supported subcommands:\n"); + for (unsigned int i = 0; NULL != cmds[i].name; i++) + { + fprintf (stderr, + "\t%s - %s\n", + cmds[i].name, + cmds[i].help); + } +} + + +/** + * Main function that will be run. + * + * @param cls closure + * @param args remaining command-line arguments + * @param cfgfile name of the configuration file used (for saving, can be NULL!) + * @param cfg configuration + */ +static void +run (void *cls, + char *const *args, + const char *cfgfile, + const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + kcfg = cfg; + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (kcfg, + "auditor", + "BASE_URL", + &auditor_url)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "auditor", + "BASE_URL"); + global_ret = 1; + return; + } + { + char *master_public_key_str; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "exchange", + "MASTER_PUBLIC_KEY", + &master_public_key_str)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "exchange", + "MASTER_PUBLIC_KEY"); + global_ret = 1; + return; + } + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_public_key_from_string ( + master_public_key_str, + strlen (master_public_key_str), + &master_pub.eddsa_pub)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid master public key given in exchange configuration."); + GNUNET_free (master_public_key_str); + global_ret = 1; + return; + } + GNUNET_free (master_public_key_str); + } + ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, + &rc); + rc = GNUNET_CURL_gnunet_rc_create (ctx); + GNUNET_SCHEDULER_add_shutdown (&do_shutdown, + NULL); + next (args); +} + + +/** + * The main function of the taler-auditor-offline tool. This tool is used to + * sign denomination keys with the auditor's key. It uses the long-term + * offline private key of the auditor and generates signatures with it. It + * also supports online operations with the exchange to download its input + * data and to upload its results. Those online operations should be performed + * on another machine in production! + * + * @param argc number of arguments from the command line + * @param argv command line arguments + * @return 0 ok, 1 on error + */ +int +main (int argc, + char *const *argv) +{ + struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_OPTION_END + }; + + /* force linker to link against libtalerutil; if we do + not do this, the linker may "optimize" libtalerutil + away and skip #TALER_OS_init(), which we do need */ + (void) TALER_project_data_default (); + GNUNET_assert (GNUNET_OK == + GNUNET_log_setup ("taler-auditor-offline", + "WARNING", + NULL)); + if (GNUNET_OK != + GNUNET_PROGRAM_run (argc, argv, + "taler-auditor-offline", + "Operations for offline signing for a Taler exchange", + options, + &run, NULL)) + return 1; + return global_ret; +} + + +/* end of taler-auditor-offline.c */ diff --git a/src/exchange-tools/taler-exchange-offline.c b/src/exchange-tools/taler-exchange-offline.c index d6b165143..5c9e6398e 100644 --- a/src/exchange-tools/taler-exchange-offline.c +++ b/src/exchange-tools/taler-exchange-offline.c @@ -2391,6 +2391,9 @@ do_sign (char *const *args) return; } } + if (GNUNET_OK != + load_offline_key ()) + return; { const char *err_name; -- cgit v1.2.3