summaryrefslogtreecommitdiff
path: root/src/exchange-tools/taler-exchange-offline.c
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2020-12-04 18:07:29 +0100
committerChristian Grothoff <christian@grothoff.org>2020-12-04 18:07:29 +0100
commit9b290fb26b2d18725858508bc14ae0933fff0250 (patch)
treed53ce3551185cab1dd9ae8f4989ceb6863a39a12 /src/exchange-tools/taler-exchange-offline.c
parent2dd11d7e9c8f336473ecdee2fe9db85ba831f458 (diff)
downloadexchange-9b290fb26b2d18725858508bc14ae0933fff0250.tar.gz
exchange-9b290fb26b2d18725858508bc14ae0933fff0250.tar.bz2
exchange-9b290fb26b2d18725858508bc14ae0933fff0250.zip
more funky boilerplate for taler-exchange-offline tool
Diffstat (limited to 'src/exchange-tools/taler-exchange-offline.c')
-rw-r--r--src/exchange-tools/taler-exchange-offline.c503
1 files changed, 496 insertions, 7 deletions
diff --git a/src/exchange-tools/taler-exchange-offline.c b/src/exchange-tools/taler-exchange-offline.c
index f8cbbc40a..a33b09eab 100644
--- a/src/exchange-tools/taler-exchange-offline.c
+++ b/src/exchange-tools/taler-exchange-offline.c
@@ -19,10 +19,21 @@
* @author Christian Grothoff
*/
#include <platform.h>
+#include <gnunet/gnunet_json_lib.h>
#include "taler_exchange_service.h"
/**
+ * Our private key, initialized in #load_offline_key().
+ */
+static struct TALER_MasterPrivateKeyP master_priv;
+
+/**
+ * Our private key, initialized in #load_offline_key().
+ */
+static struct TALER_MasterPublicKeyP master_pub;
+
+/**
* Our context for making HTTP requests.
*/
static struct GNUNET_CURL_Context *ctx;
@@ -42,6 +53,16 @@ static const struct GNUNET_CONFIGURATION_Handle *kcfg;
*/
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.
@@ -68,6 +89,34 @@ struct SubCommand
/**
+ * Data structure for denomination revocation requests.
+ */
+struct DenomRevocationRequest
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct DenomRevocationRequest *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct DenomRevocationRequest *prev;
+
+ /**
+ * Operation handle.
+ */
+ struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle *h;
+
+ /**
+ * Array index of the associated command.
+ */
+ size_t idx;
+};
+
+
+/**
* Next work item to perform.
*/
static struct GNUNET_SCHEDULER_Task *nxt;
@@ -77,6 +126,16 @@ static struct GNUNET_SCHEDULER_Task *nxt;
*/
static struct TALER_EXCHANGE_ManagementGetKeysHandle *mgkh;
+/**
+ * Active denomiantion revocation requests.
+ */
+static struct DenomRevocationRequest *drr_head;
+
+/**
+ * Active denomiantion revocation requests.
+ */
+static struct DenomRevocationRequest *drr_tail;
+
/**
* Shutdown task. Invoked when the application is being terminated.
@@ -87,6 +146,37 @@ static void
do_shutdown (void *cls)
{
(void) cls;
+
+ {
+ struct DenomRevocationRequest *drr;
+
+ while (NULL != (drr = drr_head))
+ {
+ fprintf (stderr,
+ "Aborting incomplete denomination revocation #%u\n",
+ (unsigned int) drr->idx);
+ TALER_EXCHANGE_management_revoke_denomination_key_cancel (drr->h);
+ GNUNET_CONTAINER_DLL_remove (drr_head,
+ drr_tail,
+ drr);
+ GNUNET_free (drr);
+ }
+ }
+ if (NULL != out)
+ {
+ json_dumpf (out,
+ stdout,
+ JSON_INDENT (2));
+ json_decref (out);
+ out = NULL;
+ }
+ if (NULL != in)
+ {
+ fprintf (stderr,
+ "Warning: input not consumed!\n");
+ json_decref (in);
+ in = NULL;
+ }
if (NULL != nxt)
{
GNUNET_SCHEDULER_cancel (nxt);
@@ -129,12 +219,372 @@ static void
next (char *const *args)
{
GNUNET_assert (NULL == nxt);
+ if (NULL == args[0])
+ {
+ GNUNET_SCHEDULER_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,
+ "exchange",
+ "MASTER_PRIV_FILE",
+ &fn))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchange",
+ "MASTER_PRIV_FILE");
+ GNUNET_SCHEDULER_shutdown ();
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_YES !=
+ GNUNET_DISK_file_test (fn))
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Exchange master private key `%s' does not exist yet, creating it!\n",
+ fn);
+ ret = GNUNET_CRYPTO_eddsa_key_from_file (fn,
+ GNUNET_YES,
+ &master_priv.eddsa_priv);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to initialize master key from file `%s': %s\n",
+ fn,
+ "could not create file");
+ GNUNET_free (fn);
+ GNUNET_SCHEDULER_shutdown ();
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (fn);
+ GNUNET_CRYPTO_eddsa_key_get_public (&master_priv.eddsa_priv,
+ &master_pub.eddsa_pub);
+ done = true;
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called with information about the post revocation operation result.
+ *
+ * @param cls closure with a `struct DenomRevocationRequest`
+ * @param hr HTTP response data
+ */
+static void
+denom_revocation_cb (
+ void *cls,
+ const struct TALER_EXCHANGE_HttpResponse *hr)
+{
+ struct DenomRevocationRequest *drr = cls;
+
+ if (MHD_HTTP_NO_CONTENT != hr->http_status)
+ {
+ fprintf (stderr,
+ "Upload failed for command %u with status %u (%s)\n",
+ (unsigned int) drr->idx,
+ hr->http_status,
+ hr->hint);
+ }
+ GNUNET_CONTAINER_DLL_remove (drr_head,
+ drr_tail,
+ drr);
+ GNUNET_free (drr);
+}
+
+
+/**
+ * Upload denomination revocation request 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_denom_revocation (const char *exchange_url,
+ size_t idx,
+ const json_t *value)
+{
+ struct TALER_MasterSignatureP master_sig;
+ struct GNUNET_HashCode h_denom_pub;
+ struct DenomRevocationRequest *drr;
+ 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 ("master_sig",
+ &master_sig),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value,
+ spec,
+ &err_name,
+ &err_line))
+ {
+ fprintf (stderr,
+ "Invalid input for denomination revocation: %s#%u at %u (skipping)\n",
+ err_name,
+ err_line,
+ (unsigned int) idx);
+ return;
+ }
+ drr = GNUNET_new (struct DenomRevocationRequest);
+ drr->idx = idx;
+ drr->h =
+ TALER_EXCHANGE_management_revoke_denomination_key (ctx,
+ exchange_url,
+ &h_denom_pub,
+ &master_sig,
+ &denom_revocation_cb,
+ drr);
+ GNUNET_CONTAINER_DLL_insert (drr_head,
+ drr_tail,
+ drr);
+}
+
+
+/**
+ * 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 = "revoke-denomination",
+ .cb = &upload_denom_revocation
+ },
+ // FIXME: many more handlers here!
+ /* 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;
+ GNUNET_SCHEDULER_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;
+ GNUNET_SCHEDULER_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");
+ GNUNET_SCHEDULER_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);
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = 2;
+ return;
+ }
+ }
+ if (! json_is_array (out))
+ {
+ fprintf (stderr,
+ "Error: expected JSON array for `upload` command\n");
+ GNUNET_SCHEDULER_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;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ trigger_upload (exchange_url);
+ GNUNET_free (exchange_url);
+}
+
+
+/**
+ * Revoke denomination key.
+ *
+ * @param args the array of command-line arguments to process next;
+ * args[0] must be the hash of the denomination key to revoke
+ */
+static void
+do_revoke_denomination_key (char *const *args)
+{
+ struct GNUNET_HashCode h_denom_pub;
+ struct TALER_MasterSignatureP master_sig;
+
+ if (NULL != in)
+ {
+ fprintf (stderr,
+ "Downloaded data was not consumed, refusing revocation\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = 4;
+ return;
+ }
+ if ( (NULL == args[0]) ||
+ (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[0],
+ strlen (args[0]),
+ &h_denom_pub,
+ sizeof (h_denom_pub))) )
+ {
+ fprintf (stderr,
+ "You must specify a denomination key with this subcommand\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = 5;
+ return;
+ }
+ if (GNUNET_OK !=
+ load_offline_key ())
+ return;
+ // FIXME: do sign, create master_sig!
+ output_operation ("revoke-denomination",
+ json_pack ("{s:o, s:o}",
+ "h_denom_pub",
+ GNUNET_JSON_from_data_auto (&h_denom_pub),
+ "master_sig",
+ GNUNET_JSON_from_data_auto (&master_sig)));
+}
+
+
+/**
* Function called with information about future keys. Dumps the JSON output
* (on success), either into an internal buffer or to stdout (depending on
* whether there are subsequent commands).
@@ -152,8 +602,30 @@ download_cb (void *cls,
char *const *args = cls;
mgkh = NULL;
- fprintf (stderr,
- "FIXME: dump!\n");
+ 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);
+ GNUNET_SCHEDULER_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);
+ }
next (args);
}
@@ -178,6 +650,7 @@ do_download (char *const *args)
"exchange",
"BASE_URL");
GNUNET_SCHEDULER_shutdown ();
+ global_ret = 1;
return;
}
mgkh = TALER_EXCHANGE_get_management_keys (ctx,
@@ -199,6 +672,19 @@ work (void *cls)
"obtain future public keys from exchange (to be performed online!)",
.cb = &do_download
},
+ {
+ .name = "revoke-denomination",
+ .help =
+ "revoke denomination key (hash of public key must be given as argument)",
+ .cb = &do_revoke_denomination_key
+ },
+ {
+ .name = "upload",
+ .help =
+ "upload operation result to exchange (to be performed online!)",
+ .cb = &do_upload
+ },
+ // FIXME: many more handlers here!
/* list terminator */
{
.name = NULL,
@@ -219,9 +705,12 @@ work (void *cls)
if (0 != strcasecmp ("help",
args[0]))
+ {
fprintf (stderr,
"Unexpected command `%s'\n",
args[0]);
+ global_ret = 3;
+ }
fprintf (stderr,
"Supported subcommands:");
for (unsigned int i = 0; NULL != cmds[i].name; i++)
@@ -259,12 +748,12 @@ run (void *cls,
/**
- * The main function of the taler-exchange-keyup tool. This tool is used to
+ * The main function of the taler-exchange-offline tool. This tool is used to
* create the signing and denomination keys for the exchange. It uses the
- * long-term offline private key and writes the (additional) key files to the
- * respective exchange directory (from where they can then be copied to the
- * online server). Note that we need (at least) the most recent generated
- * previous keys to align the validity periods.
+ * long-term offline private key 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