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; diff --git a/src/exchange/taler-exchange-httpd_auditors.c b/src/exchange/taler-exchange-httpd_auditors.c index f0fbb7ebe..62bfc3043 100644 --- a/src/exchange/taler-exchange-httpd_auditors.c +++ b/src/exchange/taler-exchange-httpd_auditors.c @@ -144,49 +144,34 @@ add_auditor_denom_sig (void *cls, TALER_B2S (awc->auditor_pub)); return GNUNET_DB_STATUS_HARD_ERROR; } + if (GNUNET_OK != + TALER_auditor_denom_validity_verify ( + auditor_url, + awc->h_denom_pub, + &TEH_master_public_key, + meta.start, + meta.expire_withdraw, + meta.expire_deposit, + meta.expire_legal, + &meta.value, + &meta.fee_withdraw, + &meta.fee_deposit, + &meta.fee_refresh, + &meta.fee_refund, + awc->auditor_pub, + &awc->auditor_sig)) { - struct TALER_ExchangeKeyValidityPS kv = { - .purpose.purpose = htonl (TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS), - .purpose.size = htonl (sizeof (kv)), - .master = TEH_master_public_key, - .start = GNUNET_TIME_absolute_hton (meta.start), - .expire_withdraw = GNUNET_TIME_absolute_hton (meta.expire_withdraw), - .expire_deposit = GNUNET_TIME_absolute_hton (meta.expire_deposit), - .expire_legal = GNUNET_TIME_absolute_hton (meta.expire_legal), - .denom_hash = *awc->h_denom_pub - }; - - TALER_amount_hton (&kv.value, - &meta.value); - TALER_amount_hton (&kv.fee_withdraw, - &meta.fee_withdraw); - TALER_amount_hton (&kv.fee_deposit, - &meta.fee_deposit); - TALER_amount_hton (&kv.fee_refresh, - &meta.fee_refresh); - TALER_amount_hton (&kv.fee_refund, - &meta.fee_refund); - GNUNET_CRYPTO_hash (auditor_url, - strlen (auditor_url) + 1, - &kv.auditor_url_hash); GNUNET_free (auditor_url); - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify ( - TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS, - &kv, - &awc->auditor_sig.eddsa_sig, - &TEH_master_public_key.eddsa_pub)) - { - /* signature invalid */ - GNUNET_break_op (0); - *mhd_ret = TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_EXCHANGE_AUDITORS_AUDITOR_SIGNATURE_INVALID, - NULL); - return GNUNET_DB_STATUS_HARD_ERROR; - } + /* signature invalid */ + GNUNET_break_op (0); + *mhd_ret = TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_EXCHANGE_AUDITORS_AUDITOR_SIGNATURE_INVALID, + NULL); + return GNUNET_DB_STATUS_HARD_ERROR; } + GNUNET_free (auditor_url); qs = TEH_plugin->insert_auditor_denom_sig (TEH_plugin->cls, session, diff --git a/src/include/taler_crypto_lib.h b/src/include/taler_crypto_lib.h index 757dee675..1c77bfe64 100644 --- a/src/include/taler_crypto_lib.h +++ b/src/include/taler_crypto_lib.h @@ -1294,6 +1294,79 @@ TALER_exchange_secmod_rsa_verify ( const struct TALER_SecurityModuleSignatureP *secm_sig); +/** + * Create denomination key validity signature by the auditor. + * + * @param auditor_url BASE URL of the auditor's API + * @param h_denom_pub hash of the denomination's public key + * @param master_pub master public key of the exchange + * @param stamp_start when does the exchange begin signing with this key + * @param stamp_expire_withdraw when does the exchange end signing with this key + * @param stamp_expire_deposit how long does the exchange accept the deposit of coins with this key + * @param stamp_expire_legal how long does the exchange preserve information for legal disputes with this key + * @param coin_value what is the value of coins signed with this key + * @param fee_withdraw what withdraw fee does the exchange charge for this denomination + * @param fee_deposit what deposit fee does the exchange charge for this denomination + * @param fee_refresh what refresh fee does the exchange charge for this denomination + * @param fee_refund what refund fee does the exchange charge for this denomination + * @param auditor_priv private key to sign with + * @param[out] auditor_sig where to write the signature + */ +void +TALER_auditor_denom_validity_sign ( + const char *auditor_url, + const struct GNUNET_HashCode *h_denom_pub, + const struct TALER_MasterPublicKeyP *master_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, + const struct TALER_Amount *coin_value, + const struct TALER_Amount *fee_withdraw, + const struct TALER_Amount *fee_deposit, + const struct TALER_Amount *fee_refresh, + const struct TALER_Amount *fee_refund, + const struct TALER_AuditorPrivateKeyP *auditor_priv, + struct TALER_AuditorSignatureP *auditor_sig); + + +/** + * Verify denomination key validity signature from auditor. + * + * @param auditor_url BASE URL of the auditor's API + * @param h_denom_pub hash of the denomination's public key + * @param master_pub master public key of the exchange + * @param stamp_start when does the exchange begin signing with this key + * @param stamp_expire_withdraw when does the exchange end signing with this key + * @param stamp_expire_deposit how long does the exchange accept the deposit of coins with this key + * @param stamp_expire_legal how long does the exchange preserve information for legal disputes with this key + * @param coin_value what is the value of coins signed with this key + * @param fee_withdraw what withdraw fee does the exchange charge for this denomination + * @param fee_deposit what deposit fee does the exchange charge for this denomination + * @param fee_refresh what refresh fee does the exchange charge for this denomination + * @param fee_refund what refund fee does the exchange charge for this denomination + * @param auditor_pub public key to verify against + * @param auditor_sig the signature the signature + * @return #GNUNET_OK if the signature is valid + */ +int +TALER_auditor_denom_validity_verify ( + const char *auditor_url, + const struct GNUNET_HashCode *h_denom_pub, + const struct TALER_MasterPublicKeyP *master_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, + const struct TALER_Amount *coin_value, + const struct TALER_Amount *fee_withdraw, + const struct TALER_Amount *fee_deposit, + const struct TALER_Amount *fee_refresh, + const struct TALER_Amount *fee_refund, + const struct TALER_AuditorPublicKeyP *auditor_pub, + const struct TALER_AuditorSignatureP *auditor_sig); + + /* **************** /wire account offline signing **************** */ diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h index 512dfb35c..5691e58de 100644 --- a/src/include/taler_exchange_service.h +++ b/src/include/taler_exchange_service.h @@ -393,7 +393,7 @@ struct TALER_EXCHANGE_HttpResponse /** * Function called with information about who is auditing - * a particular exchange and what key the exchange is using. + * a particular exchange and what keys the exchange is using. * * @param cls closure * @param hr HTTP response data diff --git a/src/include/taler_signatures.h b/src/include/taler_signatures.h index c30f21d60..5f380812c 100644 --- a/src/include/taler_signatures.h +++ b/src/include/taler_signatures.h @@ -1041,7 +1041,7 @@ struct TALER_ExchangeKeyValidityPS /** * The long-term offline master key of the exchange, affirmed by the - * auditor. Hashed string, including 0-terminator. + * auditor. */ struct TALER_MasterPublicKeyP master; diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c index e630adf91..9d4ebf569 100644 --- a/src/lib/exchange_api_handle.c +++ b/src/lib/exchange_api_handle.c @@ -519,7 +519,6 @@ parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor, unsigned int off; unsigned int i; const char *auditor_url; - struct TALER_ExchangeKeyValidityPS kv; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_fixed_auto ("auditor_pub", &auditor->auditor_pub), @@ -539,12 +538,6 @@ parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor, return GNUNET_SYSERR; } auditor->auditor_url = GNUNET_strdup (auditor_url); - kv.purpose.purpose = htonl (TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS); - kv.purpose.size = htonl (sizeof (struct TALER_ExchangeKeyValidityPS)); - GNUNET_CRYPTO_hash (auditor_url, - strlen (auditor_url) + 1, - &kv.auditor_url_hash); - kv.master = key_data->master_pub; len = json_array_size (keys); auditor->denom_keys = GNUNET_new_array (len, struct @@ -590,27 +583,22 @@ parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor, } if (check_sigs) { - kv.start = GNUNET_TIME_absolute_hton (dk->valid_from); - kv.expire_withdraw = GNUNET_TIME_absolute_hton (dk->withdraw_valid_until); - kv.expire_deposit = GNUNET_TIME_absolute_hton (dk->expire_deposit); - kv.expire_legal = GNUNET_TIME_absolute_hton (dk->expire_legal); - TALER_amount_hton (&kv.value, - &dk->value); - TALER_amount_hton (&kv.fee_withdraw, - &dk->fee_withdraw); - TALER_amount_hton (&kv.fee_deposit, - &dk->fee_deposit); - TALER_amount_hton (&kv.fee_refresh, - &dk->fee_refresh); - TALER_amount_hton (&kv.fee_refund, - &dk->fee_refund); - kv.denom_hash = dk->h_key; - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS, - &kv, - &auditor_sig.eddsa_sig, - &auditor->auditor_pub.eddsa_pub)) + TALER_auditor_denom_validity_verify ( + auditor_url, + &dk->h_key, + &key_data->master_pub, + dk->valid_from, + dk->withdraw_valid_until, + dk->expire_deposit, + dk->expire_legal, + &dk->value, + &dk->fee_withdraw, + &dk->fee_deposit, + &dk->fee_refresh, + &dk->fee_refund, + &auditor->auditor_pub, + &auditor_sig)) { GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); diff --git a/src/testing/testing_api_cmd_auditor_add_denom_sig.c b/src/testing/testing_api_cmd_auditor_add_denom_sig.c index 8d5aace40..efa4a9d76 100644 --- a/src/testing/testing_api_cmd_auditor_add_denom_sig.c +++ b/src/testing/testing_api_cmd_auditor_add_denom_sig.c @@ -139,36 +139,25 @@ auditor_add_run (void *cls, } else { - struct TALER_ExchangeKeyValidityPS kv = { - .purpose.purpose = htonl (TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS), - .purpose.size = htonl (sizeof (struct TALER_ExchangeKeyValidityPS)), - .start = GNUNET_TIME_absolute_hton (dk->valid_from), - .expire_withdraw = GNUNET_TIME_absolute_hton ( - dk->withdraw_valid_until), - .expire_deposit = GNUNET_TIME_absolute_hton (dk->expire_deposit), - .expire_legal = GNUNET_TIME_absolute_hton (dk->expire_legal), - .denom_hash = dk->h_key - }; + struct TALER_MasterPublicKeyP master_pub; - TALER_amount_hton (&kv.value, - &dk->value); - TALER_amount_hton (&kv.fee_withdraw, - &dk->fee_withdraw); - TALER_amount_hton (&kv.fee_deposit, - &dk->fee_deposit); - TALER_amount_hton (&kv.fee_refresh, - &dk->fee_refresh); - TALER_amount_hton (&kv.fee_refund, - &dk->fee_refund); GNUNET_CRYPTO_eddsa_key_get_public (&is->master_priv.eddsa_priv, - &kv.master.eddsa_pub); - GNUNET_CRYPTO_hash (is->auditor_url, - strlen (is->auditor_url) + 1, - &kv.auditor_url_hash); - /* Finally sign ... */ - GNUNET_CRYPTO_eddsa_sign (&is->auditor_priv.eddsa_priv, - &kv, - &auditor_sig.eddsa_sig); + &master_pub.eddsa_pub); + TALER_auditor_denom_validity_sign ( + is->auditor_url, + &dk->h_key, + &master_pub, + dk->valid_from, + dk->withdraw_valid_until, + dk->expire_deposit, + dk->expire_legal, + &dk->value, + &dk->fee_withdraw, + &dk->fee_deposit, + &dk->fee_refresh, + &dk->fee_refund, + &is->auditor_priv, + &auditor_sig); } ds->dh = TALER_EXCHANGE_add_auditor_denomination ( is->ctx, diff --git a/src/util/Makefile.am b/src/util/Makefile.am index bf460a57d..9dcc40356 100644 --- a/src/util/Makefile.am +++ b/src/util/Makefile.am @@ -60,6 +60,7 @@ lib_LTLIBRARIES = \ libtalerutil_la_SOURCES = \ amount.c \ + auditor_signatures.c \ config.c \ crypto.c \ crypto_helper_denom.c \ diff --git a/src/util/auditor_signatures.c b/src/util/auditor_signatures.c new file mode 100644 index 000000000..837b31c6c --- /dev/null +++ b/src/util/auditor_signatures.c @@ -0,0 +1,122 @@ +/* + 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 auditor_signatures.c + * @brief Utility functions for Taler auditor signatures + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_util.h" +#include "taler_signatures.h" + + +void +TALER_auditor_denom_validity_sign ( + const char *auditor_url, + const struct GNUNET_HashCode *h_denom_pub, + const struct TALER_MasterPublicKeyP *master_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, + const struct TALER_Amount *coin_value, + const struct TALER_Amount *fee_withdraw, + const struct TALER_Amount *fee_deposit, + const struct TALER_Amount *fee_refresh, + const struct TALER_Amount *fee_refund, + const struct TALER_AuditorPrivateKeyP *auditor_priv, + struct TALER_AuditorSignatureP *auditor_sig) +{ + struct TALER_ExchangeKeyValidityPS kv = { + .purpose.purpose = htonl (TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS), + .purpose.size = htonl (sizeof (kv)), + .start = GNUNET_TIME_absolute_hton (stamp_start), + .expire_withdraw = GNUNET_TIME_absolute_hton (stamp_expire_withdraw), + .expire_deposit = GNUNET_TIME_absolute_hton (stamp_expire_deposit), + .expire_legal = GNUNET_TIME_absolute_hton (stamp_expire_legal), + .denom_hash = *h_denom_pub, + .master = *master_pub, + }; + + TALER_amount_hton (&kv.value, + coin_value); + TALER_amount_hton (&kv.fee_withdraw, + fee_withdraw); + TALER_amount_hton (&kv.fee_deposit, + fee_deposit); + TALER_amount_hton (&kv.fee_refresh, + fee_refresh); + TALER_amount_hton (&kv.fee_refund, + fee_refund); + GNUNET_CRYPTO_hash (auditor_url, + strlen (auditor_url) + 1, + &kv.auditor_url_hash); + GNUNET_CRYPTO_eddsa_sign (&auditor_priv->eddsa_priv, + &kv, + &auditor_sig->eddsa_sig); +} + + +int +TALER_auditor_denom_validity_verify ( + const char *auditor_url, + const struct GNUNET_HashCode *h_denom_pub, + const struct TALER_MasterPublicKeyP *master_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, + const struct TALER_Amount *coin_value, + const struct TALER_Amount *fee_withdraw, + const struct TALER_Amount *fee_deposit, + const struct TALER_Amount *fee_refresh, + const struct TALER_Amount *fee_refund, + const struct TALER_AuditorPublicKeyP *auditor_pub, + const struct TALER_AuditorSignatureP *auditor_sig) +{ + struct TALER_ExchangeKeyValidityPS kv = { + .purpose.purpose = htonl (TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS), + .purpose.size = htonl (sizeof (kv)), + .start = GNUNET_TIME_absolute_hton (stamp_start), + .expire_withdraw = GNUNET_TIME_absolute_hton (stamp_expire_withdraw), + .expire_deposit = GNUNET_TIME_absolute_hton (stamp_expire_deposit), + .expire_legal = GNUNET_TIME_absolute_hton (stamp_expire_legal), + .denom_hash = *h_denom_pub, + .master = *master_pub, + }; + + TALER_amount_hton (&kv.value, + coin_value); + TALER_amount_hton (&kv.fee_withdraw, + fee_withdraw); + TALER_amount_hton (&kv.fee_deposit, + fee_deposit); + TALER_amount_hton (&kv.fee_refresh, + fee_refresh); + TALER_amount_hton (&kv.fee_refund, + fee_refund); + GNUNET_CRYPTO_hash (auditor_url, + strlen (auditor_url) + 1, + &kv.auditor_url_hash); + return + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS, + &kv, + &auditor_sig->eddsa_sig, + &auditor_pub->eddsa_pub); +} + + +/* end of auditor_signatures.c */