diff options
Diffstat (limited to 'src/exchange-tools/taler-auditor-offline.c')
-rw-r--r-- | src/exchange-tools/taler-auditor-offline.c | 1496 |
1 files changed, 1496 insertions, 0 deletions
diff --git a/src/exchange-tools/taler-auditor-offline.c b/src/exchange-tools/taler-auditor-offline.c new file mode 100644 index 000000000..8c280d46b --- /dev/null +++ b/src/exchange-tools/taler-auditor-offline.c @@ -0,0 +1,1496 @@ +/* + This file is part of TALER + Copyright (C) 2020-2023 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 <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-auditor-offline.c + * @brief Support for operations involving the auditor's (offline) key. + * @author Christian Grothoff + */ +#include <platform.h> +#include <gnunet/gnunet_json_lib.h> +#include "taler_json_lib.h" +#include "taler_exchange_service.h" + +/** + * Name of the input of a denomination key signature for the 'upload' operation. + * The "auditor-" prefix ensures that there is no ambiguity between + * taler-exchange-offline and taler-auditor-offline JSON formats. + * The last component --by convention-- identifies the protocol version + * and should be incremented whenever the JSON format of the 'argument' changes. + */ +#define OP_SIGN_DENOMINATION "auditor-sign-denomination-0" + +/** + * Name of the input for the 'sign' and 'show' operations. + * The "auditor-" prefix ensures that there is no ambiguity between + * taler-exchange-offline and taler-auditor-offline JSON formats. + * The last component --by convention-- identifies the protocol version + * and should be incremented whenever the JSON format of the 'argument' changes. + */ +#define OP_INPUT_KEYS "auditor-keys-0" + +/** + * Show the offline signing key. + * The last component --by convention-- identifies the protocol version + * and should be incremented whenever the JSON format of the 'argument' changes. + */ +#define OP_SETUP "auditor-setup-0" + +/** + * 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; + +/** + * Currency supported by this auditor. + */ +static char *currency; + + +/** + * 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_GetKeysHandle *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_get_keys_cancel (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; + + GNUNET_assert (NULL != out); + action = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("operation", + op_name), + GNUNET_JSON_pack_object_steal ("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. + * + * @param do_create #GNUNET_YES if the key may be created + * @return #GNUNET_OK on success + */ +static int +load_offline_key (int do_create) +{ + 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, + do_create, + &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 adr response data + */ +static void +denomination_add_cb ( + void *cls, + const struct TALER_EXCHANGE_AuditorAddDenominationResponse *adr) +{ + struct DenominationAddRequest *dar = cls; + const struct TALER_EXCHANGE_HttpResponse *hr = &adr->hr; + + 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), + NULL != hr->hint + ? hr->hint + : "no hint provided"); + global_ret = EXIT_FAILURE; + } + 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 arguments 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 TALER_DenominationHashP 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 denomination: %s#%u at %u (skipping)\n", + err_name, + err_line, + (unsigned int) idx); + global_ret = EXIT_FAILURE; + 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 #out. + * + * @param exchange_url base URL of the exchange to use + */ +static void +trigger_upload (const char *exchange_url) +{ + struct UploadHandler uhs[] = { + { + .key = OP_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 = EXIT_FAILURE; + 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 = EXIT_FAILURE; + test_shutdown (); + return; + } + } + /* test here, in case no upload was triggered (i.e. empty input) */ + test_shutdown (); +} + + +/** + * 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; + + (void) args; + if (GNUNET_YES == GNUNET_is_zero (&auditor_pub)) + { + /* private key not available, try configuration for public key */ + char *auditor_public_key_str; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (kcfg, + "auditor", + "PUBLIC_KEY", + &auditor_public_key_str)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "auditor", + "PUBLIC_KEY"); + global_ret = EXIT_NOTCONFIGURED; + test_shutdown (); + return; + } + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_public_key_from_string ( + auditor_public_key_str, + strlen (auditor_public_key_str), + &auditor_pub.eddsa_pub)) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + "auditor", + "PUBLIC_KEY", + "invalid key"); + GNUNET_free (auditor_public_key_str); + global_ret = EXIT_NOTCONFIGURED; + test_shutdown (); + return; + } + GNUNET_free (auditor_public_key_str); + } + if (NULL != in) + { + fprintf (stderr, + "Downloaded data was not consumed, refusing upload\n"); + test_shutdown (); + global_ret = EXIT_FAILURE; + 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 = EXIT_FAILURE; + return; + } + } + if (! json_is_array (out)) + { + fprintf (stderr, + "Error: expected JSON array for `upload` command\n"); + test_shutdown (); + global_ret = EXIT_FAILURE; + 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 = EXIT_NOTCONFIGURED; + 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 kr response data + * @param keys key data from the exchange + */ +static void +keys_cb ( + void *cls, + const struct TALER_EXCHANGE_KeysResponse *kr, + struct TALER_EXCHANGE_Keys *keys) +{ + char *const *args = cls; + + exchange = NULL; + switch (kr->hr.http_status) + { + case MHD_HTTP_OK: + if (NULL == kr->hr.reply) + { + GNUNET_break (0); + test_shutdown (); + global_ret = EXIT_FAILURE; + return; + } + break; + default: + fprintf (stderr, + "Failed to download keys: %s (HTTP status: %u/%u)\n", + kr->hr.hint, + kr->hr.http_status, + (unsigned int) kr->hr.ec); + test_shutdown (); + global_ret = EXIT_FAILURE; + return; + } + in = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("operation", + OP_INPUT_KEYS), + GNUNET_JSON_pack_object_incref ("arguments", + (json_t *) kr->hr.reply)); + if (NULL == args[0]) + { + json_dumpf (in, + stdout, + JSON_INDENT (2)); + json_decref (in); + in = NULL; + } + next (args); + TALER_EXCHANGE_keys_decref (keys); +} + + +/** + * 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 = EXIT_NOTCONFIGURED; + return; + } + exchange = TALER_EXCHANGE_get_keys (ctx, + exchange_url, + NULL, + &keys_cb, + (void *) args); + GNUNET_free (exchange_url); +} + + +/** + * Output @a denomkeys for human consumption. + * + * @param denomkeys keys to output + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +show_denomkeys (const json_t *denomkeys) +{ + size_t index; + json_t *value; + + json_array_foreach (denomkeys, index, value) { + struct TALER_DenominationGroup group; + const json_t *denoms; + const char *err_name; + unsigned int err_line; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_denomination_group (NULL, + currency, + &group), + GNUNET_JSON_spec_array_const ("denoms", + &denoms), + GNUNET_JSON_spec_end () + }; + size_t index2; + json_t *value2; + + 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 = EXIT_FAILURE; + test_shutdown (); + return GNUNET_SYSERR; + } + json_array_foreach (denoms, index2, value2) { + struct GNUNET_TIME_Timestamp stamp_start; + struct GNUNET_TIME_Timestamp stamp_expire_withdraw; + struct GNUNET_TIME_Timestamp stamp_expire_deposit; + struct GNUNET_TIME_Timestamp stamp_expire_legal; + struct TALER_DenominationPublicKey denom_pub; + struct TALER_MasterSignatureP master_sig; + struct GNUNET_JSON_Specification ispec[] = { + TALER_JSON_spec_denom_pub_cipher (NULL, + group.cipher, + &denom_pub), + GNUNET_JSON_spec_timestamp ("stamp_start", + &stamp_start), + GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw", + &stamp_expire_withdraw), + GNUNET_JSON_spec_timestamp ("stamp_expire_deposit", + &stamp_expire_deposit), + GNUNET_JSON_spec_timestamp ("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 TALER_DenominationHashP h_denom_pub; + + if (GNUNET_OK != + GNUNET_JSON_parse (value2, + ispec, + &err_name, + &err_line)) + { + fprintf (stderr, + "Invalid input for denomination key to 'show': %s#%u at %u/%u (skipping)\n", + err_name, + err_line, + (unsigned int) index, + (unsigned int) index2); + GNUNET_JSON_parse_free (spec); + global_ret = EXIT_FAILURE; + test_shutdown (); + return GNUNET_SYSERR; + } + duration = GNUNET_TIME_absolute_get_difference ( + stamp_start.abs_time, + stamp_expire_withdraw.abs_time); + TALER_denom_pub_hash (&denom_pub, + &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, + &group.value, + &group.fees, + &master_pub, + &master_sig)) + { + fprintf (stderr, + "Invalid master signature for key %s (aborting)\n", + TALER_B2S (&h_denom_pub)); + global_ret = EXIT_FAILURE; + GNUNET_JSON_parse_free (ispec); + GNUNET_JSON_parse_free (spec); + 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 (&group.fees.withdraw); + deposit_fee_s = TALER_amount_to_string (&group.fees.deposit); + refresh_fee_s = TALER_amount_to_string (&group.fees.refresh); + refund_fee_s = TALER_amount_to_string (&group.fees.refund); + deposit_s = GNUNET_strdup ( + GNUNET_TIME_timestamp2s (stamp_expire_deposit)); + legal_s = GNUNET_strdup ( + GNUNET_TIME_timestamp2s (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 (&group.value), + GNUNET_TIME_timestamp2s (stamp_start), + GNUNET_TIME_relative2s (duration, + false), + 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 (ispec); + } + GNUNET_JSON_parse_free (spec); + } + return GNUNET_OK; +} + + +/** + * Parse the '/keys' input for operation called @a command_name. + * + * @param command_name name of the command, for logging errors + * @return NULL if the input is malformed + */ +static json_t * +parse_keys (const char *command_name) +{ + json_t *keys; + const char *op_str; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_json ("arguments", + &keys), + GNUNET_JSON_spec_string ("operation", + &op_str), + GNUNET_JSON_spec_end () + }; + const char *err_name; + unsigned int err_line; + + if (NULL == in) + { + json_error_t err; + + in = 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 = EXIT_FAILURE; + test_shutdown (); + return NULL; + } + } + if (GNUNET_OK != + GNUNET_JSON_parse (in, + spec, + &err_name, + &err_line)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid input to '%s': %s#%u (skipping)\n", + command_name, + err_name, + err_line); + json_dumpf (in, + stderr, + JSON_INDENT (2)); + global_ret = EXIT_FAILURE; + test_shutdown (); + return NULL; + } + if (0 != strcmp (op_str, + OP_INPUT_KEYS)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid input to '%s' : operation is `%s', expected `%s'\n", + command_name, + op_str, + OP_INPUT_KEYS); + GNUNET_JSON_parse_free (spec); + return NULL; + } + json_decref (in); + in = NULL; + return keys; +} + + +/** + * Show exchange denomination keys. + * + * @param args the array of command-line arguments to process next + */ +static void +do_show (char *const *args) +{ + json_t *keys; + const char *err_name; + unsigned int err_line; + const json_t *denomkeys; + struct TALER_MasterPublicKeyP mpub; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_array_const ("denominations", + &denomkeys), + GNUNET_JSON_spec_fixed_auto ("master_public_key", + &mpub), + GNUNET_JSON_spec_end () + }; + + keys = parse_keys ("show"); + if (NULL == keys) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Showing failed: no valid input\n"); + return; + } + if (GNUNET_OK != + GNUNET_JSON_parse (keys, + spec, + &err_name, + &err_line)) + { + fprintf (stderr, + "Invalid input to 'show': %s#%u (skipping)\n", + err_name, + err_line); + global_ret = EXIT_FAILURE; + test_shutdown (); + json_decref (keys); + 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 = EXIT_FAILURE; + test_shutdown (); + json_decref (keys); + return; + } + if (GNUNET_OK != + show_denomkeys (denomkeys)) + { + global_ret = EXIT_FAILURE; + test_shutdown (); + json_decref (keys); + return; + } + json_decref (keys); + /* do NOT consume input if next argument is '-' */ + if ( (NULL != args[0]) && + (0 == strcmp ("-", + args[0])) ) + { + next (args + 1); + return; + } + next (args); +} + + +/** + * Sign @a denomkeys with offline key. + * + * @param denomkeys keys to output + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +sign_denomkeys (const json_t *denomkeys) +{ + size_t group_idx; + json_t *value; + + json_array_foreach (denomkeys, group_idx, value) { + struct TALER_DenominationGroup group = { 0 }; + const json_t *denom_keys_array; + const char *err_name; + unsigned int err_line; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_denomination_group (NULL, + currency, + &group), + GNUNET_JSON_spec_array_const ("denoms", + &denom_keys_array), + GNUNET_JSON_spec_end () + }; + size_t index; + json_t *denom_key_obj; + + 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) group_idx); + GNUNET_JSON_parse_free (spec); + global_ret = EXIT_FAILURE; + test_shutdown (); + return GNUNET_SYSERR; + } + json_array_foreach (denom_keys_array, index, denom_key_obj) { + struct GNUNET_TIME_Timestamp stamp_start; + struct GNUNET_TIME_Timestamp stamp_expire_withdraw; + struct GNUNET_TIME_Timestamp stamp_expire_deposit; + struct GNUNET_TIME_Timestamp stamp_expire_legal; + struct TALER_DenominationPublicKey denom_pub = { + .age_mask = group.age_mask + }; + struct TALER_MasterSignatureP master_sig; + struct GNUNET_JSON_Specification ispec[] = { + TALER_JSON_spec_denom_pub_cipher (NULL, + group.cipher, + &denom_pub), + GNUNET_JSON_spec_timestamp ("stamp_start", + &stamp_start), + GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw", + &stamp_expire_withdraw), + GNUNET_JSON_spec_timestamp ("stamp_expire_deposit", + &stamp_expire_deposit), + GNUNET_JSON_spec_timestamp ("stamp_expire_legal", + &stamp_expire_legal), + GNUNET_JSON_spec_fixed_auto ("master_sig", + &master_sig), + GNUNET_JSON_spec_end () + }; + struct TALER_DenominationHashP h_denom_pub; + + if (GNUNET_OK != + GNUNET_JSON_parse (denom_key_obj, + ispec, + &err_name, + &err_line)) + { + fprintf (stderr, + "Invalid input for denomination key to 'show': %s#%u at %u/%u (skipping)\n", + err_name, + err_line, + (unsigned int) group_idx, + (unsigned int) index); + GNUNET_JSON_parse_free (spec); + global_ret = EXIT_FAILURE; + test_shutdown (); + return GNUNET_SYSERR; + } + TALER_denom_pub_hash (&denom_pub, + &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, + &group.value, + &group.fees, + &master_pub, + &master_sig)) + { + fprintf (stderr, + "Invalid master signature for key %s (aborting)\n", + TALER_B2S (&h_denom_pub)); + global_ret = EXIT_FAILURE; + 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, + &group.value, + &group.fees, + &auditor_priv, + &auditor_sig); + output_operation (OP_SIGN_DENOMINATION, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("h_denom_pub", + &h_denom_pub), + GNUNET_JSON_pack_data_auto ("auditor_sig", + &auditor_sig))); + } + GNUNET_JSON_parse_free (ispec); + } + 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) +{ + json_t *keys; + const char *err_name; + unsigned int err_line; + struct TALER_MasterPublicKeyP mpub; + const json_t *denomkeys; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_array_const ("denominations", + &denomkeys), + GNUNET_JSON_spec_fixed_auto ("master_public_key", + &mpub), + GNUNET_JSON_spec_end () + }; + + keys = parse_keys ("sign"); + if (NULL == keys) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Signing failed: no valid input\n"); + return; + } + if (GNUNET_OK != + load_offline_key (GNUNET_NO)) + { + json_decref (keys); + return; + } + if (GNUNET_OK != + GNUNET_JSON_parse (keys, + spec, + &err_name, + &err_line)) + { + fprintf (stderr, + "Invalid input to 'sign': %s#%u (skipping)\n", + err_name, + err_line); + global_ret = EXIT_FAILURE; + test_shutdown (); + json_decref (keys); + 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 = EXIT_FAILURE; + test_shutdown (); + json_decref (keys); + return; + } + if (NULL == out) + { + out = json_array (); + GNUNET_assert (NULL != out); + } + if (GNUNET_OK != + sign_denomkeys (denomkeys)) + { + global_ret = EXIT_FAILURE; + test_shutdown (); + json_decref (keys); + return; + } + json_decref (keys); + next (args); +} + + +/** + * Setup and output offline signing key. + * + * @param args the array of command-line arguments to process next + */ +static void +do_setup (char *const *args) +{ + if (GNUNET_OK != + load_offline_key (GNUNET_YES)) + { + global_ret = EXIT_FAILURE; + return; + } + if (NULL != *args) + { + if (NULL == out) + { + out = json_array (); + GNUNET_assert (NULL != out); + } + output_operation (OP_SETUP, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("auditor_pub", + &auditor_pub))); + } + + else + { + char *pub_s; + + pub_s = GNUNET_STRINGS_data_to_string_alloc (&auditor_pub, + sizeof (auditor_pub)); + fprintf (stdout, + "%s\n", + pub_s); + GNUNET_free (pub_s); + } + if ( (NULL != *args) && + (0 == strcmp (*args, + "-")) ) + args++; + next (args); +} + + +static void +work (void *cls) +{ + char *const *args = cls; + struct SubCommand cmds[] = { + { + .name = "setup", + .help = + "setup auditor offline private key and show the public key", + .cb = &do_setup + }, + { + .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 = EXIT_INVALIDARGUMENT; + } + 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) +{ + (void) cls; + (void) cfgfile; + kcfg = cfg; + if (GNUNET_OK != + TALER_config_get_currency (kcfg, + ¤cy)) + { + global_ret = EXIT_NOTCONFIGURED; + return; + } + 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 = EXIT_NOTCONFIGURED; + 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 = EXIT_NOTCONFIGURED; + 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 = EXIT_NOTCONFIGURED; + 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 + }; + enum GNUNET_GenericReturnValue ret; + + if (GNUNET_OK != + GNUNET_STRINGS_get_utf8_args (argc, argv, + &argc, &argv)) + return EXIT_INVALIDARGUMENT; + /* 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 */ + TALER_OS_init (); + ret = GNUNET_PROGRAM_run ( + argc, argv, + "taler-auditor-offline", + gettext_noop ("Operations for offline signing for a Taler exchange"), + options, + &run, NULL); + GNUNET_free_nz ((void *) argv); + if (GNUNET_SYSERR == ret) + return EXIT_INVALIDARGUMENT; + if (GNUNET_NO == ret) + return EXIT_SUCCESS; + return global_ret; +} + + +/* end of taler-auditor-offline.c */ |