summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am3
-rw-r--r--src/authorization/Makefile.am98
-rw-r--r--src/authorization/anastasis_authorization_plugin.c239
-rw-r--r--src/authorization/anastasis_authorization_plugin_email.c616
-rw-r--r--src/authorization/anastasis_authorization_plugin_file.c302
-rw-r--r--src/authorization/anastasis_authorization_plugin_post.c655
-rw-r--r--src/authorization/anastasis_authorization_plugin_sms.c607
-rw-r--r--src/authorization/authorization-email-messages.json10
-rw-r--r--src/authorization/authorization-post-messages.json7
-rw-r--r--src/authorization/authorization-sms-messages.json6
-rw-r--r--src/backend/Makefile.am45
-rw-r--r--src/backend/anastasis-httpd.c943
-rw-r--r--src/backend/anastasis-httpd.h225
-rw-r--r--src/backend/anastasis-httpd_config.c132
-rw-r--r--src/backend/anastasis-httpd_config.h41
-rw-r--r--src/backend/anastasis-httpd_mhd.c70
-rw-r--r--src/backend/anastasis-httpd_mhd.h61
-rw-r--r--src/backend/anastasis-httpd_policy.c252
-rw-r--r--src/backend/anastasis-httpd_policy.h66
-rw-r--r--src/backend/anastasis-httpd_policy_upload.c1211
-rw-r--r--src/backend/anastasis-httpd_terms.c98
-rw-r--r--src/backend/anastasis-httpd_terms.h62
-rw-r--r--src/backend/anastasis-httpd_truth.c1428
-rw-r--r--src/backend/anastasis-httpd_truth.h75
-rw-r--r--src/backend/anastasis-httpd_truth_upload.c855
-rw-r--r--src/backend/anastasis.conf77
-rw-r--r--src/cli/.gitignore6
-rw-r--r--src/cli/Makefile.am56
-rw-r--r--src/cli/anastasis-cli-redux.c366
-rw-r--r--src/cli/resources/00-backup.json8
-rw-r--r--src/cli/resources/00-recovery.json8
-rw-r--r--src/cli/resources/01-backup.json41
-rw-r--r--src/cli/resources/01-recovery.json41
-rw-r--r--src/cli/resources/02-backup.json83
-rw-r--r--src/cli/resources/02-recovery.json83
-rw-r--r--src/cli/resources/03-backup.json155
-rw-r--r--src/cli/resources/04-backup.json172
-rw-r--r--src/cli/resources/05-backup.json213
-rw-r--r--src/cli/resources/06-backup.json223
-rw-r--r--src/cli/test_anastasis_reducer_1.conf9
-rw-r--r--src/cli/test_anastasis_reducer_2.conf9
-rw-r--r--src/cli/test_anastasis_reducer_3.conf9
-rw-r--r--src/cli/test_anastasis_reducer_4.conf9
-rwxr-xr-xsrc/cli/test_anastasis_reducer_add_authentication.sh134
-rwxr-xr-xsrc/cli/test_anastasis_reducer_backup_enter_user_attributes.sh140
-rwxr-xr-xsrc/cli/test_anastasis_reducer_done_authentication.sh65
-rwxr-xr-xsrc/cli/test_anastasis_reducer_done_policy_review.sh105
-rwxr-xr-xsrc/cli/test_anastasis_reducer_enter_secret.sh417
-rwxr-xr-xsrc/cli/test_anastasis_reducer_initialize_state.sh64
-rwxr-xr-xsrc/cli/test_anastasis_reducer_recovery_enter_user_attributes.sh516
-rwxr-xr-xsrc/cli/test_anastasis_reducer_select_continent.sh116
-rwxr-xr-xsrc/cli/test_anastasis_reducer_select_country.sh144
-rw-r--r--src/cli/test_reducer.conf197
-rw-r--r--src/cli/user-details-example.json6
-rw-r--r--src/include/Makefile.am15
-rw-r--r--src/include/anastasis.h986
-rw-r--r--src/include/anastasis_authorization_lib.h52
-rw-r--r--src/include/anastasis_authorization_plugin.h183
-rw-r--r--src/include/anastasis_crypto_lib.h533
-rw-r--r--src/include/anastasis_database_lib.h49
-rw-r--r--src/include/anastasis_database_plugin.h655
-rw-r--r--src/include/anastasis_json.h410
-rw-r--r--src/include/anastasis_redux.h127
-rw-r--r--src/include/anastasis_service.h703
-rw-r--r--src/include/anastasis_testing_lib.h884
-rw-r--r--src/include/anastasis_util_lib.h82
-rw-r--r--src/include/gettext.h71
-rw-r--r--src/include/platform.h60
-rw-r--r--src/lib/Makefile.am27
-rw-r--r--src/lib/anastasis_backup.c979
-rw-r--r--src/lib/anastasis_recovery.c1425
-rw-r--r--src/lib/test_merchant.priv1
-rw-r--r--src/reducer/Makefile.am45
-rw-r--r--src/reducer/anastasis_api_backup_redux.c4893
-rw-r--r--src/reducer/anastasis_api_recovery_redux.c2558
-rw-r--r--src/reducer/anastasis_api_redux.c1730
-rw-r--r--src/reducer/anastasis_api_redux.h347
-rw-r--r--src/reducer/validation_CH_AHV.c57
-rw-r--r--src/reducer/validation_CZ_BN.c59
-rw-r--r--src/reducer/validation_DE_SVN.c98
-rw-r--r--src/reducer/validation_DE_TIN.c57
-rw-r--r--src/reducer/validation_IN_AADHAR.c113
-rw-r--r--src/reducer/validation_IT_CF.c198
-rw-r--r--src/reducer/validation_XX_SQUARE.c48
-rw-r--r--src/reducer/validation_XY_PRIME.c53
-rw-r--r--src/restclient/Makefile.am42
-rw-r--r--src/restclient/anastasis_api_config.c317
-rw-r--r--src/restclient/anastasis_api_curl_defaults.c46
-rw-r--r--src/restclient/anastasis_api_curl_defaults.h38
-rw-r--r--src/restclient/anastasis_api_keyshare_lookup.c508
-rw-r--r--src/restclient/anastasis_api_policy_lookup.c356
-rw-r--r--src/restclient/anastasis_api_policy_store.c527
-rw-r--r--src/restclient/anastasis_api_truth_store.c354
-rw-r--r--src/stasis/Datenbank-Schema.xml1
-rw-r--r--src/stasis/Makefile.am95
-rw-r--r--src/stasis/anastasis-dbinit.c112
-rw-r--r--src/stasis/anastasis_db_plugin.c146
-rw-r--r--src/stasis/anastasis_db_postgres.conf7
-rw-r--r--src/stasis/drop0001.sql37
-rw-r--r--src/stasis/plugin_anastasis_postgres.c2301
-rw-r--r--src/stasis/stasis-0000.sql293
-rw-r--r--src/stasis/stasis-0001.sql194
-rw-r--r--src/stasis/stasis-postgres.conf6
-rw-r--r--src/stasis/test_anastasis_db.c344
-rw-r--r--src/stasis/test_anastasis_db_postgres.conf10
-rw-r--r--src/testing/.gitignore3
-rw-r--r--src/testing/Makefile.am91
-rwxr-xr-xsrc/testing/sms_authentication.sh10
-rw-r--r--src/testing/test_anastasis.c464
-rw-r--r--src/testing/test_anastasis_api.c421
-rw-r--r--src/testing/test_anastasis_api.conf264
-rw-r--r--src/testing/test_anastasis_api_home/.config/taler/exchange/account-2.json3
-rw-r--r--src/testing/test_anastasis_api_home/.config/taler/merchant/account-3.json1
-rw-r--r--src/testing/test_anastasis_api_home/.config/taler/merchant/default.priv1
-rw-r--r--src/testing/test_anastasis_api_home/.config/taler/merchant/dtip.priv1
-rw-r--r--src/testing/test_anastasis_api_home/.config/taler/merchant/nulltip.priv1
-rw-r--r--src/testing/test_anastasis_api_home/.config/taler/merchant/reserve/dtip.priv1
-rw-r--r--src/testing/test_anastasis_api_home/.config/taler/merchant/reserve/nulltip.priv1
-rw-r--r--src/testing/test_anastasis_api_home/.config/taler/merchant/reserve/tip.priv1
-rw-r--r--src/testing/test_anastasis_api_home/.config/taler/merchant/tip.privbin0 -> 32 bytes
-rw-r--r--src/testing/test_anastasis_api_home/.config/taler/merchant/tor.privbin0 -> 32 bytes
-rw-r--r--src/testing/test_anastasis_api_home/.config/taler/test.json8
-rw-r--r--src/testing/test_anastasis_api_home/.local/share/taler/exchange/offline-keys/master.priv1
-rw-r--r--src/testing/test_anastasis_api_home/.local/share/taler/merchant/merchant.priv1
-rw-r--r--src/testing/testing_api_cmd_config.c206
-rw-r--r--src/testing/testing_api_cmd_keyshare_lookup.c465
-rw-r--r--src/testing/testing_api_cmd_policy_lookup.c267
-rw-r--r--src/testing/testing_api_cmd_policy_store.c397
-rw-r--r--src/testing/testing_api_cmd_truth_store.c436
-rw-r--r--src/testing/testing_api_helpers.c173
-rw-r--r--src/testing/testing_api_trait_account_priv.c72
-rw-r--r--src/testing/testing_api_trait_account_pub.c72
-rw-r--r--src/testing/testing_api_trait_code.c73
-rw-r--r--src/testing/testing_api_trait_eks.c58
-rw-r--r--src/testing/testing_api_trait_hash.c72
-rw-r--r--src/testing/testing_api_trait_payment_secret.c73
-rw-r--r--src/testing/testing_api_trait_salt.c74
-rw-r--r--src/testing/testing_api_trait_truth_key.c58
-rw-r--r--src/testing/testing_api_trait_truth_uuid.c74
-rw-r--r--src/testing/testing_cmd_challenge_answer.c584
-rw-r--r--src/testing/testing_cmd_policy_create.c208
-rw-r--r--src/testing/testing_cmd_recover_secret.c518
-rw-r--r--src/testing/testing_cmd_secret_share.c441
-rw-r--r--src/testing/testing_cmd_truth_upload.c383
-rw-r--r--src/testing/testing_trait_challenge.c72
-rw-r--r--src/testing/testing_trait_core_secret.c74
-rw-r--r--src/testing/testing_trait_policy.c73
-rw-r--r--src/testing/testing_trait_truth.c73
-rw-r--r--src/util/Makefile.am57
-rw-r--r--src/util/anastasis-config.in12
-rw-r--r--src/util/anastasis_crypto.c637
-rw-r--r--src/util/os_installation.c71
-rw-r--r--src/util/paths.conf29
-rw-r--r--src/util/test_anastasis_crypto.c346
154 files changed, 42232 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..5e006a7
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,3 @@
+# This Makefile is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include
+SUBDIRS = include util stasis authorization backend restclient lib testing reducer cli
diff --git a/src/authorization/Makefile.am b/src/authorization/Makefile.am
new file mode 100644
index 0000000..8ea7e86
--- /dev/null
+++ b/src/authorization/Makefile.am
@@ -0,0 +1,98 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include
+
+pkgcfgdir = $(prefix)/share/anastasis/config.d/
+plugindir = $(libdir)/anastasis
+pkgdatadir= $(prefix)/share/anastasis/
+
+pkgdata_DATA = \
+ authorization-email-messages.json \
+ authorization-post-messages.json \
+ authorization-sms-messages.json
+
+EXTRA_DIST = $(pkgdata_DATA)
+
+
+if USE_COVERAGE
+ AM_CFLAGS = --coverage -O0
+ XLIB = -lgcov
+endif
+
+lib_LTLIBRARIES = \
+ libanastasisauthorization.la
+
+libanastasisauthorization_la_SOURCES = \
+ anastasis_authorization_plugin.c
+libanastasisauthorization_la_LIBADD = \
+ $(LTLIBINTL)
+libanastasisauthorization_la_LDFLAGS = \
+ -ltalerutil \
+ -lgnunetutil \
+ -lmicrohttpd \
+ -lltdl \
+ $(XLIB)
+
+plugin_LTLIBRARIES = \
+ libanastasis_plugin_authorization_email.la \
+ libanastasis_plugin_authorization_file.la \
+ libanastasis_plugin_authorization_post.la \
+ libanastasis_plugin_authorization_sms.la
+libanastasis_plugin_authorization_file_la_SOURCES = \
+ anastasis_authorization_plugin_file.c
+libanastasis_plugin_authorization_file_la_LIBADD = \
+ $(LTLIBINTL)
+libanastasis_plugin_authorization_file_la_LDFLAGS = \
+ $(ANASTASIS_PLUGIN_LDFLAGS) \
+ -ltalerjson \
+ -ltalermhd \
+ -ltalerutil \
+ -lgnunetjson \
+ -lgnunetutil \
+ -lmicrohttpd \
+ -ljansson \
+ $(XLIB)
+
+libanastasis_plugin_authorization_email_la_SOURCES = \
+ anastasis_authorization_plugin_email.c
+libanastasis_plugin_authorization_email_la_LIBADD = \
+ $(LTLIBINTL)
+libanastasis_plugin_authorization_email_la_LDFLAGS = \
+ $(ANASTASIS_PLUGIN_LDFLAGS) \
+ -ltalerjson \
+ -ltalermhd \
+ -ltalerutil \
+ -lgnunetjson \
+ -lgnunetutil \
+ -lmicrohttpd \
+ -ljansson \
+ $(XLIB)
+
+libanastasis_plugin_authorization_post_la_SOURCES = \
+ anastasis_authorization_plugin_post.c
+libanastasis_plugin_authorization_post_la_LIBADD = \
+ $(LTLIBINTL)
+libanastasis_plugin_authorization_post_la_LDFLAGS = \
+ $(ANASTASIS_PLUGIN_LDFLAGS) \
+ -ltalerjson \
+ -ltalermhd \
+ -ltalerutil \
+ -lgnunetjson \
+ -lgnunetutil \
+ -lmicrohttpd \
+ -ljansson \
+ $(XLIB)
+
+libanastasis_plugin_authorization_sms_la_SOURCES = \
+ anastasis_authorization_plugin_sms.c
+libanastasis_plugin_authorization_sms_la_LIBADD = \
+ $(LTLIBINTL)
+libanastasis_plugin_authorization_sms_la_LDFLAGS = \
+ $(ANASTASIS_PLUGIN_LDFLAGS) \
+ -ltalerjson \
+ -ltalermhd \
+ -ltalerutil \
+ -lgnunetjson \
+ -lgnunetutil \
+ -ljansson \
+ -lmicrohttpd \
+ $(XLIB)
diff --git a/src/authorization/anastasis_authorization_plugin.c b/src/authorization/anastasis_authorization_plugin.c
new file mode 100644
index 0000000..7874594
--- /dev/null
+++ b/src/authorization/anastasis_authorization_plugin.c
@@ -0,0 +1,239 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015, 2016, 2021 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser 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 anastasis_authorization_plugin.c
+ * @brief Logic to load database plugin
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ */
+#include "platform.h"
+#include "anastasis_authorization_plugin.h"
+#include <ltdl.h>
+
+
+/**
+ * Head of linked list for all loaded plugins
+ */
+static struct AuthPlugin *ap_head;
+
+/**
+ * Tail ofinked list for all loaded plugins
+ */
+static struct AuthPlugin *ap_tail;
+
+
+/**
+ * Authentication plugin which is used to verify code based authentication
+ * like SMS, E-Mail.
+ */
+struct AuthPlugin
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct AuthPlugin *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct AuthPlugin *prev;
+
+ /**
+ * Actual plugin handle.
+ */
+ struct ANASTASIS_AuthorizationPlugin *authorization;
+
+ /**
+ * I.e. "sms", "phone".
+ */
+ char *name;
+
+ /**
+ * Name of the shared object providing the plugin logic.
+ */
+ char *lib_name;
+
+ /**
+ * Cost of using this plugin.
+ */
+ struct TALER_Amount cost;
+};
+
+
+struct ANASTASIS_AuthorizationPlugin *
+ANASTASIS_authorization_plugin_load (
+ const char *method,
+ const struct GNUNET_CONFIGURATION_Handle *AH_cfg,
+ struct TALER_Amount *cost)
+{
+ struct ANASTASIS_AuthorizationPlugin *authorization;
+ char *lib_name;
+ char *sec_name;
+ struct AuthPlugin *ap;
+ char *currency;
+
+ for (ap = ap_head; NULL != ap; ap = ap->next)
+ if (0 == strcmp (method,
+ ap->name))
+ {
+ *cost = ap->cost;
+ return ap->authorization;
+ }
+ if (GNUNET_OK !=
+ TALER_config_get_currency (AH_cfg,
+ &currency))
+ return NULL;
+ ap = GNUNET_new (struct AuthPlugin);
+ GNUNET_asprintf (&sec_name,
+ "authorization-%s",
+ method);
+ if (GNUNET_OK !=
+ TALER_config_get_amount (AH_cfg,
+ sec_name,
+ "COST",
+ &ap->cost))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
+ sec_name,
+ "COST");
+ GNUNET_free (sec_name);
+ GNUNET_free (currency);
+ GNUNET_free (ap);
+ return NULL;
+ }
+
+ if (0 !=
+ strcasecmp (currency,
+ ap->cost.currency))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ sec_name,
+ "COST",
+ "currency mismatch");
+ GNUNET_free (currency);
+ GNUNET_free (sec_name);
+ GNUNET_free (ap);
+ return NULL;
+ }
+ GNUNET_free (currency);
+ GNUNET_free (sec_name);
+ GNUNET_asprintf (&lib_name,
+ "libanastasis_plugin_authorization_%s",
+ method);
+ authorization = GNUNET_PLUGIN_load (lib_name,
+ (void *) AH_cfg);
+ if (NULL == authorization)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Authentication method `%s' not supported\n",
+ method);
+ GNUNET_free (lib_name);
+ GNUNET_free (ap);
+ return NULL;
+ }
+ ap->name = GNUNET_strdup (method);
+ ap->lib_name = lib_name;
+ ap->authorization = authorization;
+ GNUNET_CONTAINER_DLL_insert (ap_head,
+ ap_tail,
+ ap);
+ *cost = ap->cost;
+ return authorization;
+}
+
+
+void
+ANASTASIS_authorization_plugin_shutdown (void)
+{
+ struct AuthPlugin *ap;
+
+ while (NULL != (ap = ap_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (ap_head,
+ ap_tail,
+ ap);
+ GNUNET_PLUGIN_unload (ap->lib_name,
+ ap->authorization);
+ GNUNET_free (ap->lib_name);
+ GNUNET_free (ap->name);
+ GNUNET_free (ap);
+ }
+}
+
+
+/**
+ * Libtool search path before we started.
+ */
+static char *old_dlsearchpath;
+
+
+/**
+ * Setup libtool paths.
+ */
+void __attribute__ ((constructor))
+anastasis_authorization_plugin_init (void)
+{
+ int err;
+ const char *opath;
+ char *path;
+ char *cpath;
+
+ err = lt_dlinit ();
+ if (err > 0)
+ {
+ fprintf (stderr,
+ _ ("Initialization of plugin mechanism failed: %s!\n"),
+ lt_dlerror ());
+ return;
+ }
+ opath = lt_dlgetsearchpath ();
+ if (NULL != opath)
+ old_dlsearchpath = GNUNET_strdup (opath);
+ path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_LIBDIR);
+ if (NULL != path)
+ {
+ if (NULL != opath)
+ {
+ GNUNET_asprintf (&cpath, "%s:%s", opath, path);
+ lt_dlsetsearchpath (cpath);
+ GNUNET_free (path);
+ GNUNET_free (cpath);
+ }
+ else
+ {
+ lt_dlsetsearchpath (path);
+ GNUNET_free (path);
+ }
+ }
+}
+
+
+/**
+ * Shutdown libtool.
+ */
+void __attribute__ ((destructor))
+anastasis_authorization_plugin_fini (void)
+{
+ lt_dlsetsearchpath (old_dlsearchpath);
+ if (NULL != old_dlsearchpath)
+ {
+ GNUNET_free (old_dlsearchpath);
+ }
+ lt_dlexit ();
+}
+
+
+/* end of anastasis_authorization_plugin.c */
diff --git a/src/authorization/anastasis_authorization_plugin_email.c b/src/authorization/anastasis_authorization_plugin_email.c
new file mode 100644
index 0000000..33c400b
--- /dev/null
+++ b/src/authorization/anastasis_authorization_plugin_email.c
@@ -0,0 +1,616 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2019-2021 Anastasis SARL
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file include/anastasis_authorization_plugin_email.c
+ * @brief authorization plugin email based
+ * @author Dominik Meister
+ */
+#include "platform.h"
+#include "anastasis_authorization_plugin.h"
+#include <taler/taler_mhd_lib.h>
+#include <taler/taler_json_lib.h>
+#include <regex.h>
+#include "anastasis_util_lib.h"
+
+
+/**
+ * Saves the State of a authorization plugin.
+ */
+struct Email_Context
+{
+
+ /**
+ * Command which is executed to run the plugin (some bash script or a
+ * command line argument)
+ */
+ char *auth_command;
+
+ /**
+ * Regex for email address validation.
+ */
+ regex_t regex;
+
+ /**
+ * Messages of the plugin, read from a resource file.
+ */
+ json_t *messages;
+
+};
+
+
+/**
+ * Saves the state of a authorization process
+ */
+struct ANASTASIS_AUTHORIZATION_State
+{
+ /**
+ * Public key of the challenge which is authorised
+ */
+ struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid;
+
+ /**
+ * Code which is sent to the user.
+ */
+ uint64_t code;
+
+ /**
+ * Our plugin context.
+ */
+ struct Email_Context *ctx;
+
+ /**
+ * Function to call when we made progress.
+ */
+ GNUNET_SCHEDULER_TaskCallback trigger;
+
+ /**
+ * Closure for @e trigger.
+ */
+ void *trigger_cls;
+
+ /**
+ * holds the truth information
+ */
+ char *email;
+
+ /**
+ * Handle to the helper process.
+ */
+ struct GNUNET_OS_Process *child;
+
+ /**
+ * Handle to wait for @e child
+ */
+ struct GNUNET_ChildWaitHandle *cwh;
+
+ /**
+ * Our client connection, set if suspended.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * Message to send.
+ */
+ char *msg;
+
+ /**
+ * Offset of transmission in msg.
+ */
+ size_t msg_off;
+
+ /**
+ * Exit code from helper.
+ */
+ long unsigned int exit_code;
+
+ /**
+ * How did the helper die?
+ */
+ enum GNUNET_OS_ProcessStatusType pst;
+
+};
+
+
+/**
+ * Obtain internationalized message @a msg_id from @a ctx using
+ * language preferences of @a conn.
+ *
+ * @param messages JSON object to lookup message from
+ * @param conn connection to lookup message for
+ * @param msg_id unique message ID
+ * @return NULL if message was not found
+ */
+static const char *
+get_message (const json_t *messages,
+ struct MHD_Connection *conn,
+ const char *msg_id)
+{
+ const char *accept_lang;
+
+ accept_lang = MHD_lookup_connection_value (conn,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
+ if (NULL == accept_lang)
+ accept_lang = "en_US";
+ {
+ const char *ret;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_i18n_string (msg_id,
+ accept_lang,
+ &ret),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (messages,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ return ret;
+ }
+}
+
+
+/**
+ * Validate @a data is a well-formed input into the challenge method,
+ * i.e. @a data is a well-formed phone number for sending an SMS, or
+ * a well-formed e-mail address for sending an e-mail. Not expected to
+ * check that the phone number or e-mail account actually exists.
+ *
+ * To be possibly used before issuing a 402 payment required to the client.
+ *
+ * @param cls closure
+ * @param connection HTTP client request (for queuing response)
+ * @param truth_mime mime type of @e data
+ * @param data input to validate (i.e. is it a valid phone number, etc.)
+ * @param data_length number of bytes in @a data
+ * @return #GNUNET_OK if @a data is valid,
+ * #GNUNET_NO if @a data is invalid and a reply was successfully queued on @a connection
+ * #GNUNET_SYSERR if @a data invalid but we failed to queue a reply on @a connection
+ */
+static enum GNUNET_GenericReturnValue
+email_validate (void *cls,
+ struct MHD_Connection *connection,
+ const char *mime_type,
+ const char *data,
+ size_t data_length)
+{
+ struct Email_Context *ctx = cls;
+ int regex_result;
+ char *phone_number;
+
+ phone_number = GNUNET_strndup (data,
+ data_length);
+ regex_result = regexec (&ctx->regex,
+ phone_number,
+ 0,
+ NULL,
+ 0);
+ GNUNET_free (phone_number);
+ if (0 != regex_result)
+ {
+ if (MHD_NO ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_EXPECTATION_FAILED,
+ TALER_EC_ANASTASIS_EMAIL_INVALID,
+ NULL))
+ return GNUNET_SYSERR;
+ return GNUNET_NO;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Begin issuing authentication challenge to user based on @a data.
+ * I.e. start to send SMS or e-mail or launch video identification.
+ *
+ * @param cls closure
+ * @param trigger function to call when we made progress
+ * @param trigger_cls closure for @a trigger
+ * @param truth_uuid Identifier of the challenge, to be (if possible) included in the
+ * interaction with the user
+ * @param code secret code that the user has to provide back to satisfy the challenge in
+ * the main anastasis protocol
+ * @param data input to validate (i.e. is it a valid phone number, etc.)
+ * @return state to track progress on the authorization operation, NULL on failure
+ */
+static struct ANASTASIS_AUTHORIZATION_State *
+email_start (void *cls,
+ GNUNET_SCHEDULER_TaskCallback trigger,
+ void *trigger_cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ uint64_t code,
+ const void *data,
+ size_t data_length)
+{
+ struct Email_Context *ctx = cls;
+ struct ANASTASIS_AUTHORIZATION_State *as;
+
+ as = GNUNET_new (struct ANASTASIS_AUTHORIZATION_State);
+ as->trigger = trigger;
+ as->trigger_cls = trigger_cls;
+ as->ctx = ctx;
+ as->truth_uuid = *truth_uuid;
+ as->code = code;
+ as->email = GNUNET_strndup (data,
+ data_length);
+ return as;
+}
+
+
+/**
+ * Function called when our Email helper has terminated.
+ *
+ * @param cls our `struct ANASTASIS_AUHTORIZATION_State`
+ * @param type type of the process
+ * @param exit_code status code of the process
+ */
+static void
+email_done_cb (void *cls,
+ enum GNUNET_OS_ProcessStatusType type,
+ long unsigned int exit_code)
+{
+ struct ANASTASIS_AUTHORIZATION_State *as = cls;
+
+ as->child = NULL;
+ as->cwh = NULL;
+ as->pst = type;
+ as->exit_code = exit_code;
+ MHD_resume_connection (as->connection);
+ as->trigger (as->trigger_cls);
+}
+
+
+/**
+ * Begin issuing authentication challenge to user based on @a data.
+ * I.e. start to send SMS or e-mail or launch video identification.
+ *
+ * @param as authorization state
+ * @param connection HTTP client request (for queuing response, such as redirection to video portal)
+ * @return state of the request
+ */
+static enum ANASTASIS_AUTHORIZATION_Result
+email_process (struct ANASTASIS_AUTHORIZATION_State *as,
+ struct MHD_Connection *connection)
+{
+ MHD_RESULT mres;
+ const char *mime;
+ const char *lang;
+
+ mime = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_ACCEPT);
+ if (NULL == mime)
+ mime = "text/plain";
+ lang = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
+ if (NULL == lang)
+ lang = "en";
+ if (NULL == as->msg)
+ {
+ /* First time, start child process and feed pipe */
+ struct GNUNET_DISK_PipeHandle *p;
+ struct GNUNET_DISK_FileHandle *pipe_stdin;
+
+ p = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_RW);
+ if (NULL == p)
+ {
+ mres = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_EMAIL_HELPER_EXEC_FAILED,
+ "pipe");
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_FAILED;
+ }
+ as->child = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ERR,
+ p,
+ NULL,
+ NULL,
+ as->ctx->auth_command,
+ as->ctx->auth_command,
+ as->email,
+ NULL);
+ if (NULL == as->child)
+ {
+ GNUNET_DISK_pipe_close (p);
+ mres = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_EMAIL_HELPER_EXEC_FAILED,
+ "exec");
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_FAILED;
+ }
+ pipe_stdin = GNUNET_DISK_pipe_detach_end (p,
+ GNUNET_DISK_PIPE_END_WRITE);
+ GNUNET_assert (NULL != pipe_stdin);
+ GNUNET_DISK_pipe_close (p);
+ {
+ char *tpk;
+
+ tpk = GNUNET_STRINGS_data_to_string_alloc (
+ &as->truth_uuid,
+ sizeof (as->truth_uuid));
+ GNUNET_asprintf (&as->msg,
+ get_message (as->ctx->messages,
+ connection,
+ "body"),
+ (unsigned long long) as->code,
+ tpk);
+ GNUNET_free (tpk);
+ }
+
+ {
+ const char *off = as->msg;
+ size_t left = strlen (off);
+
+ while (0 != left)
+ {
+ ssize_t ret;
+
+ if (0 == left)
+ break;
+ ret = GNUNET_DISK_file_write (pipe_stdin,
+ off,
+ left);
+ if (ret <= 0)
+ {
+ mres = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_EMAIL_HELPER_EXEC_FAILED,
+ "write");
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_FAILED;
+ }
+ as->msg_off += ret;
+ off += ret;
+ left -= ret;
+ }
+ GNUNET_DISK_file_close (pipe_stdin);
+ }
+ as->cwh = GNUNET_wait_child (as->child,
+ &email_done_cb,
+ as);
+ as->connection = connection;
+ MHD_suspend_connection (connection);
+ return ANASTASIS_AUTHORIZATION_RES_SUSPENDED;
+ }
+ if (NULL != as->cwh)
+ {
+ /* Spurious call, why are we here? */
+ GNUNET_break (0);
+ MHD_suspend_connection (connection);
+ return ANASTASIS_AUTHORIZATION_RES_SUSPENDED;
+ }
+ if ( (GNUNET_OS_PROCESS_EXITED != as->pst) ||
+ (0 != as->exit_code) )
+ {
+ char es[32];
+
+ GNUNET_snprintf (es,
+ sizeof (es),
+ "%u/%d",
+ (unsigned int) as->exit_code,
+ as->pst);
+ mres = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_EMAIL_HELPER_COMMAND_FAILED,
+ es);
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_FAILED;
+ }
+
+ /* Build HTTP response */
+ {
+ struct MHD_Response *resp;
+ const char *at;
+ size_t len;
+
+ at = strchr (as->email, '@');
+ if (NULL == at)
+ len = 0;
+ else
+ len = at - as->email;
+
+ if (TALER_MHD_xmime_matches (mime,
+ "application/json"))
+ {
+ json_t *body;
+ char *user;
+
+ user = GNUNET_strndup (as->email,
+ len);
+ body = json_pack ("{s:I, s:s, s:s}",
+ "code",
+ TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED,
+ "hint",
+ TALER_ErrorCode_get_hint (
+ TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED),
+ "detail",
+ user);
+ GNUNET_free (user);
+ GNUNET_break (NULL != body);
+ resp = TALER_MHD_make_json (body);
+ }
+ else
+ {
+ size_t reply_len;
+ char *reply;
+
+ reply_len = GNUNET_asprintf (&reply,
+ get_message (as->ctx->messages,
+ connection,
+ "instructions"),
+ (unsigned int) len,
+ as->email);
+ resp = MHD_create_response_from_buffer (reply_len,
+ reply,
+ MHD_RESPMEM_MUST_COPY);
+ GNUNET_free (reply);
+ TALER_MHD_add_global_headers (resp);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ "text/plain"));
+ }
+ mres = MHD_queue_response (connection,
+ MHD_HTTP_FORBIDDEN,
+ resp);
+ MHD_destroy_response (resp);
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_SUCCESS_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_SUCCESS;
+ }
+}
+
+
+/**
+ * Free internal state associated with @a as.
+ *
+ * @param as state to clean up
+ */
+static void
+email_cleanup (struct ANASTASIS_AUTHORIZATION_State *as)
+{
+ if (NULL != as->cwh)
+ {
+ GNUNET_wait_child_cancel (as->cwh);
+ as->cwh = NULL;
+ }
+ if (NULL != as->child)
+ {
+ (void) GNUNET_OS_process_kill (as->child,
+ SIGKILL);
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_OS_process_wait (as->child));
+ as->child = NULL;
+ }
+ GNUNET_free (as->msg);
+ GNUNET_free (as->email);
+ GNUNET_free (as);
+}
+
+
+/**
+ * Initialize email based authorization plugin
+ *
+ * @param cls a configuration instance
+ * @return NULL on error, otherwise a `struct ANASTASIS_AuthorizationPlugin`
+ */
+void *
+libanastasis_plugin_authorization_email_init (void *cls)
+{
+ struct ANASTASIS_AuthorizationPlugin *plugin;
+ struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+ struct Email_Context *ctx;
+
+ ctx = GNUNET_new (struct Email_Context);
+ {
+ char *fn;
+ json_error_t err;
+
+ GNUNET_asprintf (&fn,
+ "%sauthorization-email-messages.json",
+ GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR));
+ ctx->messages = json_load_file (fn,
+ JSON_REJECT_DUPLICATES,
+ &err);
+ if (NULL == ctx->messages)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to load messages from `%s': %s at %d:%d\n",
+ fn,
+ err.text,
+ err.line,
+ err.column);
+ GNUNET_free (fn);
+ GNUNET_free (ctx);
+ return NULL;
+ }
+ GNUNET_free (fn);
+ }
+ {
+ int regex_result;
+ const char *regexp = "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}";
+
+ regex_result = regcomp (&ctx->regex,
+ regexp,
+ REG_EXTENDED);
+ if (0 < regex_result)
+ {
+ GNUNET_break (0);
+ json_decref (ctx->messages);
+ GNUNET_free (ctx);
+ return NULL;
+ }
+ }
+
+ plugin = GNUNET_new (struct ANASTASIS_AuthorizationPlugin);
+ plugin->code_validity_period = GNUNET_TIME_UNIT_DAYS;
+ plugin->code_rotation_period = GNUNET_TIME_UNIT_HOURS;
+ plugin->code_retransmission_frequency = GNUNET_TIME_UNIT_MINUTES;
+ plugin->cls = ctx;
+ plugin->validate = &email_validate;
+ plugin->start = &email_start;
+ plugin->process = &email_process;
+ plugin->cleanup = &email_cleanup;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "authorization-email",
+ "COMMAND",
+ &ctx->auth_command))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "authorization-email",
+ "COMMAND");
+ regfree (&ctx->regex);
+ json_decref (ctx->messages);
+ GNUNET_free (ctx);
+ GNUNET_free (plugin);
+ return NULL;
+ }
+ return plugin;
+}
+
+
+/**
+ * Unload authorization plugin
+ *
+ * @param cls a `struct ANASTASIS_AuthorizationPlugin`
+ * @return NULL (always)
+ */
+void *
+libanastasis_plugin_authorization_email_done (void *cls)
+{
+ struct ANASTASIS_AuthorizationPlugin *plugin = cls;
+ struct Email_Context *ctx = plugin->cls;
+
+ GNUNET_free (ctx->auth_command);
+ regfree (&ctx->regex);
+ json_decref (ctx->messages);
+ GNUNET_free (ctx);
+ GNUNET_free (plugin);
+ return NULL;
+}
diff --git a/src/authorization/anastasis_authorization_plugin_file.c b/src/authorization/anastasis_authorization_plugin_file.c
new file mode 100644
index 0000000..210ade7
--- /dev/null
+++ b/src/authorization/anastasis_authorization_plugin_file.c
@@ -0,0 +1,302 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2019 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file include/anastasis_authorization_plugin_file.c
+ * @brief authorization plugin file based for testing
+ * @author Dominik Meister
+ */
+#include "platform.h"
+#include "anastasis_authorization_plugin.h"
+#include <taler/taler_mhd_lib.h>
+
+
+/**
+ * Saves the state of a authorization process
+ */
+struct ANASTASIS_AUTHORIZATION_State
+{
+ /**
+ * UUID of the challenge which is authorised
+ */
+ struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid;
+
+ /**
+ * Code which is sent to the user (here saved into a file)
+ */
+ uint64_t code;
+
+ /**
+ * holds the truth information
+ */
+ char *filename;
+
+ /**
+ * closure
+ */
+ void *cls;
+};
+
+
+/**
+ * Validate @a data is a well-formed input into the challenge method,
+ * i.e. @a data is a well-formed phone number for sending an SMS, or
+ * a well-formed e-mail address for sending an e-mail. Not expected to
+ * check that the phone number or e-mail account actually exists.
+ *
+ * To be possibly used before issuing a 402 payment required to the client.
+ *
+ * @param cls closure
+ * @param connection HTTP client request (for queuing response)
+ * @param truth_mime mime type of @e data
+ * @param data input to validate (i.e. is it a valid phone number, etc.)
+ * @param data_length number of bytes in @a data
+ * @return #GNUNET_OK if @a data is valid,
+ * #GNUNET_NO if @a data is invalid and a reply was successfully queued on @a connection
+ * #GNUNET_SYSERR if @a data invalid but we failed to queue a reply on @a connection
+ */
+static enum GNUNET_GenericReturnValue
+file_validate (void *cls,
+ struct MHD_Connection *connection,
+ const char *truth_mime,
+ const char *data,
+ size_t data_length)
+{
+ char *filename;
+ bool flag;
+
+ if (NULL == data)
+ return GNUNET_NO;
+ filename = GNUNET_STRINGS_data_to_string_alloc (data,
+ data_length);
+ flag = false;
+ for (size_t i = 0; i<strlen (filename); i++)
+ {
+ if ( (filename[i] == ' ') ||
+ (filename[i] == '/') )
+ {
+ flag = true;
+ break;
+ }
+ }
+ if (flag)
+ return GNUNET_NO;
+ GNUNET_free (filename);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Begin issuing authentication challenge to user based on @a data.
+ * I.e. start to send SMS or e-mail or launch video identification.
+ *
+ * @param cls closure
+ * @param trigger function to call when we made progress
+ * @param trigger_cls closure for @a trigger
+ * @param truth_uuid Identifier of the challenge, to be (if possible) included in the
+ * interaction with the user
+ * @param code secret code that the user has to provide back to satisfy the challenge in
+ * the main anastasis protocol
+ * @param data input to validate (i.e. is it a valid phone number, etc.)
+ * @return state to track progress on the authorization operation, NULL on failure
+ */
+static struct ANASTASIS_AUTHORIZATION_State *
+file_start (void *cls,
+ GNUNET_SCHEDULER_TaskCallback trigger,
+ void *trigger_cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ uint64_t code,
+ const void *data,
+ size_t data_length)
+{
+ struct ANASTASIS_AUTHORIZATION_State *as;
+
+ as = GNUNET_new (struct ANASTASIS_AUTHORIZATION_State);
+ as->cls = cls;
+ as->truth_uuid = *truth_uuid;
+ as->code = code;
+ as->filename = GNUNET_strndup (data,
+ data_length);
+ return as;
+}
+
+
+/**
+ * Begin issuing authentication challenge to user based on @a data.
+ * I.e. start to send SMS or e-mail or launch video identification.
+ *
+ * @param as authorization state
+ * @param connection HTTP client request (for queuing response, such as redirection to video portal)
+ * @return state of the request
+ */
+static enum ANASTASIS_AUTHORIZATION_Result
+file_process (struct ANASTASIS_AUTHORIZATION_State *as,
+ struct MHD_Connection *connection)
+{
+ const char *mime;
+ const char *lang;
+
+ mime = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_ACCEPT);
+ if (NULL == mime)
+ mime = "text/plain";
+ lang = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
+ if (NULL == lang)
+ lang = "en";
+ {
+ FILE *f = fopen (as->filename, "w");
+
+ if (NULL == f)
+ {
+ struct MHD_Response *resp;
+ MHD_RESULT mres;
+
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "open",
+ as->filename);
+ resp = TALER_MHD_make_error (TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "open");
+ mres = MHD_queue_response (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ resp);
+ MHD_destroy_response (resp);
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_FAILED;
+ }
+
+ /* print challenge code to file */
+ if (0 >= fprintf (f,
+ "%lu",
+ as->code))
+ {
+ struct MHD_Response *resp;
+ MHD_RESULT mres;
+
+ GNUNET_break (0 == fclose (f));
+ resp = TALER_MHD_make_error (TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "write");
+ mres = MHD_queue_response (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ resp);
+ MHD_destroy_response (resp);
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_FAILED;
+ }
+ GNUNET_break (0 == fclose (f));
+ }
+
+ /* Build HTTP response */
+ {
+ struct MHD_Response *resp;
+
+ if (TALER_MHD_xmime_matches (mime,
+ "application/json"))
+ {
+ json_t *body;
+
+ body = json_pack ("{s:s}",
+ "filename",
+ as->filename);
+ GNUNET_break (NULL != body);
+ resp = TALER_MHD_make_json (body);
+ }
+ else
+ {
+ size_t response_size;
+ char *response;
+
+ response_size = GNUNET_asprintf (&response,
+ _ ("Challenge written to file"));
+ resp = MHD_create_response_from_buffer (response_size,
+ response,
+ MHD_RESPMEM_MUST_COPY);
+ GNUNET_free (response);
+ TALER_MHD_add_global_headers (resp);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ "text/plain"));
+ }
+
+ {
+ MHD_RESULT mres;
+
+ mres = MHD_queue_response (connection,
+ MHD_HTTP_FORBIDDEN,
+ resp);
+ MHD_destroy_response (resp);
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_SUCCESS_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_SUCCESS;
+ }
+ }
+}
+
+
+/**
+ * Free internal state associated with @a as.
+ *
+ * @param as state to clean up
+ */
+static void
+file_cleanup (struct ANASTASIS_AUTHORIZATION_State *as)
+{
+ GNUNET_free (as->filename);
+ GNUNET_free (as);
+}
+
+
+/**
+ * Initialize File based authorization plugin
+ *
+ * @param cls a configuration instance
+ * @return NULL on error, otherwise a `struct ANASTASIS_AuthorizationPlugin`
+ */
+void *
+libanastasis_plugin_authorization_file_init (void *cls)
+{
+ struct ANASTASIS_AuthorizationPlugin *plugin;
+
+ plugin = GNUNET_new (struct ANASTASIS_AuthorizationPlugin);
+ plugin->code_validity_period = GNUNET_TIME_UNIT_MINUTES;
+ plugin->code_rotation_period = GNUNET_TIME_UNIT_MINUTES;
+ plugin->code_retransmission_frequency = GNUNET_TIME_UNIT_MINUTES;
+ plugin->validate = &file_validate;
+ plugin->start = &file_start;
+ plugin->process = &file_process;
+ plugin->cleanup = &file_cleanup;
+ return plugin;
+}
+
+
+/**
+ * Unload authorization plugin
+ *
+ * @param cls a `struct ANASTASIS_AuthorizationPlugin`
+ * @return NULL (always)
+ */
+void *
+libanastasis_plugin_authorization_file_done (void *cls)
+{
+ struct ANASTASIS_AuthorizationPlugin *plugin = cls;
+
+ GNUNET_free (plugin);
+ return NULL;
+}
diff --git a/src/authorization/anastasis_authorization_plugin_post.c b/src/authorization/anastasis_authorization_plugin_post.c
new file mode 100644
index 0000000..1f20ff3
--- /dev/null
+++ b/src/authorization/anastasis_authorization_plugin_post.c
@@ -0,0 +1,655 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2021 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file include/anastasis_authorization_plugin_post.c
+ * @brief authorization plugin post based
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "anastasis_authorization_plugin.h"
+#include <taler/taler_mhd_lib.h>
+#include <taler/taler_json_lib.h>
+#include <jansson.h>
+#include "anastasis_util_lib.h"
+
+
+/**
+ * Saves the State of a authorization plugin.
+ */
+struct PostContext
+{
+
+ /**
+ * Command which is executed to run the plugin (some bash script or a
+ * command line argument)
+ */
+ char *auth_command;
+
+ /**
+ * Messages of the plugin, read from a resource file.
+ */
+ json_t *messages;
+};
+
+
+/**
+ * Saves the state of a authorization process
+ */
+struct ANASTASIS_AUTHORIZATION_State
+{
+ /**
+ * Public key of the challenge which is authorised
+ */
+ struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid;
+
+ /**
+ * Code which is sent to the user.
+ */
+ uint64_t code;
+
+ /**
+ * Our plugin context.
+ */
+ struct PostContext *ctx;
+
+ /**
+ * Function to call when we made progress.
+ */
+ GNUNET_SCHEDULER_TaskCallback trigger;
+
+ /**
+ * Closure for @e trigger.
+ */
+ void *trigger_cls;
+
+ /**
+ * holds the truth information
+ */
+ json_t *post;
+
+ /**
+ * Handle to the helper process.
+ */
+ struct GNUNET_OS_Process *child;
+
+ /**
+ * Handle to wait for @e child
+ */
+ struct GNUNET_ChildWaitHandle *cwh;
+
+ /**
+ * Our client connection, set if suspended.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * Message to send.
+ */
+ char *msg;
+
+ /**
+ * Offset of transmission in msg.
+ */
+ size_t msg_off;
+
+ /**
+ * Exit code from helper.
+ */
+ long unsigned int exit_code;
+
+ /**
+ * How did the helper die?
+ */
+ enum GNUNET_OS_ProcessStatusType pst;
+
+
+};
+
+
+/**
+ * Obtain internationalized message @a msg_id from @a ctx using
+ * language preferences of @a conn.
+ *
+ * @param messages JSON object to lookup message from
+ * @param conn connection to lookup message for
+ * @param msg_id unique message ID
+ * @return NULL if message was not found
+ */
+static const char *
+get_message (const json_t *messages,
+ struct MHD_Connection *conn,
+ const char *msg_id)
+{
+ const char *accept_lang;
+
+ accept_lang = MHD_lookup_connection_value (conn,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
+ if (NULL == accept_lang)
+ accept_lang = "en_US";
+ {
+ const char *ret;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_i18n_string (msg_id,
+ accept_lang,
+ &ret),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (messages,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ return ret;
+ }
+}
+
+
+/**
+ * Validate @a data is a well-formed input into the challenge method,
+ * i.e. @a data is a well-formed phone number for sending an SMS, or
+ * a well-formed e-mail address for sending an e-mail. Not expected to
+ * check that the phone number or e-mail account actually exists.
+ *
+ * To be possibly used before issuing a 402 payment required to the client.
+ *
+ * @param cls closure
+ * @param connection HTTP client request (for queuing response)
+ * @param truth_mime mime type of @e data
+ * @param data input to validate (i.e. is it a valid phone number, etc.)
+ * @param data_length number of bytes in @a data
+ * @return #GNUNET_OK if @a data is valid,
+ * #GNUNET_NO if @a data is invalid and a reply was successfully queued on @a connection
+ * #GNUNET_SYSERR if @a data invalid but we failed to queue a reply on @a connection
+ */
+static enum GNUNET_GenericReturnValue
+post_validate (void *cls,
+ struct MHD_Connection *connection,
+ const char *mime_type,
+ const char *data,
+ size_t data_length)
+{
+ struct PostContext *ctx = cls;
+ json_t *j;
+ json_error_t error;
+ const char *name;
+ const char *street;
+ const char *city;
+ const char *zip;
+ const char *country;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("full_name",
+ &name),
+ GNUNET_JSON_spec_string ("street",
+ &street),
+ GNUNET_JSON_spec_string ("city",
+ &city),
+ GNUNET_JSON_spec_string ("postcode",
+ &zip),
+ GNUNET_JSON_spec_string ("country",
+ &country),
+ GNUNET_JSON_spec_end ()
+ };
+
+ (void) ctx;
+ j = json_loadb (data,
+ data_length,
+ JSON_REJECT_DUPLICATES,
+ &error);
+ if (NULL == j)
+ {
+ if (MHD_NO ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_EXPECTATION_FAILED,
+ TALER_EC_ANASTASIS_POST_INVALID,
+ "JSON malformed"))
+ return GNUNET_SYSERR;
+ return GNUNET_NO;
+ }
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ json_decref (j);
+ if (MHD_NO ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_EXPECTATION_FAILED,
+ TALER_EC_ANASTASIS_POST_INVALID,
+ "JSON lacked required address information"))
+ return GNUNET_SYSERR;
+ return GNUNET_NO;
+ }
+ json_decref (j);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Begin issuing authentication challenge to user based on @a data.
+ * I.e. start to send mail.
+ *
+ * @param cls closure
+ * @param trigger function to call when we made progress
+ * @param trigger_cls closure for @a trigger
+ * @param truth_uuid Identifier of the challenge, to be (if possible) included in the
+ * interaction with the user
+ * @param code secret code that the user has to provide back to satisfy the challenge in
+ * the main anastasis protocol
+ * @param data input to validate (i.e. is it a valid phone number, etc.)
+ * @return state to track progress on the authorization operation, NULL on failure
+ */
+static struct ANASTASIS_AUTHORIZATION_State *
+post_start (void *cls,
+ GNUNET_SCHEDULER_TaskCallback trigger,
+ void *trigger_cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ uint64_t code,
+ const void *data,
+ size_t data_length)
+{
+ struct PostContext *ctx = cls;
+ struct ANASTASIS_AUTHORIZATION_State *as;
+ json_error_t error;
+
+ as = GNUNET_new (struct ANASTASIS_AUTHORIZATION_State);
+ as->trigger = trigger;
+ as->trigger_cls = trigger_cls;
+ as->ctx = ctx;
+ as->truth_uuid = *truth_uuid;
+ as->code = code;
+ as->post = json_loadb (data,
+ data_length,
+ JSON_REJECT_DUPLICATES,
+ &error);
+ if (NULL == as->post)
+ {
+ GNUNET_break (0);
+ GNUNET_free (as);
+ return NULL;
+ }
+ return as;
+}
+
+
+/**
+ * Function called when our Post helper has terminated.
+ *
+ * @param cls our `struct ANASTASIS_AUHTORIZATION_State`
+ * @param type type of the process
+ * @param exit_code status code of the process
+ */
+static void
+post_done_cb (void *cls,
+ enum GNUNET_OS_ProcessStatusType type,
+ long unsigned int exit_code)
+{
+ struct ANASTASIS_AUTHORIZATION_State *as = cls;
+
+ as->child = NULL;
+ as->cwh = NULL;
+ as->pst = type;
+ as->exit_code = exit_code;
+ MHD_resume_connection (as->connection);
+ as->trigger (as->trigger_cls);
+}
+
+
+/**
+ * Begin issuing authentication challenge to user based on @a data.
+ * I.e. start to send SMS or e-mail or launch video identification.
+ *
+ * @param as authorization state
+ * @param connection HTTP client request (for queuing response, such as redirection to video portal)
+ * @return state of the request
+ */
+static enum ANASTASIS_AUTHORIZATION_Result
+post_process (struct ANASTASIS_AUTHORIZATION_State *as,
+ struct MHD_Connection *connection)
+{
+ const char *mime;
+ const char *lang;
+ MHD_RESULT mres;
+ const char *name;
+ const char *street;
+ const char *city;
+ const char *zip;
+ const char *country;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("full_name",
+ &name),
+ GNUNET_JSON_spec_string ("street",
+ &street),
+ GNUNET_JSON_spec_string ("city",
+ &city),
+ GNUNET_JSON_spec_string ("postcode",
+ &zip),
+ GNUNET_JSON_spec_string ("country",
+ &country),
+ GNUNET_JSON_spec_end ()
+ };
+
+ mime = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_ACCEPT);
+ if (NULL == mime)
+ mime = "text/plain";
+ lang = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
+ if (NULL == lang)
+ lang = "en";
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (as->post,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ mres = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_POST_INVALID,
+ "address information incomplete");
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_FAILED;
+ }
+ if (NULL == as->msg)
+ {
+ /* First time, start child process and feed pipe */
+ struct GNUNET_DISK_PipeHandle *p;
+ struct GNUNET_DISK_FileHandle *pipe_stdin;
+
+ p = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_RW);
+ if (NULL == p)
+ {
+ mres = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_POST_HELPER_EXEC_FAILED,
+ "pipe");
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_FAILED;
+ }
+ as->child = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ERR,
+ p,
+ NULL,
+ NULL,
+ as->ctx->auth_command,
+ as->ctx->auth_command,
+ name,
+ street,
+ city,
+ zip,
+ country,
+ NULL);
+ if (NULL == as->child)
+ {
+ GNUNET_DISK_pipe_close (p);
+ mres = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_POST_HELPER_EXEC_FAILED,
+ "exec");
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_FAILED;
+ }
+ pipe_stdin = GNUNET_DISK_pipe_detach_end (p,
+ GNUNET_DISK_PIPE_END_WRITE);
+ GNUNET_assert (NULL != pipe_stdin);
+ GNUNET_DISK_pipe_close (p);
+ {
+ char *tpk;
+
+ tpk = GNUNET_STRINGS_data_to_string_alloc (
+ &as->truth_uuid,
+ sizeof (as->truth_uuid));
+ GNUNET_asprintf (&as->msg,
+ get_message (as->ctx->messages,
+ connection,
+ "body"),
+ (unsigned long long) as->code,
+ tpk);
+ GNUNET_free (tpk);
+ }
+
+ {
+ const char *off = as->msg;
+ size_t left = strlen (off);
+
+ while (0 != left)
+ {
+ ssize_t ret;
+
+ if (0 == left)
+ break;
+ ret = GNUNET_DISK_file_write (pipe_stdin,
+ off,
+ left);
+ if (ret <= 0)
+ {
+ mres = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_POST_HELPER_EXEC_FAILED,
+ "write");
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_FAILED;
+ }
+ as->msg_off += ret;
+ off += ret;
+ left -= ret;
+ }
+ GNUNET_DISK_file_close (pipe_stdin);
+ }
+ as->cwh = GNUNET_wait_child (as->child,
+ &post_done_cb,
+ as);
+ as->connection = connection;
+ MHD_suspend_connection (connection);
+ return ANASTASIS_AUTHORIZATION_RES_SUSPENDED;
+ }
+ if (NULL != as->cwh)
+ {
+ /* Spurious call, why are we here? */
+ GNUNET_break (0);
+ MHD_suspend_connection (connection);
+ return ANASTASIS_AUTHORIZATION_RES_SUSPENDED;
+ }
+ if ( (GNUNET_OS_PROCESS_EXITED != as->pst) ||
+ (0 != as->exit_code) )
+ {
+ char es[32];
+
+ GNUNET_snprintf (es,
+ sizeof (es),
+ "%u/%d",
+ (unsigned int) as->exit_code,
+ as->pst);
+ mres = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_POST_HELPER_COMMAND_FAILED,
+ es);
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_FAILED;
+ }
+
+ /* Build HTTP response */
+ {
+ struct MHD_Response *resp;
+
+ if (TALER_MHD_xmime_matches (mime,
+ "application/json"))
+ {
+ json_t *body;
+
+ body = json_pack ("{s:I, s:s, s:s}",
+ "code",
+ TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED,
+ "hint",
+ TALER_ErrorCode_get_hint (
+ TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED),
+ "detail",
+ zip);
+ GNUNET_break (NULL != body);
+ resp = TALER_MHD_make_json (body);
+ }
+ else
+ {
+ size_t reply_len;
+ char *reply;
+
+ reply_len = GNUNET_asprintf (&reply,
+ get_message (as->ctx->messages,
+ connection,
+ "instructions"),
+ zip);
+ resp = MHD_create_response_from_buffer (reply_len,
+ reply,
+ MHD_RESPMEM_MUST_COPY);
+ GNUNET_free (reply);
+ TALER_MHD_add_global_headers (resp);
+ }
+ mres = MHD_queue_response (connection,
+ MHD_HTTP_FORBIDDEN,
+ resp);
+ MHD_destroy_response (resp);
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_SUCCESS_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_SUCCESS;
+ }
+}
+
+
+/**
+ * Free internal state associated with @a as.
+ *
+ * @param as state to clean up
+ */
+static void
+post_cleanup (struct ANASTASIS_AUTHORIZATION_State *as)
+{
+ if (NULL != as->cwh)
+ {
+ GNUNET_wait_child_cancel (as->cwh);
+ as->cwh = NULL;
+ }
+ if (NULL != as->child)
+ {
+ (void) GNUNET_OS_process_kill (as->child,
+ SIGKILL);
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_OS_process_wait (as->child));
+ as->child = NULL;
+ }
+ GNUNET_free (as->msg);
+ json_decref (as->post);
+ GNUNET_free (as);
+}
+
+
+/**
+ * Initialize post based authorization plugin
+ *
+ * @param cls a configuration instance
+ * @return NULL on error, otherwise a `struct ANASTASIS_AuthorizationPlugin`
+ */
+void *
+libanastasis_plugin_authorization_post_init (void *cls)
+{
+ struct ANASTASIS_AuthorizationPlugin *plugin;
+ struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+ struct PostContext *ctx;
+
+ ctx = GNUNET_new (struct PostContext);
+ {
+ char *fn;
+ json_error_t err;
+
+ GNUNET_asprintf (&fn,
+ "%sauthorization-post-messages.json",
+ GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR));
+ ctx->messages = json_load_file (fn,
+ JSON_REJECT_DUPLICATES,
+ &err);
+ if (NULL == ctx->messages)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to load messages from `%s': %s at %d:%d\n",
+ fn,
+ err.text,
+ err.line,
+ err.column);
+ GNUNET_free (fn);
+ GNUNET_free (ctx);
+ return NULL;
+ }
+ GNUNET_free (fn);
+ }
+ plugin = GNUNET_new (struct ANASTASIS_AuthorizationPlugin);
+ plugin->code_validity_period = GNUNET_TIME_UNIT_MONTHS;
+ plugin->code_rotation_period = GNUNET_TIME_UNIT_WEEKS;
+ plugin->code_retransmission_frequency
+ = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_DAYS,
+ 2);
+ plugin->cls = ctx;
+ plugin->validate = &post_validate;
+ plugin->start = &post_start;
+ plugin->process = &post_process;
+ plugin->cleanup = &post_cleanup;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "authorization-post",
+ "COMMAND",
+ &ctx->auth_command))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "authorization-post",
+ "COMMAND");
+ json_decref (ctx->messages);
+ GNUNET_free (ctx);
+ GNUNET_free (plugin);
+ return NULL;
+ }
+ return plugin;
+}
+
+
+/**
+ * Unload authorization plugin
+ *
+ * @param cls a `struct ANASTASIS_AuthorizationPlugin`
+ * @return NULL (always)
+ */
+void *
+libanastasis_plugin_authorization_post_done (void *cls)
+{
+ struct ANASTASIS_AuthorizationPlugin *plugin = cls;
+ struct PostContext *ctx = plugin->cls;
+
+ GNUNET_free (ctx->auth_command);
+ json_decref (ctx->messages);
+ GNUNET_free (ctx);
+ GNUNET_free (plugin);
+ return NULL;
+}
diff --git a/src/authorization/anastasis_authorization_plugin_sms.c b/src/authorization/anastasis_authorization_plugin_sms.c
new file mode 100644
index 0000000..01b5f73
--- /dev/null
+++ b/src/authorization/anastasis_authorization_plugin_sms.c
@@ -0,0 +1,607 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2019, 2021 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file include/anastasis_authorization_plugin_email.c
+ * @brief authorization plugin email based
+ * @author Dominik Meister
+ */
+#include "platform.h"
+#include "anastasis_authorization_plugin.h"
+#include <taler/taler_mhd_lib.h>
+#include <taler/taler_json_lib.h>
+#include <regex.h>
+#include "anastasis_util_lib.h"
+
+
+/**
+ * Saves the State of a authorization plugin.
+ */
+struct SMS_Context
+{
+
+ /**
+ * Command which is executed to run the plugin (some bash script or a
+ * command line argument)
+ */
+ char *auth_command;
+
+ /**
+ * Regex for phone number validation.
+ */
+ regex_t regex;
+
+ /**
+ * Messages of the plugin, read from a resource file.
+ */
+ json_t *messages;
+};
+
+
+/**
+ * Saves the State of a authorization process
+ */
+struct ANASTASIS_AUTHORIZATION_State
+{
+ /**
+ * Public key of the challenge which is authorised
+ */
+ struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid;
+
+ /**
+ * Code which is sent to the user (here sent via SMS)
+ */
+ uint64_t code;
+
+ /**
+ * Our plugin context.
+ */
+ struct SMS_Context *ctx;
+
+ /**
+ * Function to call when we made progress.
+ */
+ GNUNET_SCHEDULER_TaskCallback trigger;
+
+ /**
+ * Closure for @e trigger.
+ */
+ void *trigger_cls;
+
+ /**
+ * holds the truth information
+ */
+ char *phone_number;
+
+ /**
+ * Handle to the helper process.
+ */
+ struct GNUNET_OS_Process *child;
+
+ /**
+ * Handle to wait for @e child
+ */
+ struct GNUNET_ChildWaitHandle *cwh;
+
+ /**
+ * Our client connection, set if suspended.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * Message to send.
+ */
+ char *msg;
+
+ /**
+ * Offset of transmission in msg.
+ */
+ size_t msg_off;
+
+ /**
+ * Exit code from helper.
+ */
+ long unsigned int exit_code;
+
+ /**
+ * How did the helper die?
+ */
+ enum GNUNET_OS_ProcessStatusType pst;
+
+};
+
+
+/**
+ * Obtain internationalized message @a msg_id from @a ctx using
+ * language preferences of @a conn.
+ *
+ * @param messages JSON object to lookup message from
+ * @param conn connection to lookup message for
+ * @param msg_id unique message ID
+ * @return NULL if message was not found
+ */
+static const char *
+get_message (const json_t *messages,
+ struct MHD_Connection *conn,
+ const char *msg_id)
+{
+ const char *accept_lang;
+
+ accept_lang = MHD_lookup_connection_value (conn,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
+ if (NULL == accept_lang)
+ accept_lang = "en_US";
+ {
+ const char *ret;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_i18n_string (msg_id,
+ accept_lang,
+ &ret),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (messages,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ return ret;
+ }
+}
+
+
+/**
+ * Validate @a data is a well-formed input into the challenge method,
+ * i.e. @a data is a well-formed phone number for sending an SMS, or
+ * a well-formed e-mail address for sending an e-mail. Not expected to
+ * check that the phone number or e-mail account actually exists.
+ *
+ * To be possibly used before issuing a 402 payment required to the client.
+ *
+ * @param cls closure with a `struct SMS_Context`
+ * @param connection HTTP client request (for queuing response)
+ * @param truth_mime mime type of @e data
+ * @param data input to validate (i.e. is it a valid phone number, etc.)
+ * @param data_length number of bytes in @a data
+ * @return #GNUNET_OK if @a data is valid,
+ * #GNUNET_NO if @a data is invalid and a reply was successfully queued on @a connection
+ * #GNUNET_SYSERR if @a data invalid but we failed to queue a reply on @a connection
+ */
+static enum GNUNET_GenericReturnValue
+sms_validate (void *cls,
+ struct MHD_Connection *connection,
+ const char *truth_mime,
+ const char *data,
+ size_t data_length)
+{
+ struct SMS_Context *ctx = cls;
+ int regex_result;
+ char *phone_number;
+
+ phone_number = GNUNET_strndup (data,
+ data_length);
+ regex_result = regexec (&ctx->regex,
+ phone_number,
+ 0,
+ NULL,
+ 0);
+ GNUNET_free (phone_number);
+ if (0 != regex_result)
+ {
+ if (MHD_NO ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_EXPECTATION_FAILED,
+ TALER_EC_ANASTASIS_SMS_PHONE_INVALID,
+ NULL))
+ return GNUNET_SYSERR;
+ return GNUNET_NO;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Begin issuing authentication challenge to user based on @a data.
+ * Sends SMS.
+ *
+ * @param cls closure with a `struct SMS_Context`
+ * @param trigger function to call when we made progress
+ * @param trigger_cls closure for @a trigger
+ * @param truth_uuid Identifier of the challenge, to be (if possible) included in the
+ * interaction with the user
+ * @param code secret code that the user has to provide back to satisfy the challenge in
+ * the main anastasis protocol
+ * @param data input to validate (i.e. is it a valid phone number, etc.)
+ * @return state to track progress on the authorization operation, NULL on failure
+ */
+static struct ANASTASIS_AUTHORIZATION_State *
+sms_start (void *cls,
+ GNUNET_SCHEDULER_TaskCallback trigger,
+ void *trigger_cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ uint64_t code,
+ const void *data,
+ size_t data_length)
+{
+ struct SMS_Context *ctx = cls;
+ struct ANASTASIS_AUTHORIZATION_State *as;
+
+ as = GNUNET_new (struct ANASTASIS_AUTHORIZATION_State);
+ as->trigger = trigger;
+ as->trigger_cls = trigger_cls;
+ as->ctx = ctx;
+ as->truth_uuid = *truth_uuid;
+ as->code = code;
+ as->phone_number = GNUNET_strndup (data,
+ data_length);
+ return as;
+}
+
+
+/**
+ * Function called when our SMS helper has terminated.
+ *
+ * @param cls our `struct ANASTASIS_AUHTORIZATION_State`
+ * @param type type of the process
+ * @param exit_code status code of the process
+ */
+static void
+sms_done_cb (void *cls,
+ enum GNUNET_OS_ProcessStatusType type,
+ long unsigned int exit_code)
+{
+ struct ANASTASIS_AUTHORIZATION_State *as = cls;
+
+ as->child = NULL;
+ as->cwh = NULL;
+ as->pst = type;
+ as->exit_code = exit_code;
+ MHD_resume_connection (as->connection);
+ as->trigger (as->trigger_cls);
+}
+
+
+/**
+ * Begin issuing authentication challenge to user based on @a data.
+ * I.e. start to send SMS or e-mail or launch video identification.
+ *
+ * @param as authorization state
+ * @param connection HTTP client request (for queuing response, such as redirection to video portal)
+ * @return state of the request
+ */
+static enum ANASTASIS_AUTHORIZATION_Result
+sms_process (struct ANASTASIS_AUTHORIZATION_State *as,
+ struct MHD_Connection *connection)
+{
+ MHD_RESULT mres;
+ const char *mime;
+ const char *lang;
+
+ mime = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_ACCEPT);
+ if (NULL == mime)
+ mime = "text/plain";
+ lang = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
+ if (NULL == lang)
+ lang = "en";
+ if (NULL == as->msg)
+ {
+ /* First time, start child process and feed pipe */
+ struct GNUNET_DISK_PipeHandle *p;
+ struct GNUNET_DISK_FileHandle *pipe_stdin;
+
+ p = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_RW);
+ if (NULL == p)
+ {
+ mres = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_SMS_HELPER_EXEC_FAILED,
+ "pipe");
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_FAILED;
+ }
+ as->child = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ERR,
+ p,
+ NULL,
+ NULL,
+ as->ctx->auth_command,
+ as->ctx->auth_command,
+ as->phone_number,
+ NULL);
+ if (NULL == as->child)
+ {
+ GNUNET_DISK_pipe_close (p);
+ mres = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_SMS_HELPER_EXEC_FAILED,
+ "exec");
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_FAILED;
+ }
+ pipe_stdin = GNUNET_DISK_pipe_detach_end (p,
+ GNUNET_DISK_PIPE_END_WRITE);
+ GNUNET_assert (NULL != pipe_stdin);
+ GNUNET_DISK_pipe_close (p);
+ {
+ char *tpk;
+
+ tpk = GNUNET_STRINGS_data_to_string_alloc (
+ &as->truth_uuid,
+ sizeof (as->truth_uuid));
+ GNUNET_asprintf (&as->msg,
+ "A-%llu\nAnastasis\n%s",
+ (unsigned long long) as->code,
+ tpk);
+ GNUNET_free (tpk);
+ }
+
+ {
+ const char *off = as->msg;
+ size_t left = strlen (off);
+
+ while (0 != left)
+ {
+ ssize_t ret;
+
+ if (0 == left)
+ break;
+ ret = GNUNET_DISK_file_write (pipe_stdin,
+ off,
+ left);
+ if (ret <= 0)
+ {
+ mres = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_SMS_HELPER_EXEC_FAILED,
+ "write");
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_FAILED;
+ }
+ as->msg_off += ret;
+ off += ret;
+ left -= ret;
+ }
+ GNUNET_DISK_file_close (pipe_stdin);
+ }
+ as->cwh = GNUNET_wait_child (as->child,
+ &sms_done_cb,
+ as);
+ as->connection = connection;
+ MHD_suspend_connection (connection);
+ return ANASTASIS_AUTHORIZATION_RES_SUSPENDED;
+ }
+ if (NULL != as->cwh)
+ {
+ /* Spurious call, why are we here? */
+ GNUNET_break (0);
+ MHD_suspend_connection (connection);
+ return ANASTASIS_AUTHORIZATION_RES_SUSPENDED;
+ }
+ if ( (GNUNET_OS_PROCESS_EXITED != as->pst) ||
+ (0 != as->exit_code) )
+ {
+ char es[32];
+
+ GNUNET_snprintf (es,
+ sizeof (es),
+ "%u/%d",
+ (unsigned int) as->exit_code,
+ as->pst);
+ mres = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_SMS_HELPER_COMMAND_FAILED,
+ es);
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_FAILED;
+ }
+
+ /* Build HTTP response */
+ {
+ struct MHD_Response *resp;
+ const char *end;
+ size_t slen;
+
+ slen = strlen (as->phone_number);
+ if (slen > 4)
+ end = &as->phone_number[slen - 4];
+ else
+ end = &as->phone_number[slen / 2];
+
+ if (TALER_MHD_xmime_matches (mime,
+ "application/json"))
+ {
+ json_t *body;
+
+ body = json_pack ("{s:I, s:s, s:s}",
+ "code",
+ TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED,
+ "hint",
+ TALER_ErrorCode_get_hint (
+ TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED),
+ "detail",
+ end);
+ GNUNET_break (NULL != body);
+ resp = TALER_MHD_make_json (body);
+ }
+ else
+ {
+ size_t reply_len;
+ char *reply;
+
+ reply_len = GNUNET_asprintf (&reply,
+ get_message (as->ctx->messages,
+ connection,
+ "instructions"),
+ end);
+ resp = MHD_create_response_from_buffer (reply_len,
+ reply,
+ MHD_RESPMEM_MUST_COPY);
+ GNUNET_free (reply);
+ TALER_MHD_add_global_headers (resp);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ "text/plain"));
+ }
+ mres = MHD_queue_response (connection,
+ MHD_HTTP_FORBIDDEN,
+ resp);
+ MHD_destroy_response (resp);
+ if (MHD_YES != mres)
+ return ANASTASIS_AUTHORIZATION_RES_SUCCESS_REPLY_FAILED;
+ return ANASTASIS_AUTHORIZATION_RES_SUCCESS;
+ }
+}
+
+
+/**
+ * Free internal state associated with @a as.
+ *
+ * @param as state to clean up
+ */
+static void
+sms_cleanup (struct ANASTASIS_AUTHORIZATION_State *as)
+{
+ if (NULL != as->cwh)
+ {
+ GNUNET_wait_child_cancel (as->cwh);
+ as->cwh = NULL;
+ }
+ if (NULL != as->child)
+ {
+ (void) GNUNET_OS_process_kill (as->child,
+ SIGKILL);
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_OS_process_wait (as->child));
+ as->child = NULL;
+ }
+ GNUNET_free (as->msg);
+ GNUNET_free (as->phone_number);
+ GNUNET_free (as);
+}
+
+
+/**
+ * Initialize email based authorization plugin
+ *
+ * @param cls a configuration instance
+ * @return NULL on error, otherwise a `struct ANASTASIS_AuthorizationPlugin`
+ */
+void *
+libanastasis_plugin_authorization_sms_init (void *cls)
+{
+ struct ANASTASIS_AuthorizationPlugin *plugin;
+ struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+ struct SMS_Context *ctx;
+
+ ctx = GNUNET_new (struct SMS_Context);
+ {
+ char *fn;
+ json_error_t err;
+
+ GNUNET_asprintf (&fn,
+ "%sauthorization-sms-messages.json",
+ GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR));
+ ctx->messages = json_load_file (fn,
+ JSON_REJECT_DUPLICATES,
+ &err);
+ if (NULL == ctx->messages)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to load messages from `%s': %s at %d:%d\n",
+ fn,
+ err.text,
+ err.line,
+ err.column);
+ GNUNET_free (fn);
+ GNUNET_free (ctx);
+ return NULL;
+ }
+ GNUNET_free (fn);
+ }
+ {
+ int regex_result;
+ const char *regexp = "^\\+?[0-9]+$";
+
+ regex_result = regcomp (&ctx->regex,
+ regexp,
+ REG_EXTENDED);
+ if (0 != regex_result)
+ {
+ GNUNET_break (0);
+ json_decref (ctx->messages);
+ GNUNET_free (ctx);
+ return NULL;
+ }
+ }
+ plugin = GNUNET_new (struct ANASTASIS_AuthorizationPlugin);
+ plugin->code_validity_period = GNUNET_TIME_UNIT_DAYS;
+ plugin->code_rotation_period = GNUNET_TIME_UNIT_HOURS;
+ plugin->code_retransmission_frequency = GNUNET_TIME_UNIT_MINUTES;
+ plugin->cls = ctx;
+ plugin->validate = &sms_validate;
+ plugin->start = &sms_start;
+ plugin->process = &sms_process;
+ plugin->cleanup = &sms_cleanup;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "authorization-sms",
+ "COMMAND",
+ &ctx->auth_command))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "authorization-sms",
+ "COMMAND");
+ regfree (&ctx->regex);
+ json_decref (ctx->messages);
+ GNUNET_free (ctx);
+ GNUNET_free (plugin);
+ return NULL;
+ }
+ return plugin;
+}
+
+
+/**
+ * Unload authorization plugin
+ *
+ * @param cls a `struct ANASTASIS_AuthorizationPlugin`
+ * @return NULL (always)
+ */
+void *
+libanastasis_plugin_authorization_sms_done (void *cls)
+{
+ struct ANASTASIS_AuthorizationPlugin *plugin = cls;
+ struct SMS_Context *ctx = plugin->cls;
+
+ GNUNET_free (ctx->auth_command);
+ regfree (&ctx->regex);
+ json_decref (ctx->messages);
+ GNUNET_free (ctx);
+ GNUNET_free (plugin);
+ return NULL;
+}
diff --git a/src/authorization/authorization-email-messages.json b/src/authorization/authorization-email-messages.json
new file mode 100644
index 0000000..56f648c
--- /dev/null
+++ b/src/authorization/authorization-email-messages.json
@@ -0,0 +1,10 @@
+{
+ "instructions" : "Recovery TAN was sent to email %.*s@DOMAIN",
+ "instructions_i18n" : {
+ "de_DE" : "Ein Authorisierungscode wurde an %.*s@DOMAIN geschickt"
+ },
+ "body" : "Subject: Anastasis recovery code: A-%llu\n\nThis is for challenge %s.\n",
+ "body_i18n" : {
+ "de_DE" : "Subject: Anastasis Autorisierungscode: A-%llu\n\nDies ist der Code für den Vorgang %s.\n"
+ }
+}
diff --git a/src/authorization/authorization-post-messages.json b/src/authorization/authorization-post-messages.json
new file mode 100644
index 0000000..d2ac83a
--- /dev/null
+++ b/src/authorization/authorization-post-messages.json
@@ -0,0 +1,7 @@
+{
+ "instructions" : "Recovery message send to an address with ZIP code %s",
+ "instructions_i18n" : {
+ "de_DE" : "Ein Authorisierungscode wurde an eine Addresse mit der Postleitzahl %s geschickt"
+ },
+ "body" : "Dear Customer\n\nThe Anastasis recovery code you need to\nrecover your data is A-%llu.\nThis is for challenge %s.\n\nBest regards\n\nYour Anastasis provider"
+}
diff --git a/src/authorization/authorization-sms-messages.json b/src/authorization/authorization-sms-messages.json
new file mode 100644
index 0000000..cb45ed8
--- /dev/null
+++ b/src/authorization/authorization-sms-messages.json
@@ -0,0 +1,6 @@
+{
+ "instructions" : "Recovery TAN send to phone number ending with %s",
+ "instructions_i18n" : {
+ "de_DE" : "Ein Authorisierungscode wurde an die Telefonnummer mit der Endung %s geschickt"
+ }
+}
diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am
new file mode 100644
index 0000000..1046810
--- /dev/null
+++ b/src/backend/Makefile.am
@@ -0,0 +1,45 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include
+
+pkgcfgdir = $(prefix)/share/anastasis/config.d/
+
+if USE_COVERAGE
+ AM_CFLAGS = --coverage -O0
+ XLIB = -lgcov
+endif
+
+pkgcfg_DATA = \
+ anastasis.conf
+
+bin_PROGRAMS = \
+ anastasis-httpd
+
+anastasis_httpd_SOURCES = \
+ anastasis-httpd.c anastasis-httpd.h \
+ anastasis-httpd_mhd.c anastasis-httpd_mhd.h \
+ anastasis-httpd_policy.c anastasis-httpd_policy.h \
+ anastasis-httpd_policy_upload.c \
+ anastasis-httpd_truth.c anastasis-httpd_truth.h \
+ anastasis-httpd_terms.c anastasis-httpd_terms.h \
+ anastasis-httpd_config.c anastasis-httpd_config.h \
+ anastasis-httpd_truth_upload.c
+
+anastasis_httpd_LDADD = \
+ $(top_builddir)/src/util/libanastasisutil.la \
+ $(top_builddir)/src/stasis/libanastasisdb.la \
+ $(top_builddir)/src/authorization/libanastasisauthorization.la \
+ -ljansson \
+ -ltalermerchant \
+ -ltalermhd \
+ -ltalerjson \
+ -ltalerutil \
+ -lgnunetcurl \
+ -lgnunetrest \
+ -lgnunetjson \
+ -lgnunetutil \
+ -lmicrohttpd \
+ -luuid \
+ $(XLIB)
+
+EXTRA_DIST = \
+ $(pkgcfg_DATA)
diff --git a/src/backend/anastasis-httpd.c b/src/backend/anastasis-httpd.c
new file mode 100644
index 0000000..56bd7c9
--- /dev/null
+++ b/src/backend/anastasis-httpd.c
@@ -0,0 +1,943 @@
+/*
+ This file is part of TALER
+ (C) 2020 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero 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 backup/anastasis-httpd.c
+ * @brief HTTP serving layer intended to provide basic backup operations
+ * @author Christian Grothoff
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ */
+#include "platform.h"
+#include "anastasis-httpd.h"
+#include "anastasis_util_lib.h"
+#include "anastasis-httpd_mhd.h"
+#include "anastasis_database_lib.h"
+#include "anastasis-httpd_policy.h"
+#include "anastasis-httpd_truth.h"
+#include "anastasis-httpd_terms.h"
+#include "anastasis-httpd_config.h"
+
+
+/**
+ * Backlog for listen operation on unix-domain sockets.
+ */
+#define UNIX_BACKLOG 500
+
+/**
+ * Upload limit to the service, in megabytes.
+ */
+unsigned long long int AH_upload_limit_mb;
+
+/**
+ * Annual fee for the backup account.
+ */
+struct TALER_Amount AH_annual_fee;
+
+/**
+ * Fee for a truth upload.
+ */
+struct TALER_Amount AH_truth_upload_fee;
+
+/**
+ * Amount of insurance.
+ */
+struct TALER_Amount AH_insurance;
+
+/**
+ * Cost for secure question truth download.
+ */
+struct TALER_Amount AH_question_cost;
+
+/**
+ * Our configuration.
+ */
+const struct GNUNET_CONFIGURATION_Handle *AH_cfg;
+
+/**
+ * Our Taler backend to process payments.
+ */
+char *AH_backend_url;
+
+/**
+ * Taler currency.
+ */
+char *AH_currency;
+
+/**
+ * Our fulfillment URL.
+ */
+char *AH_fulfillment_url;
+
+/**
+ * Our business name.
+ */
+char *AH_business_name;
+
+/**
+ * Our server salt.
+ */
+struct ANASTASIS_CRYPTO_ProviderSaltP AH_server_salt;
+
+/**
+ * Number of policy uploads permitted per annual fee payment.
+ */
+unsigned long long AH_post_counter = 64LLU;
+
+/**
+ * Our context for making HTTP requests.
+ */
+struct GNUNET_CURL_Context *AH_ctx;
+
+/**
+ * Should a "Connection: close" header be added to each HTTP response?
+ */
+static int AH_connection_close;
+
+/**
+ * Task running the HTTP server.
+ */
+static struct GNUNET_SCHEDULER_Task *mhd_task;
+
+/**
+ * Global return code
+ */
+static int global_result;
+
+/**
+ * The MHD Daemon
+ */
+static struct MHD_Daemon *mhd;
+
+/**
+ * Connection handle to the our database
+ */
+struct ANASTASIS_DatabasePlugin *db;
+
+/**
+ * Reschedule context for #SH_ctx.
+ */
+static struct GNUNET_CURL_RescheduleContext *rc;
+
+/**
+ * Set if we should immediately #MHD_run again.
+ */
+static int triggered;
+
+/**
+ * Username and password to use for client authentication
+ * (optional).
+ */
+static char *userpass;
+
+/**
+ * Type of the client's TLS certificate (optional).
+ */
+static char *certtype;
+
+/**
+ * File with the client's TLS certificate (optional).
+ */
+static char *certfile;
+
+/**
+ * File with the client's TLS private key (optional).
+ */
+static char *keyfile;
+
+/**
+ * This value goes in the Authorization:-header.
+ */
+static char *apikey;
+
+/**
+ * Passphrase to decrypt client's TLS private key file (optional).
+ */
+static char *keypass;
+
+
+/**
+ * Function that queries MHD's select sets and
+ * starts the task waiting for them.
+ */
+static struct GNUNET_SCHEDULER_Task *
+prepare_daemon (void);
+
+
+/**
+ * Call MHD to process pending requests and then go back
+ * and schedule the next run.
+ *
+ * @param cls the `struct MHD_Daemon` of the HTTP server to run
+ */
+static void
+run_daemon (void *cls)
+{
+ (void) cls;
+ mhd_task = NULL;
+ do {
+ triggered = 0;
+ GNUNET_assert (MHD_YES == MHD_run (mhd));
+ } while (0 != triggered);
+ mhd_task = prepare_daemon ();
+}
+
+
+/**
+ * Kick MHD to run now, to be called after MHD_resume_connection().
+ * Basically, we need to explicitly resume MHD's event loop whenever
+ * we made progress serving a request. This function re-schedules
+ * the task processing MHD's activities to run immediately.
+ *
+ * @param cls NULL
+ */
+void
+AH_trigger_daemon (void *cls)
+{
+ (void) cls;
+ if (NULL != mhd_task)
+ {
+ GNUNET_SCHEDULER_cancel (mhd_task);
+ mhd_task = GNUNET_SCHEDULER_add_now (&run_daemon,
+ NULL);
+ }
+ else
+ {
+ triggered = 1;
+ }
+}
+
+
+/**
+ * Kick GNUnet Curl scheduler to begin curl interactions.
+ */
+void
+AH_trigger_curl (void)
+{
+ GNUNET_CURL_gnunet_scheduler_reschedule (&rc);
+}
+
+
+/**
+ * A client has requested the given url using the given method
+ * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT,
+ * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). The callback
+ * must call MHD callbacks to provide content to give back to the
+ * client and return an HTTP status code (i.e. #MHD_HTTP_OK,
+ * #MHD_HTTP_NOT_FOUND, etc.).
+ *
+ * @param cls argument given together with the function
+ * pointer when the handler was registered with MHD
+ * @param url the requested url
+ * @param method the HTTP method used (#MHD_HTTP_METHOD_GET,
+ * #MHD_HTTP_METHOD_PUT, etc.)
+ * @param version the HTTP version string (i.e.
+ * #MHD_HTTP_VERSION_1_1)
+ * @param upload_data the data being uploaded (excluding HEADERS,
+ * for a POST that fits into memory and that is encoded
+ * with a supported encoding, the POST data will NOT be
+ * given in upload_data and is instead available as
+ * part of #MHD_get_connection_values; very large POST
+ * data *will* be made available incrementally in
+ * @a upload_data)
+ * @param upload_data_size set initially to the size of the
+ * @a upload_data provided; the method must update this
+ * value to the number of bytes NOT processed;
+ * @param con_cls pointer that the callback can set to some
+ * address and that will be preserved by MHD for future
+ * calls for this request; since the access handler may
+ * be called many times (i.e., for a PUT/POST operation
+ * with plenty of upload data) this allows the application
+ * to easily associate some request-specific state.
+ * If necessary, this state can be cleaned up in the
+ * global #MHD_RequestCompletedCallback (which
+ * can be set with the #MHD_OPTION_NOTIFY_COMPLETED).
+ * Initially, `*con_cls` will be NULL.
+ * @return #MHD_YES if the connection was handled successfully,
+ * #MHD_NO if the socket must be closed due to a serious
+ * error while handling the request
+ */
+static MHD_RESULT
+url_handler (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data,
+ size_t *upload_data_size,
+ void **con_cls)
+{
+ static struct AH_RequestHandler handlers[] = {
+ /* Landing page, tell humans to go away. */
+ { "/", MHD_HTTP_METHOD_GET, "text/plain",
+ "Hello, I'm Anastasis. This HTTP server is not for humans.\n", 0,
+ &TMH_MHD_handler_static_response, MHD_HTTP_OK },
+ { "/agpl", MHD_HTTP_METHOD_GET, "text/plain",
+ NULL, 0,
+ &TMH_MHD_handler_agpl_redirect, MHD_HTTP_FOUND },
+ { "/terms", MHD_HTTP_METHOD_GET, NULL,
+ NULL, 0,
+ &AH_handler_terms, MHD_HTTP_OK },
+ { "/privacy", MHD_HTTP_METHOD_GET, NULL,
+ NULL, 0,
+ &AH_handler_terms, MHD_HTTP_OK },
+ { "/config", MHD_HTTP_METHOD_GET, "text/json",
+ NULL, 0,
+ &AH_handler_config, MHD_HTTP_OK },
+ {NULL, NULL, NULL, NULL, 0, 0 }
+ };
+ static struct AH_RequestHandler h404 = {
+ "", NULL, "text/html",
+ "<html><title>404: not found</title></html>", 0,
+ &TMH_MHD_handler_static_response, MHD_HTTP_NOT_FOUND
+ };
+ static struct AH_RequestHandler h405 = {
+ "", NULL, "text/html",
+ "<html><title>405: method not allowed</title></html>", 0,
+ &TMH_MHD_handler_static_response, MHD_HTTP_METHOD_NOT_ALLOWED
+ };
+ struct TM_HandlerContext *hc = *con_cls;
+ const char *correlation_id = NULL;
+ bool path_matched;
+
+ if (NULL == hc)
+ {
+ struct GNUNET_AsyncScopeId aid;
+
+ GNUNET_async_scope_fresh (&aid);
+ /* We only read the correlation ID on the first callback for every client */
+ correlation_id = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ "Anastasis-Correlation-Id");
+ if ((NULL != correlation_id) &&
+ (GNUNET_YES != GNUNET_CURL_is_valid_scope_id (correlation_id)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Invalid incoming correlation ID\n");
+ correlation_id = NULL;
+ }
+ hc = GNUNET_new (struct TM_HandlerContext);
+ *con_cls = hc;
+ hc->async_scope_id = aid;
+ }
+ if (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_HEAD))
+ method = MHD_HTTP_METHOD_GET; /* MHD will throw away the body */
+
+ GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id);
+ if (NULL != correlation_id)
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Handling request for (%s) URL '%s', correlation_id=%s\n",
+ method,
+ url,
+ correlation_id);
+ else
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Handling request (%s) for URL '%s'\n",
+ method,
+ url);
+ if (0 == strncmp (url,
+ "/policy/",
+ strlen ("/policy/")))
+ {
+ const char *account = url + strlen ("/policy/");
+ struct ANASTASIS_CRYPTO_AccountPublicKeyP account_pub;
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (
+ account,
+ strlen (account),
+ &account_pub,
+ sizeof (struct ANASTASIS_CRYPTO_AccountPublicKeyP)))
+ {
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "account public key");
+ }
+ if (0 == strcmp (method,
+ MHD_HTTP_METHOD_GET))
+ {
+ return AH_policy_get (connection,
+ &account_pub);
+ }
+ if (0 == strcmp (method,
+ MHD_HTTP_METHOD_POST))
+ {
+ return AH_handler_policy_post (connection,
+ hc,
+ &account_pub,
+ upload_data,
+ upload_data_size);
+ }
+ return TMH_MHD_handler_static_response (&h405,
+ connection);
+ }
+ if (0 == strncmp (url,
+ "/truth/",
+ strlen ("/truth/")))
+ {
+ struct ANASTASIS_CRYPTO_TruthUUIDP tu;
+ const char *pub_key_str;
+
+ pub_key_str = &url[strlen ("/truth/")];
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (
+ pub_key_str,
+ strlen (pub_key_str),
+ &tu,
+ sizeof(tu)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "truth UUID");
+ }
+ if (0 == strcmp (method,
+ MHD_HTTP_METHOD_GET))
+ {
+ return AH_handler_truth_get (connection,
+ &tu,
+ hc);
+ }
+ if (0 == strcmp (method,
+ MHD_HTTP_METHOD_POST))
+ {
+ return AH_handler_truth_post (connection,
+ hc,
+ &tu,
+ upload_data,
+ upload_data_size);
+ }
+ return TMH_MHD_handler_static_response (&h405,
+ connection);
+ }
+ path_matched = false;
+ for (unsigned int i = 0; NULL != handlers[i].url; i++)
+ {
+ struct AH_RequestHandler *rh = &handlers[i];
+
+ if (0 == strcmp (url,
+ rh->url))
+ {
+ path_matched = true;
+ if (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_OPTIONS))
+ {
+ return TALER_MHD_reply_cors_preflight (connection);
+ }
+ if ( (NULL == rh->method) ||
+ (0 == strcasecmp (method,
+ rh->method)) )
+ {
+ return rh->handler (rh,
+ connection);
+ }
+ }
+ }
+ if (path_matched)
+ return TMH_MHD_handler_static_response (&h405,
+ connection);
+ return TMH_MHD_handler_static_response (&h404,
+ connection);
+}
+
+
+/**
+ * Shutdown task (magically invoked when the application is being
+ * quit)
+ *
+ * @param cls NULL
+ */
+static void
+do_shutdown (void *cls)
+{
+ (void) cls;
+ AH_resume_all_bc ();
+ AH_truth_shutdown ();
+ AH_truth_upload_shutdown ();
+ if (NULL != mhd_task)
+ {
+ GNUNET_SCHEDULER_cancel (mhd_task);
+ mhd_task = NULL;
+ }
+ if (NULL != AH_ctx)
+ {
+ GNUNET_CURL_fini (AH_ctx);
+ AH_ctx = NULL;
+ }
+ if (NULL != rc)
+ {
+ GNUNET_CURL_gnunet_rc_destroy (rc);
+ rc = NULL;
+ }
+ if (NULL != mhd)
+ {
+ MHD_stop_daemon (mhd);
+ mhd = NULL;
+ }
+ if (NULL != db)
+ {
+ ANASTASIS_DB_plugin_unload (db);
+ db = NULL;
+ }
+}
+
+
+/**
+ * Function called whenever MHD is done with a request. If the
+ * request was a POST, we may have stored a `struct Buffer *` in the
+ * @a con_cls that might still need to be cleaned up. Call the
+ * respective function to free the memory.
+ *
+ * @param cls client-defined closure
+ * @param connection connection handle
+ * @param con_cls value as set by the last call to
+ * the #MHD_AccessHandlerCallback
+ * @param toe reason for request termination
+ * @see #MHD_OPTION_NOTIFY_COMPLETED
+ * @ingroup request
+ */
+static void
+handle_mhd_completion_callback (void *cls,
+ struct MHD_Connection *connection,
+ void **con_cls,
+ enum MHD_RequestTerminationCode toe)
+{
+ struct TM_HandlerContext *hc = *con_cls;
+
+ (void) cls;
+ (void) connection;
+ if (NULL == hc)
+ return;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Finished handling request with status %d\n",
+ (int) toe);
+ if (NULL != hc->cc)
+ hc->cc (hc);
+ GNUNET_free (hc);
+ *con_cls = NULL;
+}
+
+
+/**
+ * Function that queries MHD's select sets and
+ * starts the task waiting for them.
+ *
+ * @param daemon_handle HTTP server to prepare to run
+ */
+static struct GNUNET_SCHEDULER_Task *
+prepare_daemon (void)
+{
+ struct GNUNET_SCHEDULER_Task *ret;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ struct GNUNET_NETWORK_FDSet *wrs;
+ struct GNUNET_NETWORK_FDSet *wws;
+ int max;
+ MHD_UNSIGNED_LONG_LONG timeout;
+ int haveto;
+ struct GNUNET_TIME_Relative tv;
+
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ wrs = GNUNET_NETWORK_fdset_create ();
+ wws = GNUNET_NETWORK_fdset_create ();
+ max = -1;
+ GNUNET_assert (MHD_YES ==
+ MHD_get_fdset (mhd,
+ &rs,
+ &ws,
+ &es,
+ &max));
+ haveto = MHD_get_timeout (mhd, &timeout);
+ if (haveto == MHD_YES)
+ tv = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
+ timeout);
+ else
+ tv = GNUNET_TIME_UNIT_FOREVER_REL;
+ GNUNET_NETWORK_fdset_copy_native (wrs, &rs, max + 1);
+ GNUNET_NETWORK_fdset_copy_native (wws, &ws, max + 1);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Adding run_daemon select task\n");
+ ret = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_HIGH,
+ tv,
+ wrs,
+ wws,
+ &run_daemon,
+ NULL);
+ GNUNET_NETWORK_fdset_destroy (wrs);
+ GNUNET_NETWORK_fdset_destroy (wws);
+ return ret;
+}
+
+
+/**
+ * Main function that will be run by the scheduler.
+ *
+ * @param cls closure
+ * @param args remaining command-line arguments
+ * @param cfgfile name of the configuration file used (for saving, can be
+ * NULL!)
+ * @param config configuration
+ */
+static void
+run (void *cls,
+ char *const *args,
+ const char *cfgfile,
+ const struct GNUNET_CONFIGURATION_Handle *config)
+{
+ int fh;
+ uint16_t port;
+ enum TALER_MHD_GlobalOptions go;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Starting anastasis-httpd\n");
+ go = TALER_MHD_GO_NONE;
+ if (AH_connection_close)
+ go |= TALER_MHD_GO_FORCE_CONNECTION_CLOSE;
+ AH_load_terms (config);
+ TALER_MHD_setup (go);
+ AH_cfg = config;
+ global_result = GNUNET_SYSERR;
+ GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
+ NULL);
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_number (config,
+ "anastasis",
+ "UPLOAD_LIMIT_MB",
+ &AH_upload_limit_mb))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "anastasis",
+ "UPLOAD_LIMIT_MB");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_config_get_amount (config,
+ "anastasis",
+ "INSURANCE",
+ &AH_insurance))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "anastasis",
+ "INSURANCE");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_config_get_amount (config,
+ "authorization-question",
+ "COST",
+ &AH_question_cost))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "authorization-question",
+ "COST");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_config_get_amount (config,
+ "anastasis",
+ "ANNUAL_FEE",
+ &AH_annual_fee))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "anastasis",
+ "ANNUAL_FEE");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_config_get_amount (config,
+ "anastasis",
+ "TRUTH_UPLOAD_FEE",
+ &AH_truth_upload_fee))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "anastasis",
+ "TRUTH_UPLOAD_FEE");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_config_get_currency (config,
+ &AH_currency))
+ {
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (0 != strcasecmp (AH_currency,
+ AH_annual_fee.currency))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ "anastasis",
+ "ANNUAL_FEE",
+ "currency mismatch");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_amount_cmp_currency (&AH_insurance,
+ &AH_annual_fee))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ "anastasis",
+ "INSURANCE",
+ "currency mismatch");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (config,
+ "anastasis",
+ "PAYMENT_BACKEND_URL",
+ &AH_backend_url))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "anastasis",
+ "PAYMENT_BACKEND_URL");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if ( (0 != strncasecmp ("https://",
+ AH_backend_url,
+ strlen ("https://"))) &&
+ (0 != strncasecmp ("http://",
+ AH_backend_url,
+ strlen ("http://"))) )
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ "anastasis",
+ "PAYMENT_BACKEND_URL",
+ "Must be HTTP(S) URL");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+
+ if ( (0 == strcasecmp ("https://",
+ AH_backend_url)) ||
+ (0 == strcasecmp ("http://",
+ AH_backend_url)) )
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ "anastasis",
+ "PAYMENT_BACKEND_URL",
+ "Must have domain name");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (config,
+ "anastasis",
+ "FULFILLMENT_URL",
+ &AH_fulfillment_url))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "anastasis",
+ "FULFILLMENT_URL");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_number (config,
+ "anastasis",
+ "ANNUAL_POLICY_UPLOAD_LIMIT",
+ &AH_post_counter))
+ {
+ /* only warn, we will use the default */
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
+ "anastasis",
+ "ANNUAL_POLICY_UPLOAD_LIMIT");
+ }
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (config,
+ "anastasis",
+ "BUSINESS_NAME",
+ &AH_business_name))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "anastasis",
+ "BUSINESS_NAME");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ {
+ char *server_salt;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (config,
+ "anastasis",
+ "SERVER_SALT",
+ &server_salt))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "anastasis",
+ "SERVER_SALT");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ GNUNET_assert (GNUNET_YES ==
+ GNUNET_CRYPTO_kdf (&AH_server_salt,
+ sizeof (AH_server_salt),
+ "anastasis-server-salt",
+ strlen ("anastasis-server-salt"),
+ server_salt,
+ strlen (server_salt),
+ NULL,
+ 0));
+ GNUNET_free (server_salt);
+ }
+
+ /* setup HTTP client event loop */
+ AH_ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
+ &rc);
+ rc = GNUNET_CURL_gnunet_rc_create (AH_ctx);
+ if (NULL != userpass)
+ GNUNET_CURL_set_userpass (AH_ctx,
+ userpass);
+ if (NULL != keyfile)
+ GNUNET_CURL_set_tlscert (AH_ctx,
+ certtype,
+ certfile,
+ keyfile,
+ keypass);
+ if (NULL != apikey)
+ {
+ char *auth_header;
+
+ GNUNET_asprintf (&auth_header,
+ "%s: %s",
+ MHD_HTTP_HEADER_AUTHORIZATION,
+ apikey);
+ if (GNUNET_OK !=
+ GNUNET_CURL_append_header (AH_ctx,
+ auth_header))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed so set %s header, trying without\n",
+ MHD_HTTP_HEADER_AUTHORIZATION);
+ }
+ GNUNET_free (auth_header);
+ }
+
+ if (NULL ==
+ (db = ANASTASIS_DB_plugin_load (config)))
+ {
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+
+ fh = TALER_MHD_bind (config,
+ "anastasis",
+ &port);
+ if ( (0 == port) &&
+ (-1 == fh) )
+ {
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ mhd = MHD_start_daemon (MHD_USE_SUSPEND_RESUME | MHD_USE_DUAL_STACK,
+ port,
+ NULL, NULL,
+ &url_handler, NULL,
+ MHD_OPTION_LISTEN_SOCKET, fh,
+ MHD_OPTION_NOTIFY_COMPLETED,
+ &handle_mhd_completion_callback, NULL,
+ MHD_OPTION_CONNECTION_TIMEOUT, (unsigned
+ int) 10 /* 10s */,
+ MHD_OPTION_END);
+ if (NULL == mhd)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to launch HTTP service (port %u in use?), exiting.\n",
+ port);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ global_result = GNUNET_OK;
+ mhd_task = prepare_daemon ();
+}
+
+
+/**
+ * The main function of the serve tool
+ *
+ * @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)
+{
+ enum GNUNET_GenericReturnValue res;
+ struct GNUNET_GETOPT_CommandLineOption options[] = {
+ GNUNET_GETOPT_option_string ('A',
+ "auth",
+ "USERNAME:PASSWORD",
+ "use the given USERNAME and PASSWORD for client authentication",
+ &userpass),
+ GNUNET_GETOPT_option_flag ('C',
+ "connection-close",
+ "force HTTP connections to be closed after each request",
+ &AH_connection_close),
+ GNUNET_GETOPT_option_string ('k',
+ "key",
+ "KEYFILE",
+ "file with the private TLS key for TLS client authentication",
+ &keyfile),
+ GNUNET_GETOPT_option_string ('p',
+ "pass",
+ "KEYFILEPASSPHRASE",
+ "passphrase needed to decrypt the TLS client private key file",
+ &keypass),
+ GNUNET_GETOPT_option_string ('K',
+ "apikey",
+ "APIKEY",
+ "API key to use in the HTTP request to the merchant backend",
+ &apikey),
+ GNUNET_GETOPT_option_string ('t',
+ "type",
+ "CERTTYPE",
+ "type of the TLS client certificate, defaults to PEM if not specified",
+ &certtype),
+
+ GNUNET_GETOPT_OPTION_END
+ };
+
+ /* FIRST get the libtalerutil initialization out
+ of the way. Then throw that one away, and force
+ the ANASTASIS defaults to be used! */
+ (void) TALER_project_data_default ();
+ GNUNET_OS_init (ANASTASIS_project_data_default ());
+ res = GNUNET_PROGRAM_run (argc, argv,
+ "anastasis-httpd",
+ "Anastasis HTTP interface",
+ options, &run, NULL);
+ if (GNUNET_SYSERR == res)
+ return 3;
+ if (GNUNET_NO == res)
+ return 0;
+ return (GNUNET_OK == global_result) ? 0 : 1;
+}
diff --git a/src/backend/anastasis-httpd.h b/src/backend/anastasis-httpd.h
new file mode 100644
index 0000000..6fe0023
--- /dev/null
+++ b/src/backend/anastasis-httpd.h
@@ -0,0 +1,225 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2019 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 anastasis/anastasis-httpd.h
+ * @brief HTTP serving layer
+ * @author Christian Grothoff
+ */
+#ifndef ANASTASIS_HTTPD_H
+#define ANASTASIS_HTTPD_H
+
+#include "platform.h"
+#include "anastasis_database_lib.h"
+#include <microhttpd.h>
+#include <taler/taler_mhd_lib.h>
+#include <gnunet/gnunet_mhd_compat.h>
+
+
+/**
+ * For how many years do we allow users to store truth at most? Also
+ * how long we store things if the cost is zero.
+ */
+#define ANASTASIS_MAX_YEARS_STORAGE 5
+
+
+/**
+ * @brief Struct describing an URL and the handler for it.
+ */
+struct AH_RequestHandler
+{
+
+ /**
+ * URL the handler is for.
+ */
+ const char *url;
+
+ /**
+ * Method the handler is for, NULL for "all".
+ */
+ const char *method;
+
+ /**
+ * Mime type to use in reply (hint, can be NULL).
+ */
+ const char *mime_type;
+
+ /**
+ * Raw data for the @e handler
+ */
+ const void *data;
+
+ /**
+ * Number of bytes in @e data, 0 for 0-terminated.
+ */
+ size_t data_size;
+
+
+ /**
+ * Function to call to handle the request.
+ *
+ * @param rh this struct
+ * @param connection the MHD connection to handle
+ * @return MHD result code
+ */
+ MHD_RESULT (*handler)(struct AH_RequestHandler *rh,
+ struct MHD_Connection *connection);
+
+ /**
+ * Default response code.
+ */
+ unsigned int response_code;
+};
+
+
+/**
+ * Each MHD response handler that sets the "connection_cls" to a
+ * non-NULL value must use a struct that has this struct as its first
+ * member. This struct contains a single callback, which will be
+ * invoked to clean up the memory when the contection is completed.
+ */
+struct TM_HandlerContext;
+
+/**
+ * Signature of a function used to clean up the context
+ * we keep in the "connection_cls" of MHD when handling
+ * a request.
+ *
+ * @param hc header of the context to clean up.
+ */
+typedef void
+(*TM_ContextCleanup)(struct TM_HandlerContext *hc);
+
+
+/**
+ * Each MHD response handler that sets the "connection_cls" to a
+ * non-NULL value must use a struct that has this struct as its first
+ * member. This struct contains a single callback, which will be
+ * invoked to clean up the memory when the connection is completed.
+ */
+struct TM_HandlerContext
+{
+
+ /**
+ * Function to execute the handler-specific cleanup of the
+ * (typically larger) context.
+ */
+ TM_ContextCleanup cc;
+
+ /**
+ * Handler-specific context.
+ */
+ void *ctx;
+
+ /**
+ * Which request handler is handling this request?
+ */
+ const struct AH_RequestHandler *rh;
+
+ /**
+ * Asynchronous request context id.
+ */
+ struct GNUNET_AsyncScopeId async_scope_id;
+};
+
+/**
+ * Handle to the database backend.
+ */
+extern struct ANASTASIS_DatabasePlugin *db;
+
+/**
+ * Upload limit to the service, in megabytes.
+ */
+extern unsigned long long AH_upload_limit_mb;
+
+/**
+ * Annual fee for the backup account.
+ */
+extern struct TALER_Amount AH_annual_fee;
+
+/**
+ * Fee for a truth upload.
+ */
+extern struct TALER_Amount AH_truth_upload_fee;
+
+/**
+ * Amount of insurance.
+ */
+extern struct TALER_Amount AH_insurance;
+
+/**
+ * Cost for secure question truth download.
+ */
+extern struct TALER_Amount AH_question_cost;
+
+/**
+ * Our Taler backend to process payments.
+ */
+extern char *AH_backend_url;
+
+/**
+ * Taler currency.
+ */
+extern char *AH_currency;
+
+/**
+ * Our configuration.
+ */
+extern const struct GNUNET_CONFIGURATION_Handle *AH_cfg;
+
+/**
+ * Number of policy uploads permitted per annual fee payment.
+ */
+extern unsigned long long AH_post_counter;
+
+/**
+ * Our fulfillment URL
+ */
+extern char *AH_fulfillment_url;
+
+/**
+ * Our business name.
+ */
+extern char *AH_business_name;
+
+/**
+ * Our server salt.
+ */
+extern struct ANASTASIS_CRYPTO_ProviderSaltP AH_server_salt;
+
+/**
+ * Our context for making HTTP requests.
+ */
+extern struct GNUNET_CURL_Context *AH_ctx;
+
+
+/**
+ * Kick MHD to run now, to be called after MHD_resume_connection().
+ * Basically, we need to explicitly resume MHD's event loop whenever
+ * we made progress serving a request. This function re-schedules
+ * the task processing MHD's activities to run immediately.
+ *
+ * @param cls NULL
+ */
+void
+AH_trigger_daemon (void *cls);
+
+/**
+ * Kick GNUnet Curl scheduler to begin curl interactions.
+ */
+void
+AH_trigger_curl (void);
+
+#endif
diff --git a/src/backend/anastasis-httpd_config.c b/src/backend/anastasis-httpd_config.c
new file mode 100644
index 0000000..fff6bcb
--- /dev/null
+++ b/src/backend/anastasis-httpd_config.c
@@ -0,0 +1,132 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file backend/anastasis-httpd_config.c
+ * @brief headers for /terms handler
+ * @author Christian Grothoff
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ */
+#include "platform.h"
+#include <jansson.h>
+#include "anastasis-httpd_config.h"
+#include "anastasis-httpd.h"
+#include <taler/taler_json_lib.h>
+#include "anastasis_authorization_lib.h"
+
+
+/**
+ * Add enabled methods and their fees to the ``/config`` response.
+ *
+ * @param[in,out] cls a `json_t` array to build
+ * @param section configuration section to inspect
+ */
+static void
+add_methods (void *cls,
+ const char *section)
+{
+ json_t *method_arr = cls;
+ struct ANASTASIS_AuthorizationPlugin *p;
+ struct TALER_Amount cost;
+ json_t *method;
+
+ if (0 != strncasecmp (section,
+ "authorization-",
+ strlen ("authorization-")))
+ return;
+ if (GNUNET_YES !=
+ GNUNET_CONFIGURATION_get_value_yesno (AH_cfg,
+ section,
+ "ENABLED"))
+ return;
+ section += strlen ("authorization-");
+ p = ANASTASIS_authorization_plugin_load (section,
+ AH_cfg,
+ &cost);
+ if (NULL == p)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to load authorization plugin `%s'\n",
+ section);
+ return;
+ }
+ method = json_pack ("{s:s, s:o}",
+ "type",
+ section,
+ "cost",
+ TALER_JSON_from_amount (&cost));
+ GNUNET_assert (NULL != method);
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (method_arr,
+ method));
+}
+
+
+MHD_RESULT
+AH_handler_config (struct AH_RequestHandler *rh,
+ struct MHD_Connection *connection)
+{
+ json_t *method_arr = json_array ();
+
+ GNUNET_assert (NULL != method_arr);
+ {
+ json_t *method;
+
+ method = json_pack ("{s:s, s:o}",
+ "type",
+ "question",
+ "cost",
+ TALER_JSON_from_amount (&AH_question_cost));
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (method_arr,
+ method));
+ }
+ GNUNET_CONFIGURATION_iterate_sections (AH_cfg,
+ &add_methods,
+ method_arr);
+ return TALER_MHD_reply_json_pack (connection,
+ MHD_HTTP_OK,
+ "{s:s, s:s, s:s, s:s, s:o, s:I,"
+ " s:o, s:o, s:o, s:o }",
+ "name",
+ "anastasis",
+ "version",
+ "0:0:0",
+ "business_name",
+ AH_business_name,
+ "currency",
+ (char *) AH_currency,
+ "methods",
+ method_arr,
+ "storage_limit_in_megabytes",
+ (json_int_t) AH_upload_limit_mb,
+ /* 6 */
+ "annual_fee",
+ TALER_JSON_from_amount (&AH_annual_fee),
+ "truth_upload_fee",
+ TALER_JSON_from_amount (
+ &AH_truth_upload_fee),
+ "liability_limit",
+ TALER_JSON_from_amount (&AH_insurance),
+ "server_salt",
+ GNUNET_JSON_from_data_auto (
+ &AH_server_salt));
+}
+
+
+/* end of anastasis-httpd_config.c */
diff --git a/src/backend/anastasis-httpd_config.h b/src/backend/anastasis-httpd_config.h
new file mode 100644
index 0000000..7d58792
--- /dev/null
+++ b/src/backend/anastasis-httpd_config.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file backend/anastasis-httpd_config.h
+ * @brief headers for /terms handler
+ * @author Christian Grothoff
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ */
+#ifndef ANASTASIS_HTTPD_CONFIG_H
+#define ANASTASIS_HTTPD_CONFIG_H
+#include <microhttpd.h>
+#include "anastasis-httpd.h"
+
+/**
+ * Manages a /config call.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @return MHD result code
+ */
+MHD_RESULT
+AH_handler_config (struct AH_RequestHandler *rh,
+ struct MHD_Connection *connection);
+
+#endif
+
+/* end of anastasis-httpd_config.h */
diff --git a/src/backend/anastasis-httpd_mhd.c b/src/backend/anastasis-httpd_mhd.c
new file mode 100644
index 0000000..c39a54c
--- /dev/null
+++ b/src/backend/anastasis-httpd_mhd.c
@@ -0,0 +1,70 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015, 2016 GNUnet e.V. and INRIA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file anastasis-httpd_mhd.c
+ * @brief helpers for MHD interaction; these are TALER_EXCHANGE_handler_ functions
+ * that generate simple MHD replies that do not require any real operations
+ * to be performed (error handling, static pages, etc.)
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include "anastasis-httpd_mhd.h"
+
+
+/**
+ * Function to call to handle the request by sending
+ * back static data from the @a rh.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_MHD_handler_static_response (struct AH_RequestHandler *rh,
+ struct MHD_Connection *connection)
+{
+ if (0 == rh->data_size)
+ rh->data_size = strlen ((const char *) rh->data);
+ return TALER_MHD_reply_static (connection,
+ rh->response_code,
+ rh->mime_type,
+ (void *) rh->data,
+ rh->data_size);
+}
+
+
+/**
+ * Function to call to handle the request by sending
+ * back a redirect to the AGPL source code.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_MHD_handler_agpl_redirect (struct AH_RequestHandler *rh,
+ struct MHD_Connection *connection)
+{
+ (void) rh;
+ return TALER_MHD_reply_agpl (connection,
+ "http://www.git.taler.net/anastasis.git");
+}
+
+
+/* end of anastasis-httpd_mhd.c */
diff --git a/src/backend/anastasis-httpd_mhd.h b/src/backend/anastasis-httpd_mhd.h
new file mode 100644
index 0000000..628abfa
--- /dev/null
+++ b/src/backend/anastasis-httpd_mhd.h
@@ -0,0 +1,61 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015 GNUnet e.V. and INRIA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file anastasis-httpd_mhd.h
+ * @brief helpers for MHD interaction, used to generate simple responses
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#ifndef ANASTASIS_HTTPD_MHD_H
+#define ANASTASIS_HTTPD_MHD_H
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "anastasis-httpd.h"
+
+
+/**
+ * Function to call to handle the request by sending
+ * back static data from the @a rh.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param mi merchant backend instance, NULL is allowed in this case!
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_MHD_handler_static_response (struct AH_RequestHandler *rh,
+ struct MHD_Connection *connection);
+
+
+/**
+ * Function to call to handle the request by sending
+ * back a redirect to the AGPL source code.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_MHD_handler_agpl_redirect (struct AH_RequestHandler *rh,
+ struct MHD_Connection *connection);
+
+
+#endif
diff --git a/src/backend/anastasis-httpd_policy.c b/src/backend/anastasis-httpd_policy.c
new file mode 100644
index 0000000..2417e15
--- /dev/null
+++ b/src/backend/anastasis-httpd_policy.c
@@ -0,0 +1,252 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2019, 2021 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file anastasis-httpd_policy.c
+ * @brief functions to handle incoming requests on /policy/
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "anastasis-httpd.h"
+#include "anastasis-httpd_policy.h"
+#include "anastasis_service.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_rest_lib.h>
+#include <taler/taler_json_lib.h>
+#include <taler/taler_merchant_service.h>
+#include <taler/taler_signatures.h>
+
+/**
+ * How long do we hold an HTTP client connection if
+ * we are awaiting payment before giving up?
+ */
+#define CHECK_PAYMENT_GENERIC_TIMEOUT GNUNET_TIME_relative_multiply ( \
+ GNUNET_TIME_UNIT_MINUTES, 30)
+
+
+/**
+ * Return the current recoverydocument of @a account on @a connection
+ * using @a default_http_status on success.
+ *
+ * @param connection MHD connection to use
+ * @param account account to query
+ * @return MHD result code
+ */
+static MHD_RESULT
+return_policy (struct MHD_Connection *connection,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ struct MHD_Response *resp;
+ struct ANASTASIS_AccountSignatureP account_sig;
+ struct GNUNET_HashCode recovery_data_hash;
+ const char *version_s;
+ char version_b[14];
+ uint32_t version;
+ void *res_recovery_data;
+ size_t res_recovery_data_size;
+
+ version_s = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "version");
+ if (NULL != version_s)
+ {
+ char dummy;
+
+ if (1 != sscanf (version_s,
+ "%u%c",
+ &version,
+ &dummy))
+ {
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "version");
+ }
+ qs = db->get_recovery_document (db->cls,
+ account_pub,
+ version,
+ &account_sig,
+ &recovery_data_hash,
+ &res_recovery_data_size,
+ &res_recovery_data);
+ }
+ else
+ {
+ qs = db->get_latest_recovery_document (db->cls,
+ account_pub,
+ &account_sig,
+ &recovery_data_hash,
+ &res_recovery_data_size,
+ &res_recovery_data,
+ &version);
+ GNUNET_snprintf (version_b,
+ sizeof (version_b),
+ "%u",
+ (unsigned int) version);
+ version_s = version_b;
+ }
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get_recovery_document");
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_SOFT_FAILURE,
+ "get_recovery_document");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_ANASTASIS_POLICY_NOT_FOUND,
+ NULL);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* interesting case below */
+ break;
+ }
+ resp = MHD_create_response_from_buffer (res_recovery_data_size,
+ res_recovery_data,
+ MHD_RESPMEM_MUST_FREE);
+ TALER_MHD_add_global_headers (resp);
+ {
+ char *sig_s;
+ char *etag;
+
+ sig_s = GNUNET_STRINGS_data_to_string_alloc (&account_sig,
+ sizeof (account_sig));
+ etag = GNUNET_STRINGS_data_to_string_alloc (&recovery_data_hash,
+ sizeof (recovery_data_hash));
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ ANASTASIS_HTTP_HEADER_POLICY_SIGNATURE,
+ sig_s));
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ ANASTASIS_HTTP_HEADER_POLICY_VERSION,
+ version_s));
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ MHD_HTTP_HEADER_ETAG,
+ etag));
+ GNUNET_free (etag);
+ GNUNET_free (sig_s);
+ }
+ {
+ MHD_RESULT ret;
+
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_OK,
+ resp);
+ MHD_destroy_response (resp);
+ return ret;
+ }
+}
+
+
+MHD_RESULT
+AH_policy_get (struct MHD_Connection *connection,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub)
+{
+ struct GNUNET_HashCode recovery_data_hash;
+ enum ANASTASIS_DB_AccountStatus as;
+ MHD_RESULT ret;
+ uint32_t version;
+ struct GNUNET_TIME_Absolute expiration;
+
+ as = db->lookup_account (db->cls,
+ account_pub,
+ &expiration,
+ &recovery_data_hash,
+ &version);
+ switch (as)
+ {
+ case ANASTASIS_DB_ACCOUNT_STATUS_PAYMENT_REQUIRED:
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_SYNC_ACCOUNT_UNKNOWN,
+ NULL);
+ case ANASTASIS_DB_ACCOUNT_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup account");
+ case ANASTASIS_DB_ACCOUNT_STATUS_NO_RESULTS:
+ {
+ struct MHD_Response *resp;
+
+ resp = MHD_create_response_from_buffer (0,
+ NULL,
+ MHD_RESPMEM_PERSISTENT);
+ TALER_MHD_add_global_headers (resp);
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_NO_CONTENT,
+ resp);
+ MHD_destroy_response (resp);
+ }
+ return ret;
+ case ANASTASIS_DB_ACCOUNT_STATUS_VALID_HASH_RETURNED:
+ {
+ const char *inm;
+
+ inm = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_IF_NONE_MATCH);
+ if (NULL != inm)
+ {
+ struct GNUNET_HashCode inm_h;
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (inm,
+ strlen (inm),
+ &inm_h,
+ sizeof (inm_h)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_ANASTASIS_POLICY_BAD_IF_NONE_MATCH,
+ "Etag must be a base32-encoded SHA-512 hash");
+ }
+ if (0 == GNUNET_memcmp (&inm_h,
+ &recovery_data_hash))
+ {
+ struct MHD_Response *resp;
+
+ resp = MHD_create_response_from_buffer (0,
+ NULL,
+ MHD_RESPMEM_PERSISTENT);
+ TALER_MHD_add_global_headers (resp);
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_NOT_MODIFIED,
+ resp);
+ MHD_destroy_response (resp);
+ return ret;
+ }
+ }
+ }
+ /* We have a result, should fetch and return it! */
+ break;
+ }
+ return return_policy (connection,
+ account_pub);
+}
diff --git a/src/backend/anastasis-httpd_policy.h b/src/backend/anastasis-httpd_policy.h
new file mode 100644
index 0000000..9fb630d
--- /dev/null
+++ b/src/backend/anastasis-httpd_policy.h
@@ -0,0 +1,66 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015, 2016 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file anastasis-httpd_policy.h
+ * @brief functions to handle incoming requests on /policy/
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ * @author Christian Grothoff
+ */
+#ifndef ANASTASIS_HTTPD_POLICY_H
+#define ANASTASIS_HTTPD_POLICY_H
+#include <microhttpd.h>
+
+
+/**
+ * Service is shutting down, resume all MHD connections NOW.
+ */
+void
+AH_resume_all_bc (void);
+
+
+/**
+ * Handle GET /policy/$ACCOUNT_PUB request.
+ *
+ * @param connection the MHD connection to handle
+ * @param account_pub public key of the account
+ * @return MHD result code
+ */
+MHD_RESULT
+AH_policy_get (struct MHD_Connection *connection,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub);
+
+
+/**
+ * Handle POST /policy/$ACCOUNT_PUB request.
+ *
+ * @param connection the MHD connection to handle
+ * @param con_cls the connection's closure
+ * @param account_pub public key of the account
+ * @param upload_data upload data
+ * @param upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+MHD_RESULT
+AH_handler_policy_post (
+ struct MHD_Connection *connection,
+ struct TM_HandlerContext *hc,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub,
+ const char *upload_data,
+ size_t *upload_data_size);
+
+
+#endif
diff --git a/src/backend/anastasis-httpd_policy_upload.c b/src/backend/anastasis-httpd_policy_upload.c
new file mode 100644
index 0000000..b8bd5ed
--- /dev/null
+++ b/src/backend/anastasis-httpd_policy_upload.c
@@ -0,0 +1,1211 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2021 Anastasis SARL
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file anastasis-httpd_policy.c
+ * @brief functions to handle incoming requests on /policy/
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "anastasis-httpd.h"
+#include "anastasis-httpd_policy.h"
+#include "anastasis_service.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_rest_lib.h>
+#include <taler/taler_json_lib.h>
+#include <taler/taler_merchant_service.h>
+#include <taler/taler_signatures.h>
+
+/**
+ * How long do we hold an HTTP client connection if
+ * we are awaiting payment before giving up?
+ */
+#define CHECK_PAYMENT_GENERIC_TIMEOUT GNUNET_TIME_relative_multiply ( \
+ GNUNET_TIME_UNIT_MINUTES, 30)
+
+
+/**
+ * Context for an upload operation.
+ */
+struct PolicyUploadContext
+{
+
+ /**
+ * Signature of the account holder.
+ */
+ struct ANASTASIS_AccountSignatureP account_sig;
+
+ /**
+ * Public key of the account holder.
+ */
+ struct ANASTASIS_CRYPTO_AccountPublicKeyP account;
+
+ /**
+ * Hash of the upload we are receiving right now (as promised
+ * by the client, to be verified!).
+ */
+ struct GNUNET_HashCode new_policy_upload_hash;
+
+ /**
+ * Hash context for the upload.
+ */
+ struct GNUNET_HashContext *hash_ctx;
+
+ /**
+ * Kept in DLL for shutdown handling while suspended.
+ */
+ struct PolicyUploadContext *next;
+
+ /**
+ * Kept in DLL for shutdown handling while suspended.
+ */
+ struct PolicyUploadContext *prev;
+
+ /**
+ * Used while suspended for resumption.
+ */
+ struct MHD_Connection *con;
+
+ /**
+ * Upload, with as many bytes as we have received so far.
+ */
+ char *upload;
+
+ /**
+ * Used while we are awaiting proposal creation.
+ */
+ struct TALER_MERCHANT_PostOrdersHandle *po;
+
+ /**
+ * Used while we are waiting payment.
+ */
+ struct TALER_MERCHANT_OrderMerchantGetHandle *cpo;
+
+ /**
+ * HTTP response code to use on resume, if non-NULL.
+ */
+ struct MHD_Response *resp;
+
+ /**
+ * Order under which the client promised payment, or NULL.
+ */
+ const char *order_id;
+
+ /**
+ * Payment Identifier
+ */
+ struct ANASTASIS_PaymentSecretP payment_identifier;
+
+ /**
+ * Timestamp of the order in @e payment_identifier. Used to
+ * select the most recent unpaid offer.
+ */
+ struct GNUNET_TIME_Absolute existing_pi_timestamp;
+
+ /**
+ * When does the operation timeout?
+ */
+ struct GNUNET_TIME_Absolute timeout;
+
+ /**
+ * How long must the account be valid? Determines whether we should
+ * trigger payment, and if so how much.
+ */
+ struct GNUNET_TIME_Absolute end_date;
+
+ /**
+ * How long is the account already valid?
+ * Determines how much the user needs to pay.
+ */
+ struct GNUNET_TIME_Absolute paid_until;
+
+ /**
+ * Expected total upload size.
+ */
+ size_t upload_size;
+
+ /**
+ * Current offset for the upload.
+ */
+ size_t upload_off;
+
+ /**
+ * HTTP response code to use on resume, if resp is set.
+ */
+ unsigned int response_code;
+
+ /**
+ * For how many years does the client still have
+ * to pay?
+ */
+ unsigned int years_to_pay;
+
+ /**
+ * true if client provided a payment secret / order ID?
+ */
+ bool payment_identifier_provided;
+
+};
+
+
+/**
+ * Kept in DLL for shutdown handling while suspended.
+ */
+static struct PolicyUploadContext *puc_head;
+
+/**
+ * Kept in DLL for shutdown handling while suspended.
+ */
+static struct PolicyUploadContext *puc_tail;
+
+
+/**
+ * Service is shutting down, resume all MHD connections NOW.
+ */
+void
+AH_resume_all_bc ()
+{
+ struct PolicyUploadContext *puc;
+
+ while (NULL != (puc = puc_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (puc_head,
+ puc_tail,
+ puc);
+ if (NULL != puc->po)
+ {
+ TALER_MERCHANT_orders_post_cancel (puc->po);
+ puc->po = NULL;
+ }
+ if (NULL != puc->cpo)
+ {
+ TALER_MERCHANT_merchant_order_get_cancel (puc->cpo);
+ puc->cpo = NULL;
+ }
+ MHD_resume_connection (puc->con);
+ }
+}
+
+
+/**
+ * Function called to clean up a backup context.
+ *
+ * @param hc a `struct PolicyUploadContext`
+ */
+static void
+cleanup_ctx (struct TM_HandlerContext *hc)
+{
+ struct PolicyUploadContext *puc = hc->ctx;
+
+ if (NULL != puc->po)
+ TALER_MERCHANT_orders_post_cancel (puc->po);
+ if (NULL != puc->cpo)
+ TALER_MERCHANT_merchant_order_get_cancel (puc->cpo);
+ if (NULL != puc->hash_ctx)
+ GNUNET_CRYPTO_hash_context_abort (puc->hash_ctx);
+ if (NULL != puc->resp)
+ MHD_destroy_response (puc->resp);
+ GNUNET_free (puc->upload);
+ GNUNET_free (puc);
+}
+
+
+/**
+ * Transmit a payment request for @a order_id on @a connection
+ *
+ * @param connection MHD connection
+ * @param order_id our backend's order ID
+ * @return #GNUNET_OK on success
+ */
+static int
+make_payment_request (struct PolicyUploadContext *puc)
+{
+ struct MHD_Response *resp;
+
+ /* request payment via Taler */
+ resp = MHD_create_response_from_buffer (0,
+ NULL,
+ MHD_RESPMEM_PERSISTENT);
+ if (NULL == resp)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ TALER_MHD_add_global_headers (resp);
+ {
+ char *hdr;
+ char *pfx;
+ char *hn;
+
+ if (0 == strncasecmp ("https://",
+ AH_backend_url,
+ strlen ("https://")))
+ {
+ pfx = "taler://";
+ hn = &AH_backend_url[strlen ("https://")];
+ }
+ else if (0 == strncasecmp ("http://",
+ AH_backend_url,
+ strlen ("http://")))
+ {
+ pfx = "taler+http://";
+ hn = &AH_backend_url[strlen ("http://")];
+ }
+ else
+ {
+ GNUNET_break (0);
+ MHD_destroy_response (resp);
+ return GNUNET_SYSERR;
+ }
+ if (0 == strlen (hn))
+ {
+ GNUNET_break (0);
+ MHD_destroy_response (resp);
+ return GNUNET_SYSERR;
+ }
+ {
+ char *order_id;
+
+ order_id = GNUNET_STRINGS_data_to_string_alloc (
+ &puc->payment_identifier,
+ sizeof (puc->payment_identifier));
+ GNUNET_asprintf (&hdr,
+ "%spay/%s%s/",
+ pfx,
+ hn,
+ order_id);
+ GNUNET_free (order_id);
+ }
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ ANASTASIS_HTTP_HEADER_TALER,
+ hdr));
+ GNUNET_free (hdr);
+ }
+ puc->resp = resp;
+ puc->response_code = MHD_HTTP_PAYMENT_REQUIRED;
+ return GNUNET_OK;
+}
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * POST /private/orders request to a merchant.
+ *
+ * @param cls our `struct PolicyUploadContext`
+ * @param por response details
+ */
+static void
+proposal_cb (void *cls,
+ const struct TALER_MERCHANT_PostOrdersReply *por)
+{
+ struct PolicyUploadContext *puc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ puc->po = NULL;
+ GNUNET_CONTAINER_DLL_remove (puc_head,
+ puc_tail,
+ puc);
+ MHD_resume_connection (puc->con);
+ AH_trigger_daemon (NULL);
+ if (MHD_HTTP_OK != por->hr.http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Backend returned status %u/%d\n",
+ por->hr.http_status,
+ (int) por->hr.ec);
+ GNUNET_break (0);
+ puc->resp = TALER_MHD_make_json_pack (
+ "{s:I, s:s, s:I, s:I, s:O?}",
+ "code",
+ (json_int_t) TALER_EC_SYNC_PAYMENT_CREATE_BACKEND_ERROR,
+ "hint",
+ "Failed to setup order with merchant backend",
+ "backend-ec",
+ (json_int_t) por->hr.ec,
+ "backend-http-status",
+ (json_int_t) por->hr.http_status,
+ "backend-reply",
+ por->hr.reply);
+ GNUNET_assert (NULL != puc->resp);
+ puc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Storing payment request for order `%s'\n",
+ por->details.ok.order_id);
+
+ qs = db->record_recdoc_payment (db->cls,
+ &puc->account,
+ (uint32_t) AH_post_counter,
+ &puc->payment_identifier,
+ &AH_annual_fee);
+ if (0 >= qs)
+ {
+ GNUNET_break (0);
+ puc->resp = TALER_MHD_make_error (
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "record recdoc payment");
+ puc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ return;
+ }
+ if (GNUNET_OK !=
+ make_payment_request (puc))
+ {
+ GNUNET_break (0);
+ puc->resp = TALER_MHD_make_error (
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "failed to initiate payment");
+ puc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ }
+}
+
+
+/**
+ * Callback to process a GET /check-payment request
+ *
+ * @param cls our `struct PolicyUploadContext`
+ * @param hr HTTP response details
+ * @param osr order status
+ */
+static void
+check_payment_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr,
+ const struct TALER_MERCHANT_OrderStatusResponse *osr)
+{
+ struct PolicyUploadContext *puc = cls;
+
+ /* refunds are not supported, verify */
+ puc->cpo = NULL;
+ GNUNET_CONTAINER_DLL_remove (puc_head,
+ puc_tail,
+ puc);
+ MHD_resume_connection (puc->con);
+ AH_trigger_daemon (NULL);
+ switch (hr->http_status)
+ {
+ case MHD_HTTP_OK:
+ GNUNET_assert (NULL != osr);
+ break; /* processed below */
+ case MHD_HTTP_UNAUTHORIZED:
+ puc->resp = TALER_MHD_make_error (
+ TALER_EC_ANASTASIS_GENERIC_PAYMENT_CHECK_UNAUTHORIZED,
+ NULL);
+ puc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ return;
+ default:
+ puc->resp = TALER_MHD_make_error (
+ TALER_EC_ANASTASIS_GENERIC_BACKEND_ERROR,
+ "failed to initiate payment");
+ puc->response_code = MHD_HTTP_BAD_GATEWAY;
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Payment status checked: %s\n",
+ osr->status ? "paid" : "unpaid");
+ switch (osr->status)
+ {
+ case TALER_MERCHANT_OSC_PAID:
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ unsigned int years;
+ struct GNUNET_TIME_Relative paid_until;
+ const json_t *contract;
+ struct TALER_Amount amount;
+ struct GNUNET_JSON_Specification cspec[] = {
+ TALER_JSON_spec_amount ("amount",
+ AH_currency,
+ &amount),
+ GNUNET_JSON_spec_end ()
+ };
+
+ contract = osr->details.paid.contract_terms;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (contract,
+ cspec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ puc->resp = TALER_MHD_make_error (
+ TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
+ "no amount given");
+ puc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ return; /* continue as planned */
+ }
+ years = TALER_amount_divide2 (&amount,
+ &AH_annual_fee);
+ paid_until = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_YEARS,
+ years);
+ /* add 1 week grace period, otherwise if a user
+ wants to pay for 1 year, the first seconds
+ would have passed between making the payment
+ and our subsequent check if +1 year was
+ paid... So we actually say 1 year = 52 weeks
+ on the server, while the client calculates
+ with 365 days. */
+ paid_until = GNUNET_TIME_relative_add (paid_until,
+ GNUNET_TIME_UNIT_WEEKS);
+
+ qs = db->increment_lifetime (db->cls,
+ &puc->account,
+ &puc->payment_identifier,
+ paid_until,
+ &puc->paid_until);
+ if (0 <= qs)
+ return; /* continue as planned */
+ GNUNET_break (0);
+ puc->resp = TALER_MHD_make_error (
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "increment lifetime");
+ puc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ return; /* continue as planned */
+ }
+ case TALER_MERCHANT_OSC_UNPAID:
+ case TALER_MERCHANT_OSC_CLAIMED:
+ break;
+ }
+ if (0 != puc->existing_pi_timestamp.abs_value_us)
+ {
+ /* repeat payment request */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Repeating payment request\n");
+ if (GNUNET_OK !=
+ make_payment_request (puc))
+ {
+ GNUNET_break (0);
+ puc->resp = TALER_MHD_make_error (
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "failed to initiate payment");
+ puc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ }
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Timeout waiting for payment\n");
+ puc->resp = TALER_MHD_make_error (TALER_EC_SYNC_PAYMENT_GENERIC_TIMEOUT,
+ "Timeout awaiting promised payment");
+ GNUNET_assert (NULL != puc->resp);
+ puc->response_code = MHD_HTTP_REQUEST_TIMEOUT;
+}
+
+
+/**
+ * Helper function used to ask our backend to await
+ * a payment for the user's account.
+ *
+ * @param puc context to begin payment for.
+ * @param timeout when to give up trying
+ */
+static void
+await_payment (struct PolicyUploadContext *puc)
+{
+ struct GNUNET_TIME_Relative timeout
+ = GNUNET_TIME_absolute_get_remaining (puc->timeout);
+
+ GNUNET_CONTAINER_DLL_insert (puc_head,
+ puc_tail,
+ puc);
+ MHD_suspend_connection (puc->con);
+ {
+ char *order_id;
+
+ order_id = GNUNET_STRINGS_data_to_string_alloc (
+ &puc->payment_identifier,
+ sizeof(struct ANASTASIS_PaymentSecretP));
+ puc->cpo = TALER_MERCHANT_merchant_order_get (AH_ctx,
+ AH_backend_url,
+ order_id,
+ NULL /* our payments are NOT session-bound */,
+ false,
+ timeout,
+ &check_payment_cb,
+ puc);
+ GNUNET_free (order_id);
+ }
+ AH_trigger_curl ();
+}
+
+
+/**
+ * Helper function used to ask our backend to begin processing a
+ * payment for the user's account. May perform asynchronous
+ * operations by suspending the connection if required.
+ *
+ * @param puc context to begin payment for.
+ * @return MHD status code
+ */
+static MHD_RESULT
+begin_payment (struct PolicyUploadContext *puc)
+{
+ json_t *order;
+
+ GNUNET_CONTAINER_DLL_insert (puc_head,
+ puc_tail,
+ puc);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Suspending connection while creating order at `%s'\n",
+ AH_backend_url);
+ {
+ char *order_id;
+ struct TALER_Amount upload_fee;
+
+ if (0 >
+ TALER_amount_multiply (&upload_fee,
+ &AH_annual_fee,
+ puc->years_to_pay))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (puc->con,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "storage_duration_years");
+ }
+
+ order_id = GNUNET_STRINGS_data_to_string_alloc (
+ &puc->payment_identifier,
+ sizeof(struct ANASTASIS_PaymentSecretP));
+ order = json_pack ("{s:o, s:s, s:[{s:s,s:I,s:s}], s:s }",
+ "amount", TALER_JSON_from_amount (&upload_fee),
+ "summary", "Anastasis policy storage fee",
+ "products",
+ "description", "policy storage fee",
+ "quantity", (json_int_t) puc->years_to_pay,
+ "unit", "years",
+ "order_id", order_id);
+ GNUNET_free (order_id);
+ }
+ MHD_suspend_connection (puc->con);
+ puc->po = TALER_MERCHANT_orders_post2 (AH_ctx,
+ AH_backend_url,
+ order,
+ GNUNET_TIME_UNIT_ZERO,
+ NULL, /* no payment target */
+ 0,
+ NULL, /* no inventory products */
+ 0,
+ NULL, /* no uuids */
+ false, /* do NOT require claim token */
+ &proposal_cb,
+ puc);
+ AH_trigger_curl ();
+ json_decref (order);
+ return MHD_YES;
+}
+
+
+/**
+ * Prepare to receive a payment, possibly requesting it, or just waiting
+ * for it to be completed by the client.
+ *
+ * @param puc context to prepare payment for
+ * @return MHD status
+ */
+static MHD_RESULT
+prepare_payment (struct PolicyUploadContext *puc)
+{
+ if (! puc->payment_identifier_provided)
+ {
+ GNUNET_CRYPTO_random_block (
+ GNUNET_CRYPTO_QUALITY_NONCE,
+ &puc->payment_identifier,
+ sizeof (struct ANASTASIS_PaymentSecretP));
+ puc->payment_identifier_provided = true;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "No payment identifier, initiating payment\n");
+ return begin_payment (puc);
+ }
+ await_payment (puc);
+ return MHD_YES;
+}
+
+
+MHD_RESULT
+AH_handler_policy_post (
+ struct MHD_Connection *connection,
+ struct TM_HandlerContext *hc,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub,
+ const char *recovery_data,
+ size_t *recovery_data_size)
+{
+ struct PolicyUploadContext *puc = hc->ctx;
+
+ if (NULL == puc)
+ {
+ /* first call, setup internals */
+ puc = GNUNET_new (struct PolicyUploadContext);
+ hc->ctx = puc;
+ hc->cc = &cleanup_ctx;
+ puc->con = connection;
+
+ {
+ const char *pay_id;
+
+ pay_id = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER);
+ if (NULL != pay_id)
+ {
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (
+ pay_id,
+ strlen (pay_id),
+ &puc->payment_identifier,
+ sizeof (struct ANASTASIS_PaymentSecretP)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER
+ " header must be a base32-encoded Payment-Secret");
+ }
+ puc->payment_identifier_provided = true;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Policy upload started with payment identifier `%s'\n",
+ pay_id);
+ }
+ }
+ puc->account = *account_pub;
+ /* now setup 'puc' */
+ {
+ const char *lens;
+ unsigned long len;
+
+ lens = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_CONTENT_LENGTH);
+ if ( (NULL == lens) ||
+ (1 != sscanf (lens,
+ "%lu",
+ &len)) )
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ (NULL == lens)
+ ? TALER_EC_ANASTASIS_GENERIC_MISSING_CONTENT_LENGTH
+ : TALER_EC_ANASTASIS_GENERIC_MALFORMED_CONTENT_LENGTH,
+ NULL);
+ }
+ if (len / 1024 / 1024 >= AH_upload_limit_mb)
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_PAYLOAD_TOO_LARGE,
+ TALER_EC_SYNC_MALFORMED_CONTENT_LENGTH,
+ "Content-length value not acceptable");
+ }
+ puc->upload = GNUNET_malloc_large (len);
+ if (NULL == puc->upload)
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "malloc");
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_PAYLOAD_TOO_LARGE,
+ TALER_EC_ANASTASIS_POLICY_OUT_OF_MEMORY_ON_CONTENT_LENGTH,
+ NULL);
+ }
+ puc->upload_size = (size_t) len;
+ }
+ {
+ /* Check if header contains Anastasis-Policy-Signature */
+ const char *sig_s;
+
+ sig_s = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ ANASTASIS_HTTP_HEADER_POLICY_SIGNATURE);
+ if ( (NULL == sig_s) ||
+ (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (sig_s,
+ strlen (sig_s),
+ &puc->account_sig,
+ sizeof (puc->account_sig))) )
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_ANASTASIS_POLICY_BAD_SIGNATURE,
+ ANASTASIS_HTTP_HEADER_POLICY_SIGNATURE
+ " header must include a base32-encoded EdDSA signature");
+ }
+ }
+ {
+ /* Check if header contains an ETAG */
+ const char *etag;
+
+ etag = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_IF_NONE_MATCH);
+ if ( (NULL == etag) ||
+ (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (etag,
+ strlen (etag),
+ &puc->new_policy_upload_hash,
+ sizeof (puc->new_policy_upload_hash))) )
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_ANASTASIS_POLICY_BAD_IF_MATCH,
+ MHD_HTTP_HEADER_IF_NONE_MATCH
+ " header must include a base32-encoded SHA-512 hash");
+ }
+ }
+ /* validate signature */
+ {
+ struct ANASTASIS_UploadSignaturePS usp = {
+ .purpose.size = htonl (sizeof (usp)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_ANASTASIS_POLICY_UPLOAD),
+ .new_recovery_data_hash = puc->new_policy_upload_hash
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_ANASTASIS_POLICY_UPLOAD,
+ &usp,
+ &puc->account_sig.eddsa_sig,
+ &account_pub->pub))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_ANASTASIS_POLICY_BAD_SIGNATURE,
+ ANASTASIS_HTTP_HEADER_POLICY_SIGNATURE);
+ }
+ }
+
+ {
+ const char *long_poll_timeout_ms;
+
+ long_poll_timeout_ms = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "timeout_ms");
+ if (NULL != long_poll_timeout_ms)
+ {
+ unsigned int timeout;
+
+ if (1 != sscanf (long_poll_timeout_ms,
+ "%u",
+ &timeout))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "timeout_ms (must be non-negative number)");
+ }
+ puc->timeout
+ = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_MILLISECONDS,
+ timeout));
+ }
+ else
+ {
+ puc->timeout = GNUNET_TIME_relative_to_absolute
+ (CHECK_PAYMENT_GENERIC_TIMEOUT);
+ }
+ }
+
+ /* check if the client insists on paying */
+ {
+ const char *req;
+ unsigned int years;
+
+ req = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "storage_duration");
+ if (NULL != req)
+ {
+ char dummy;
+
+ if (1 != sscanf (req,
+ "%u%c",
+ &years,
+ &dummy))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "storage_duration (must be non-negative number)");
+ }
+ }
+ else
+ {
+ years = 0;
+ }
+ puc->end_date = GNUNET_TIME_relative_to_absolute (
+ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_YEARS,
+ years));
+ }
+
+ /* get ready to hash (done here as we may go async for payments next) */
+ puc->hash_ctx = GNUNET_CRYPTO_hash_context_start ();
+
+ /* Check database to see if the transaction is permissible */
+ {
+ struct GNUNET_TIME_Relative rem;
+
+ rem = GNUNET_TIME_absolute_get_remaining (puc->end_date);
+ puc->years_to_pay = rem.rel_value_us
+ / GNUNET_TIME_UNIT_YEARS.rel_value_us;
+ if (0 != (rem.rel_value_us % GNUNET_TIME_UNIT_YEARS.rel_value_us))
+ puc->years_to_pay++;
+
+ if (puc->payment_identifier_provided)
+ {
+ /* check if payment identifier is valid (existing and paid) */
+ bool paid;
+ bool valid_counter;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = db->check_payment_identifier (db->cls,
+ &puc->payment_identifier,
+ &paid,
+ &valid_counter);
+ if (qs < 0)
+ return TALER_MHD_reply_with_error (puc->con,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL);
+
+ if ( (! paid) ||
+ (! valid_counter) )
+ {
+ if (! valid_counter)
+ {
+ puc->payment_identifier_provided = false;
+ if (0 == puc->years_to_pay)
+ puc->years_to_pay = 1;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Too many uploads with this payment identifier, initiating fresh payment\n");
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Given payment identifier not known to be paid, initiating payment\n");
+ }
+ return prepare_payment (puc);
+ }
+ }
+
+ if (! puc->payment_identifier_provided)
+ {
+ struct TALER_Amount zero_amount;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Relative rel;
+
+ TALER_amount_set_zero (AH_currency,
+ &zero_amount);
+ /* generate fresh payment identifier */
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG,
+ &puc->payment_identifier,
+ sizeof (struct ANASTASIS_PaymentSecretP));
+ if (0 != TALER_amount_cmp (&AH_annual_fee,
+ &zero_amount))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "No payment identifier, requesting payment\n");
+ return begin_payment (puc);
+ }
+ /* Cost is zero, fake "zero" payment having happened */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Policy upload is free, allowing upload without payment\n");
+ qs = db->record_recdoc_payment (db->cls,
+ account_pub,
+ AH_post_counter,
+ &puc->payment_identifier,
+ &AH_annual_fee);
+ if (qs <= 0)
+ return TALER_MHD_reply_with_error (puc->con,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL);
+ rel = GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_YEARS,
+ ANASTASIS_MAX_YEARS_STORAGE);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Policy lifetime is %s (%u years)\n",
+ GNUNET_STRINGS_relative_time_to_string (rel,
+ GNUNET_YES),
+ ANASTASIS_MAX_YEARS_STORAGE);
+ puc->paid_until = GNUNET_TIME_relative_to_absolute (rel);
+ qs = db->update_lifetime (db->cls,
+ account_pub,
+ &puc->payment_identifier,
+ puc->paid_until);
+ if (qs <= 0)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (puc->con,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL);
+ }
+ }
+ }
+
+ /* Check if existing policy matches upload (and if, skip it) */
+ {
+ struct GNUNET_HashCode hc;
+ enum ANASTASIS_DB_AccountStatus as;
+ uint32_t version;
+ struct GNUNET_TIME_Absolute now;
+ struct GNUNET_TIME_Relative rem;
+
+ as = db->lookup_account (db->cls,
+ account_pub,
+ &puc->paid_until,
+ &hc,
+ &version);
+ now = GNUNET_TIME_absolute_get ();
+ if (puc->paid_until.abs_value_us < now.abs_value_us)
+ puc->paid_until = now;
+ rem = GNUNET_TIME_absolute_get_difference (puc->paid_until,
+ puc->end_date);
+ puc->years_to_pay = rem.rel_value_us
+ / GNUNET_TIME_UNIT_YEARS.rel_value_us;
+ if (0 != (rem.rel_value_us % GNUNET_TIME_UNIT_YEARS.rel_value_us))
+ puc->years_to_pay++;
+
+ if ( (ANASTASIS_DB_ACCOUNT_STATUS_VALID_HASH_RETURNED == as) &&
+ (0 != puc->years_to_pay) )
+ {
+ /* user requested extension, force payment */
+ as = ANASTASIS_DB_ACCOUNT_STATUS_PAYMENT_REQUIRED;
+ }
+ switch (as)
+ {
+ case ANASTASIS_DB_ACCOUNT_STATUS_PAYMENT_REQUIRED:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Expiration too low, initiating payment\n");
+ return prepare_payment (puc);
+ case ANASTASIS_DB_ACCOUNT_STATUS_HARD_ERROR:
+ return TALER_MHD_reply_with_error (puc->con,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL);
+ case ANASTASIS_DB_ACCOUNT_STATUS_NO_RESULTS:
+ /* continue below */
+ break;
+ case ANASTASIS_DB_ACCOUNT_STATUS_VALID_HASH_RETURNED:
+ if (0 == GNUNET_memcmp (&hc,
+ &puc->new_policy_upload_hash))
+ {
+ /* Refuse upload: we already have that backup! */
+ struct MHD_Response *resp;
+ MHD_RESULT ret;
+ char version_s[14];
+
+ GNUNET_snprintf (version_s,
+ sizeof (version_s),
+ "%u",
+ (unsigned int) version);
+ resp = MHD_create_response_from_buffer (0,
+ NULL,
+ MHD_RESPMEM_PERSISTENT);
+ TALER_MHD_add_global_headers (resp);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ ANASTASIS_HTTP_HEADER_POLICY_VERSION,
+ version_s));
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_NOT_MODIFIED,
+ resp);
+ GNUNET_break (MHD_YES == ret);
+ MHD_destroy_response (resp);
+ return ret;
+ }
+ break;
+ }
+ }
+ /* ready to begin! */
+ return MHD_YES;
+ }
+
+ if (NULL != puc->resp)
+ {
+ MHD_RESULT ret;
+
+ /* We generated a response asynchronously, queue that */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Returning asynchronously generated response with HTTP status %u\n",
+ puc->response_code);
+ ret = MHD_queue_response (connection,
+ puc->response_code,
+ puc->resp);
+ GNUNET_break (MHD_YES == ret);
+ MHD_destroy_response (puc->resp);
+ puc->resp = NULL;
+ return ret;
+ }
+
+ /* handle upload */
+ if (0 != *recovery_data_size)
+ {
+ /* check MHD invariant */
+ GNUNET_assert (puc->upload_off + *recovery_data_size <= puc->upload_size);
+ memcpy (&puc->upload[puc->upload_off],
+ recovery_data,
+ *recovery_data_size);
+ puc->upload_off += *recovery_data_size;
+ GNUNET_CRYPTO_hash_context_read (puc->hash_ctx,
+ recovery_data,
+ *recovery_data_size);
+ *recovery_data_size = 0;
+ return MHD_YES;
+ }
+
+ if ( (0 == puc->upload_off) &&
+ (0 != puc->upload_size) &&
+ (NULL == puc->resp) )
+ {
+ /* wait for upload */
+ return MHD_YES;
+ }
+
+ /* finished with upload, check hash */
+ if (NULL != puc->hash_ctx)
+ {
+ struct GNUNET_HashCode our_hash;
+
+ GNUNET_CRYPTO_hash_context_finish (puc->hash_ctx,
+ &our_hash);
+ puc->hash_ctx = NULL;
+ if (0 != GNUNET_memcmp (&our_hash,
+ &puc->new_policy_upload_hash))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_ANASTASIS_POLICY_INVALID_UPLOAD,
+ "Data uploaded does not match Etag promise");
+ }
+ }
+
+ /* store backup to database */
+ {
+ enum ANASTASIS_DB_StoreStatus ss;
+ uint32_t version = UINT32_MAX;
+ char version_s[14];
+ char expir_s[32];
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Uploading recovery document\n");
+ ss = db->store_recovery_document (db->cls,
+ &puc->account,
+ &puc->account_sig,
+ &puc->new_policy_upload_hash,
+ puc->upload,
+ puc->upload_size,
+ &puc->payment_identifier,
+ &version);
+ GNUNET_snprintf (version_s,
+ sizeof (version_s),
+ "%u",
+ (unsigned int) version);
+ GNUNET_snprintf (expir_s,
+ sizeof (expir_s),
+ "%llu",
+ (unsigned long long)
+ (puc->paid_until.abs_value_us
+ / GNUNET_TIME_UNIT_SECONDS.rel_value_us));
+ switch (ss)
+ {
+ case ANASTASIS_DB_STORE_STATUS_STORE_LIMIT_EXCEEDED:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Storage request limit exceeded, requesting payment\n");
+ if (! puc->payment_identifier_provided)
+ {
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG,
+ &puc->payment_identifier,
+ sizeof (struct ANASTASIS_PaymentSecretP));
+ puc->payment_identifier_provided = true;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Also no payment identifier, requesting payment\n");
+ }
+ return begin_payment (puc);
+ case ANASTASIS_DB_STORE_STATUS_PAYMENT_REQUIRED:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Policy store operation requires payment\n");
+ if (! puc->payment_identifier_provided)
+ {
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG,
+ &puc->payment_identifier,
+ sizeof (struct ANASTASIS_PaymentSecretP));
+ puc->payment_identifier_provided = true;
+ }
+ return begin_payment (puc);
+ case ANASTASIS_DB_STORE_STATUS_HARD_ERROR:
+ case ANASTASIS_DB_STORE_STATUS_SOFT_ERROR:
+ return TALER_MHD_reply_with_error (puc->con,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL);
+ case ANASTASIS_DB_STORE_STATUS_NO_RESULTS:
+ {
+ /* database says nothing actually changed, 304 (could
+ theoretically happen if another equivalent upload succeeded
+ since we last checked!) */
+ struct MHD_Response *resp;
+ MHD_RESULT ret;
+
+ resp = MHD_create_response_from_buffer (0,
+ NULL,
+ MHD_RESPMEM_PERSISTENT);
+ TALER_MHD_add_global_headers (resp);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ "Anastasis-Version",
+ version_s));
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_NOT_MODIFIED,
+ resp);
+ GNUNET_break (MHD_YES == ret);
+ MHD_destroy_response (resp);
+ return ret;
+ }
+ case ANASTASIS_DB_STORE_STATUS_SUCCESS:
+ /* generate main (204) standard success reply */
+ {
+ struct MHD_Response *resp;
+ MHD_RESULT ret;
+
+ resp = MHD_create_response_from_buffer (0,
+ NULL,
+ MHD_RESPMEM_PERSISTENT);
+ TALER_MHD_add_global_headers (resp);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ ANASTASIS_HTTP_HEADER_POLICY_VERSION,
+ version_s));
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ ANASTASIS_HTTP_HEADER_POLICY_EXPIRATION,
+ expir_s));
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_NO_CONTENT,
+ resp);
+ GNUNET_break (MHD_YES == ret);
+ MHD_destroy_response (resp);
+ return ret;
+ }
+ }
+ }
+ GNUNET_break (0);
+ return MHD_NO;
+}
diff --git a/src/backend/anastasis-httpd_terms.c b/src/backend/anastasis-httpd_terms.c
new file mode 100644
index 0000000..6be5690
--- /dev/null
+++ b/src/backend/anastasis-httpd_terms.c
@@ -0,0 +1,98 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file backend/anastasis-httpd_terms.c
+ * @brief headers for /terms handler
+ * @author Christian Grothoff
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ */
+#include "platform.h"
+#include "anastasis-httpd_terms.h"
+#include <taler/taler_json_lib.h>
+
+/**
+ * Our terms of service.
+ */
+static struct TALER_MHD_Legal *tos;
+
+
+/**
+ * Our privacy policy.
+ */
+static struct TALER_MHD_Legal *pp;
+
+
+/**
+ * Manages a /terms call.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @return MHD result code
+ */
+MHD_RESULT
+AH_handler_terms (struct AH_RequestHandler *rh,
+ struct MHD_Connection *connection)
+{
+ (void) rh;
+ return TALER_MHD_reply_legal (connection,
+ tos);
+}
+
+
+/**
+ * Handle a "/privacy" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @return MHD result code
+ */
+MHD_RESULT
+AH_handler_privacy (const struct AH_RequestHandler *rh,
+ struct MHD_Connection *connection)
+{
+ (void) rh;
+ return TALER_MHD_reply_legal (connection,
+ pp);
+}
+
+
+/**
+ * Load our terms of service as per configuration.
+ *
+ * @param cfg configuration to process
+ */
+void
+AH_load_terms (const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ tos = TALER_MHD_legal_load (cfg,
+ "anastasis",
+ "TERMS_DIR",
+ "TERMS_ETAG");
+ if (NULL == tos)
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Terms of service not configured\n");
+ pp = TALER_MHD_legal_load (cfg,
+ "anastasis",
+ "PRIVACY_DIR",
+ "PRIVACY_ETAG");
+ if (NULL == pp)
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Privacy policy not configured\n");
+}
+
+
+/* end of anastasis-httpd_terms.c */
diff --git a/src/backend/anastasis-httpd_terms.h b/src/backend/anastasis-httpd_terms.h
new file mode 100644
index 0000000..dc59d41
--- /dev/null
+++ b/src/backend/anastasis-httpd_terms.h
@@ -0,0 +1,62 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 2021 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file backend/anastasis-httpd_terms.h
+ * @brief headers for /terms handler
+ * @author Christian Grothoff
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ */
+#ifndef ANASTASIS_HTTPD_TERMS_H
+#define ANASTASIS_HTTPD_TERMS_H
+#include <microhttpd.h>
+#include "anastasis-httpd.h"
+
+/**
+ * Manages a /terms call.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @return MHD result code
+ */
+MHD_RESULT
+AH_handler_terms (struct AH_RequestHandler *rh,
+ struct MHD_Connection *connection);
+
+
+/**
+ * Handle a "/privacy" request.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @return MHD result code
+ */
+MHD_RESULT
+AH_handler_privacy (const struct AH_RequestHandler *rh,
+ struct MHD_Connection *connection);
+
+/**
+ * Load our terms of service as per configuration.
+ *
+ * @param cfg configuration to process
+ */
+void
+AH_load_terms (const struct GNUNET_CONFIGURATION_Handle *cfg);
+
+
+#endif
+
+/* end of anastasis-httpd_terms.h */
diff --git a/src/backend/anastasis-httpd_truth.c b/src/backend/anastasis-httpd_truth.c
new file mode 100644
index 0000000..164c33a
--- /dev/null
+++ b/src/backend/anastasis-httpd_truth.c
@@ -0,0 +1,1428 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2019, 2021 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file anastasis-httpd_truth.c
+ * @brief functions to handle incoming requests on /truth
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "anastasis-httpd.h"
+#include "anastasis_service.h"
+#include "anastasis-httpd_truth.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_rest_lib.h>
+#include "anastasis_authorization_lib.h"
+#include <taler/taler_merchant_service.h>
+#include <taler/taler_json_lib.h>
+
+/**
+ * What is the maximum frequency at which we allow
+ * clients to attempt to answer security questions?
+ */
+#define MAX_QUESTION_FREQ GNUNET_TIME_relative_multiply ( \
+ GNUNET_TIME_UNIT_SECONDS, 30)
+
+/**
+ * How long do we hold an HTTP client connection if
+ * we are awaiting payment before giving up?
+ */
+#define CHECK_PAYMENT_GENERIC_TIMEOUT GNUNET_TIME_relative_multiply ( \
+ GNUNET_TIME_UNIT_MINUTES, 30)
+
+/**
+ * How long should the wallet check for auto-refunds before giving up?
+ */
+#define AUTO_REFUND_TIMEOUT GNUNET_TIME_relative_multiply ( \
+ GNUNET_TIME_UNIT_MINUTES, 2)
+
+
+/**
+ * How many retries do we allow per code?
+ */
+#define INITIAL_RETRY_COUNTER 3
+
+struct GetContext
+{
+
+ /**
+ * Payment Identifier
+ */
+ struct ANASTASIS_PaymentSecretP payment_identifier;
+
+ /**
+ * Public key of the challenge which is solved.
+ */
+ struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid;
+
+ /**
+ * Key to decrypt the truth.
+ */
+ struct ANASTASIS_CRYPTO_TruthKeyP truth_key;
+
+ /**
+ * true if client provided a payment secret / order ID?
+ */
+ struct TALER_Amount challenge_cost;
+
+ /**
+ * Our handler context.
+ */
+ struct TM_HandlerContext *hc;
+
+ /**
+ * Kept in DLL for shutdown handling while suspended.
+ */
+ struct GetContext *next;
+
+ /**
+ * Kept in DLL for shutdown handling while suspended.
+ */
+ struct GetContext *prev;
+
+ /**
+ * Connection handle for closing or resuming
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * Reference to the authorization plugin which was loaded
+ */
+ struct ANASTASIS_AuthorizationPlugin *authorization;
+
+ /**
+ * Status of the authorization
+ */
+ struct ANASTASIS_AUTHORIZATION_State *as;
+
+ /**
+ * Used while we are awaiting proposal creation.
+ */
+ struct TALER_MERCHANT_PostOrdersHandle *po;
+
+ /**
+ * Used while we are waiting payment.
+ */
+ struct TALER_MERCHANT_OrderMerchantGetHandle *cpo;
+
+ /**
+ * HTTP response code to use on resume, if non-NULL.
+ */
+ struct MHD_Response *resp;
+
+ /**
+ * How long do we wait at most for payment?
+ */
+ struct GNUNET_TIME_Absolute timeout;
+
+ /**
+ * Random authorization code we are using.
+ */
+ uint64_t code;
+
+ /**
+ * HTTP response code to use on resume, if resp is set.
+ */
+ unsigned int response_code;
+
+ /**
+ * true if client provided a payment secret / order ID?
+ */
+ bool payment_identifier_provided;
+
+ /**
+ * True if this entry is in the #gc_head DLL.
+ */
+ bool in_list;
+
+ /**
+ * True if this entry is currently suspended.
+ */
+ bool suspended;
+
+ /**
+ * Did the request include a response?
+ */
+ bool have_response;
+
+};
+
+/**
+ * Information we track for refunds.
+ */
+struct RefundEntry
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct RefundEntry *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct RefundEntry *prev;
+
+ /**
+ * Operation handle.
+ */
+ struct TALER_MERCHANT_OrderRefundHandle *ro;
+
+ /**
+ * Which order is being refunded.
+ */
+ char *order_id;
+
+ /**
+ * Payment Identifier
+ */
+ struct ANASTASIS_PaymentSecretP payment_identifier;
+
+ /**
+ * Public key of the challenge which is solved.
+ */
+ struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid;
+};
+
+
+/**
+ * Head of linked list of active refund operations.
+ */
+static struct RefundEntry *re_head;
+
+/**
+ * Tail of linked list of active refund operations.
+ */
+static struct RefundEntry *re_tail;
+
+/**
+ * Head of linked list over all authorization processes
+ */
+static struct GetContext *gc_head;
+
+/**
+ * Tail of linked list over all authorization processes
+ */
+static struct GetContext *gc_tail;
+
+
+void
+AH_truth_shutdown (void)
+{
+ struct GetContext *gc;
+ struct RefundEntry *re;
+
+ while (NULL != (re = re_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (re_head,
+ re_tail,
+ re);
+ if (NULL != re->ro)
+ {
+ TALER_MERCHANT_post_order_refund_cancel (re->ro);
+ re->ro = NULL;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Refund `%s' failed due to shutdown\n",
+ re->order_id);
+ GNUNET_free (re->order_id);
+ GNUNET_free (re);
+ }
+
+ while (NULL != (gc = gc_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (gc_head,
+ gc_tail,
+ gc);
+ gc->in_list = false;
+ if (NULL != gc->cpo)
+ {
+ TALER_MERCHANT_merchant_order_get_cancel (gc->cpo);
+ gc->cpo = NULL;
+ }
+ if (NULL != gc->po)
+ {
+ TALER_MERCHANT_orders_post_cancel (gc->po);
+ gc->po = NULL;
+ }
+ if (gc->suspended)
+ {
+ MHD_resume_connection (gc->connection);
+ gc->suspended = false;
+ }
+ if (NULL != gc->as)
+ {
+ gc->authorization->cleanup (gc->as);
+ gc->as = NULL;
+ gc->authorization = NULL;
+ }
+ }
+ ANASTASIS_authorization_plugin_shutdown ();
+}
+
+
+/**
+ * Callback to process a POST /orders/ID/refund request
+ *
+ * @param cls closure
+ * @param http_status HTTP status code for this request
+ * @param ec taler-specific error code
+ * @param taler_refund_uri the refund uri offered to the wallet
+ * @param h_contract hash of the contract a Browser may need to authorize
+ * obtaining the HTTP response.
+ */
+static void
+refund_cb (
+ void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr,
+ const char *taler_refund_uri,
+ const struct GNUNET_HashCode *h_contract)
+{
+ struct RefundEntry *re = cls;
+
+ re->ro = NULL;
+ switch (hr->http_status)
+ {
+ case MHD_HTTP_OK:
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Refund `%s' succeeded\n",
+ re->order_id);
+ qs = db->record_challenge_refund (db->cls,
+ &re->truth_uuid,
+ &re->payment_identifier);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ break;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break (0);
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ }
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Refund `%s' failed with HTTP status %u: %s (#%u)\n",
+ re->order_id,
+ hr->http_status,
+ hr->hint,
+ (unsigned int) hr->ec);
+ break;
+ }
+ GNUNET_CONTAINER_DLL_remove (re_head,
+ re_tail,
+ re);
+ GNUNET_free (re->order_id);
+ GNUNET_free (re);
+}
+
+
+/**
+ * Start to give a refund for the challenge created by @a gc.
+ *
+ * @param gc request where we failed and should now grant a refund for
+ */
+static void
+begin_refund (const struct GetContext *gc)
+{
+ struct RefundEntry *re;
+
+ re = GNUNET_new (struct RefundEntry);
+ re->order_id = GNUNET_STRINGS_data_to_string_alloc (
+ &gc->payment_identifier,
+ sizeof (gc->payment_identifier));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Challenge execution failed, triggering refund for order `%s'\n",
+ re->order_id);
+ re->payment_identifier = gc->payment_identifier;
+ re->truth_uuid = gc->truth_uuid;
+ re->ro = TALER_MERCHANT_post_order_refund (AH_ctx,
+ AH_backend_url,
+ re->order_id,
+ &gc->challenge_cost,
+ "failed to issue challenge",
+ &refund_cb,
+ re);
+ if (NULL == re->ro)
+ {
+ GNUNET_break (0);
+ GNUNET_free (re->order_id);
+ GNUNET_free (re);
+ return;
+ }
+ GNUNET_CONTAINER_DLL_insert (re_head,
+ re_tail,
+ re);
+}
+
+
+/**
+ * Callback used to notify the application about completed requests.
+ * Cleans up the requests data structures.
+ *
+ * @param hc
+ */
+static void
+request_done (struct TM_HandlerContext *hc)
+{
+ struct GetContext *gc = hc->ctx;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Request completed\n");
+ if (NULL == gc)
+ return;
+ hc->cc = NULL;
+ GNUNET_assert (! gc->suspended);
+ if (gc->in_list)
+ {
+ GNUNET_CONTAINER_DLL_remove (gc_head,
+ gc_tail,
+ gc);
+ gc->in_list = false;
+ }
+ if (NULL != gc->as)
+ {
+ gc->authorization->cleanup (gc->as);
+ gc->authorization = NULL;
+ gc->as = NULL;
+ }
+ if (NULL != gc->cpo)
+ {
+ TALER_MERCHANT_merchant_order_get_cancel (gc->cpo);
+ gc->cpo = NULL;
+ }
+ if (NULL != gc->po)
+ {
+ TALER_MERCHANT_orders_post_cancel (gc->po);
+ gc->po = NULL;
+ }
+ GNUNET_free (gc);
+ hc->ctx = NULL;
+}
+
+
+/**
+ * Transmit a payment request for @a order_id on @a connection
+ *
+ * @param gc context to make payment request for
+ */
+static void
+make_payment_request (struct GetContext *gc)
+{
+ struct MHD_Response *resp;
+
+ resp = MHD_create_response_from_buffer (0,
+ NULL,
+ MHD_RESPMEM_PERSISTENT);
+ GNUNET_assert (NULL != resp);
+ TALER_MHD_add_global_headers (resp);
+ {
+ char *hdr;
+ char *order_id;
+ const char *pfx;
+ const char *hn;
+
+ if (0 == strncasecmp ("https://",
+ AH_backend_url,
+ strlen ("https://")))
+ {
+ pfx = "taler://";
+ hn = &AH_backend_url[strlen ("https://")];
+ }
+ else if (0 == strncasecmp ("http://",
+ AH_backend_url,
+ strlen ("http://")))
+ {
+ pfx = "taler+http://";
+ hn = &AH_backend_url[strlen ("http://")];
+ }
+ else
+ {
+ /* This invariant holds as per check in anastasis-httpd.c */
+ GNUNET_assert (0);
+ }
+ /* This invariant holds as per check in anastasis-httpd.c */
+ GNUNET_assert (0 != strlen (hn));
+
+ order_id = GNUNET_STRINGS_data_to_string_alloc (
+ &gc->payment_identifier,
+ sizeof (gc->payment_identifier));
+ GNUNET_asprintf (&hdr,
+ "%spay/%s%s/",
+ pfx,
+ hn,
+ order_id);
+ GNUNET_free (order_id);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Sending payment request `%s'\n",
+ hdr);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ ANASTASIS_HTTP_HEADER_TALER,
+ hdr));
+ GNUNET_free (hdr);
+ }
+ gc->resp = resp;
+ gc->response_code = MHD_HTTP_PAYMENT_REQUIRED;
+}
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * /contract request to a merchant.
+ *
+ * @param cls our `struct GetContext`
+ * @param por response details
+ */
+static void
+proposal_cb (void *cls,
+ const struct TALER_MERCHANT_PostOrdersReply *por)
+{
+ struct GetContext *gc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ gc->po = NULL;
+ GNUNET_assert (gc->in_list);
+ GNUNET_CONTAINER_DLL_remove (gc_head,
+ gc_tail,
+ gc);
+ gc->in_list = false;
+ GNUNET_assert (gc->suspended);
+ MHD_resume_connection (gc->connection);
+ gc->suspended = false;
+ AH_trigger_daemon (NULL);
+ if (MHD_HTTP_OK != por->hr.http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Backend returned status %u/%d\n",
+ por->hr.http_status,
+ (int) por->hr.ec);
+ GNUNET_break (0);
+ gc->resp = TALER_MHD_make_json_pack (
+ "{s:I, s:s, s:I, s:I, s:O?}",
+ "code",
+ (json_int_t) TALER_EC_ANASTASIS_TRUTH_PAYMENT_CREATE_BACKEND_ERROR,
+ "hint",
+ "Failed to setup order with merchant backend",
+ "backend-ec",
+ (json_int_t) por->hr.ec,
+ "backend-http-status",
+ (json_int_t) por->hr.http_status,
+ "backend-reply",
+ por->hr.reply);
+ GNUNET_assert (NULL != gc->resp);
+ gc->response_code = MHD_HTTP_BAD_GATEWAY;
+ return;
+ }
+ qs = db->record_challenge_payment (db->cls,
+ &gc->truth_uuid,
+ &gc->payment_identifier,
+ &gc->challenge_cost);
+ if (0 >= qs)
+ {
+ GNUNET_break (0);
+ gc->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED,
+ "record challenge payment");
+ gc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Setup fresh order, creating payment request\n");
+ make_payment_request (gc);
+}
+
+
+/**
+ * Callback to process a GET /check-payment request
+ *
+ * @param cls our `struct GetContext`
+ * @param hr HTTP response details
+ * @param osr order status
+ */
+static void
+check_payment_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr,
+ const struct TALER_MERCHANT_OrderStatusResponse *osr)
+
+{
+ struct GetContext *gc = cls;
+
+ gc->cpo = NULL;
+ GNUNET_assert (gc->in_list);
+ GNUNET_CONTAINER_DLL_remove (gc_head,
+ gc_tail,
+ gc);
+ gc->in_list = false;
+ GNUNET_assert (gc->suspended);
+ MHD_resume_connection (gc->connection);
+ gc->suspended = false;
+ AH_trigger_daemon (NULL);
+
+ switch (hr->http_status)
+ {
+ case MHD_HTTP_OK:
+ GNUNET_assert (NULL != osr);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* We created this order before, how can it be not found now? */
+ GNUNET_break (0);
+ gc->resp = TALER_MHD_make_error (TALER_EC_ANASTASIS_TRUTH_ORDER_DISAPPEARED,
+ NULL);
+ gc->response_code = MHD_HTTP_BAD_GATEWAY;
+ return;
+ case MHD_HTTP_BAD_GATEWAY:
+ gc->resp = TALER_MHD_make_error (
+ TALER_EC_ANASTASIS_TRUTH_BACKEND_EXCHANGE_BAD,
+ NULL);
+ gc->response_code = MHD_HTTP_BAD_GATEWAY;
+ return;
+ case MHD_HTTP_GATEWAY_TIMEOUT:
+ gc->resp = TALER_MHD_make_error (TALER_EC_ANASTASIS_GENERIC_BACKEND_TIMEOUT,
+ "Timeout check payment status");
+ GNUNET_assert (NULL != gc->resp);
+ gc->response_code = MHD_HTTP_GATEWAY_TIMEOUT;
+ return;
+ default:
+ {
+ char status[14];
+
+ GNUNET_snprintf (status,
+ sizeof (status),
+ "%u",
+ hr->http_status);
+ gc->resp = TALER_MHD_make_error (
+ TALER_EC_ANASTASIS_TRUTH_UNEXPECTED_PAYMENT_STATUS,
+ status);
+ GNUNET_assert (NULL != gc->resp);
+ gc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ return;
+ }
+ }
+
+ switch (osr->status)
+ {
+ case TALER_MERCHANT_OSC_PAID:
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = db->update_challenge_payment (db->cls,
+ &gc->truth_uuid,
+ &gc->payment_identifier);
+ if (0 <= qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order has been paid, continuing with request processing\n");
+ return; /* continue as planned */
+ }
+ GNUNET_break (0);
+ gc->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED,
+ "update challenge payment");
+ gc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ return; /* continue as planned */
+ }
+ case TALER_MERCHANT_OSC_CLAIMED:
+ case TALER_MERCHANT_OSC_UNPAID:
+ /* repeat payment request */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order remains unpaid, sending payment request again\n");
+ make_payment_request (gc);
+ return;
+ }
+ /* should never get here */
+ GNUNET_break (0);
+}
+
+
+/**
+ * Helper function used to ask our backend to begin processing a
+ * payment for the user's account. May perform asynchronous
+ * operations by suspending the connection if required.
+ *
+ * @param gc context to begin payment for.
+ * @return MHD status code
+ */
+static MHD_RESULT
+begin_payment (struct GetContext *gc)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ char *order_id;
+
+ qs = db->lookup_challenge_payment (db->cls,
+ &gc->truth_uuid,
+ &gc->payment_identifier);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (gc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup challenge payment");
+ }
+ GNUNET_assert (! gc->in_list);
+ gc->in_list = true;
+ GNUNET_CONTAINER_DLL_insert (gc_tail,
+ gc_head,
+ gc);
+ GNUNET_assert (! gc->suspended);
+ gc->suspended = true;
+ MHD_suspend_connection (gc->connection);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ /* We already created the order, check if it was paid */
+ struct GNUNET_TIME_Relative timeout;
+
+ order_id = GNUNET_STRINGS_data_to_string_alloc (
+ &gc->payment_identifier,
+ sizeof (gc->payment_identifier));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order exists, checking payment status for order `%s'\n",
+ order_id);
+ timeout = GNUNET_TIME_absolute_get_remaining (gc->timeout);
+ gc->cpo = TALER_MERCHANT_merchant_order_get (AH_ctx,
+ AH_backend_url,
+ order_id,
+ NULL /* NOT session-bound */,
+ false,
+ timeout,
+ &check_payment_cb,
+ gc);
+ }
+ else
+ {
+ /* Create a fresh order */
+ json_t *order;
+ struct GNUNET_TIME_Absolute pay_deadline;
+
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &gc->payment_identifier,
+ sizeof (struct ANASTASIS_PaymentSecretP));
+ order_id = GNUNET_STRINGS_data_to_string_alloc (
+ &gc->payment_identifier,
+ sizeof (gc->payment_identifier));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Creating fresh order `%s'\n",
+ order_id);
+ pay_deadline = GNUNET_TIME_relative_to_absolute (
+ ANASTASIS_CHALLENGE_OFFER_LIFETIME);
+ GNUNET_TIME_round_abs (&pay_deadline);
+ order = json_pack ("{s:o, s:s, s:s, s:o, s:o}",
+ "amount", TALER_JSON_from_amount (&gc->challenge_cost),
+ "summary", "challenge fee for anastasis service",
+ "order_id", order_id,
+ "auto_refund", GNUNET_JSON_from_time_rel (
+ AUTO_REFUND_TIMEOUT),
+ "pay_deadline", GNUNET_JSON_from_time_abs (
+ pay_deadline));
+ gc->po = TALER_MERCHANT_orders_post2 (AH_ctx,
+ AH_backend_url,
+ order,
+ AUTO_REFUND_TIMEOUT,
+ NULL, /* no payment target */
+ 0,
+ NULL, /* no inventory products */
+ 0,
+ NULL, /* no uuids */
+ false, /* do NOT require claim token */
+ &proposal_cb,
+ gc);
+ json_decref (order);
+ }
+ GNUNET_free (order_id);
+ AH_trigger_curl ();
+ return MHD_YES;
+}
+
+
+/**
+ * Load encrypted keyshare from db and return it to the client.
+ *
+ * @param truth_uuid UUID to the truth for the looup
+ * @param connection the connection to respond upon
+ * @return MHD status code
+ */
+static MHD_RESULT
+return_key_share (
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ struct MHD_Connection *connection)
+{
+ struct ANASTASIS_CRYPTO_EncryptedKeyShareP encrypted_keyshare;
+
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = db->get_key_share (db->cls,
+ truth_uuid,
+ &encrypted_keyshare);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get key share");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_ANASTASIS_TRUTH_KEY_SHARE_GONE,
+ NULL);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Returning key share\n");
+ {
+ struct MHD_Response *resp;
+ MHD_RESULT ret;
+
+ resp = MHD_create_response_from_buffer (sizeof (encrypted_keyshare),
+ &encrypted_keyshare,
+ MHD_RESPMEM_MUST_COPY);
+ TALER_MHD_add_global_headers (resp);
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_OK,
+ resp);
+ MHD_destroy_response (resp);
+ return ret;
+ }
+}
+
+
+/**
+ * Run the authorization method-specific 'process' function and continue
+ * based on its result with generating an HTTP response.
+ *
+ * @param connection the connection we are handling
+ * @param gc our overall handler context
+ */
+static MHD_RESULT
+run_authorization_process (struct MHD_Connection *connection,
+ struct GetContext *gc)
+{
+ enum ANASTASIS_AUTHORIZATION_Result ret;
+ enum GNUNET_DB_QueryStatus qs;
+
+ ret = gc->authorization->process (gc->as,
+ connection);
+ switch (ret)
+ {
+ case ANASTASIS_AUTHORIZATION_RES_SUCCESS:
+ /* Challenge sent successfully */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Authorization request sent successfully\n");
+ qs = db->mark_challenge_sent (db->cls,
+ &gc->payment_identifier,
+ &gc->truth_uuid,
+ gc->code);
+ GNUNET_break (0 < qs);
+ gc->authorization->cleanup (gc->as);
+ gc->as = NULL;
+ return MHD_YES;
+ case ANASTASIS_AUTHORIZATION_RES_FAILED:
+ if (gc->payment_identifier_provided)
+ {
+ begin_refund (gc);
+ }
+ gc->authorization->cleanup (gc->as);
+ gc->as = NULL;
+ return MHD_YES;
+ case ANASTASIS_AUTHORIZATION_RES_SUSPENDED:
+ /* connection was suspended again, odd that this happens */
+ return MHD_YES;
+ case ANASTASIS_AUTHORIZATION_RES_SUCCESS_REPLY_FAILED:
+ /* Challenge sent successfully */
+ qs = db->mark_challenge_sent (db->cls,
+ &gc->payment_identifier,
+ &gc->truth_uuid,
+ gc->code);
+ GNUNET_break (0 < qs);
+ gc->authorization->cleanup (gc->as);
+ gc->as = NULL;
+ return MHD_NO;
+ case ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED:
+ gc->authorization->cleanup (gc->as);
+ gc->as = NULL;
+ return MHD_NO;
+ }
+ GNUNET_break (0);
+ return MHD_NO;
+}
+
+
+/**
+ * @param connection the MHD connection to handle
+ * @param url handles a URL of the format "/truth/$UUID[&response=$RESPONSE]"
+ * @param hc
+ * @return MHD result code
+ */
+MHD_RESULT
+AH_handler_truth_get (
+ struct MHD_Connection *connection,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ struct TM_HandlerContext *hc)
+{
+ struct GetContext *gc = hc->ctx;
+ struct GNUNET_HashCode challenge_response;
+ void *encrypted_truth;
+ size_t encrypted_truth_size;
+ void *decrypted_truth;
+ size_t decrypted_truth_size;
+ char *truth_mime = NULL;
+ bool is_question;
+
+ if (NULL == gc)
+ {
+ /* Fresh request, do initial setup */
+ gc = GNUNET_new (struct GetContext);
+ gc->hc = hc;
+ hc->ctx = gc;
+ gc->connection = connection;
+ gc->truth_uuid = *truth_uuid;
+ gc->hc->cc = &request_done;
+ {
+ const char *pay_id;
+
+ pay_id = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER);
+ if (NULL != pay_id)
+ {
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (
+ pay_id,
+ strlen (pay_id),
+ &gc->payment_identifier,
+ sizeof (struct ANASTASIS_PaymentSecretP)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER);
+ }
+ gc->payment_identifier_provided = true;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Client provided payment identifier `%s'\n",
+ pay_id);
+ }
+ }
+
+ {
+ /* check if header contains Truth-Decryption-Key */
+ const char *tdk;
+
+ tdk = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ ANASTASIS_HTTP_HEADER_TRUTH_DECRYPTION_KEY);
+ if (NULL == tdk)
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MISSING,
+ ANASTASIS_HTTP_HEADER_TRUTH_DECRYPTION_KEY);
+ }
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (
+ tdk,
+ strlen (tdk),
+ &gc->truth_key,
+ sizeof (struct ANASTASIS_CRYPTO_TruthKeyP)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ ANASTASIS_HTTP_HEADER_TRUTH_DECRYPTION_KEY);
+ }
+ }
+
+ {
+ const char *challenge_response_s;
+
+ challenge_response_s = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "response");
+ if ( (NULL != challenge_response_s) &&
+ (GNUNET_OK !=
+ GNUNET_CRYPTO_hash_from_string (challenge_response_s,
+ &challenge_response)) )
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "response");
+ }
+ gc->have_response = (NULL != challenge_response_s);
+ }
+
+ {
+ const char *long_poll_timeout_ms;
+
+ long_poll_timeout_ms = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "timeout_ms");
+ if (NULL != long_poll_timeout_ms)
+ {
+ unsigned int timeout;
+
+ if (1 != sscanf (long_poll_timeout_ms,
+ "%u",
+ &timeout))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "timeout_ms (must be non-negative number)");
+ }
+ gc->timeout
+ = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_MILLISECONDS,
+ timeout));
+ }
+ else
+ {
+ gc->timeout = GNUNET_TIME_relative_to_absolute (
+ GNUNET_TIME_UNIT_SECONDS);
+ }
+ }
+
+ } /* end of first-time initialization (if NULL == gc) */
+ else
+ {
+ if (NULL != gc->resp)
+ {
+ MHD_RESULT ret;
+
+ /* We generated a response asynchronously, queue that */
+ ret = MHD_queue_response (connection,
+ gc->response_code,
+ gc->resp);
+ GNUNET_break (MHD_YES == ret);
+ MHD_destroy_response (gc->resp);
+ gc->resp = NULL;
+ return ret;
+ }
+ if (NULL != gc->as)
+ {
+ /* Authorization process is "running", check what is going on */
+ GNUNET_assert (NULL != gc->authorization);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Continuing with running the authorization process\n");
+ return run_authorization_process (connection,
+ gc);
+
+ }
+ /* We get here if the async check for payment said this request
+ was indeed paid! */
+ }
+
+ {
+ /* load encrypted truth from DB */
+ enum GNUNET_DB_QueryStatus qs;
+ char *method;
+
+ qs = db->get_escrow_challenge (db->cls,
+ &gc->truth_uuid,
+ &encrypted_truth,
+ &encrypted_truth_size,
+ &truth_mime,
+ &method);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (gc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get escrow challenge");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_ANASTASIS_TRUTH_UNKNOWN,
+ NULL);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ is_question = (0 == strcmp ("question",
+ method));
+ if (! is_question)
+ {
+ gc->authorization
+ = ANASTASIS_authorization_plugin_load (method,
+ AH_cfg,
+ &gc->challenge_cost);
+ if (NULL == gc->authorization)
+ {
+ MHD_RESULT ret;
+
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_METHOD_NO_LONGER_SUPPORTED,
+ method);
+ GNUNET_free (encrypted_truth);
+ GNUNET_free (truth_mime);
+ GNUNET_free (method);
+ return ret;
+ }
+ }
+ else
+ {
+ gc->challenge_cost = AH_question_cost;
+ }
+ GNUNET_free (method);
+ }
+
+ {
+ struct TALER_Amount zero_amount;
+
+ TALER_amount_set_zero (AH_currency,
+ &zero_amount);
+ if (0 != TALER_amount_cmp (&gc->challenge_cost,
+ &zero_amount))
+ {
+ /* Check database to see if the transaction is paid for */
+ enum GNUNET_DB_QueryStatus qs;
+ bool paid;
+
+ if (! gc->payment_identifier_provided)
+ {
+ GNUNET_free (truth_mime);
+ GNUNET_free (encrypted_truth);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Beginning payment, client did not provide payment identifier\n");
+ return begin_payment (gc);
+ }
+ qs = db->check_challenge_payment (db->cls,
+ &gc->payment_identifier,
+ &gc->truth_uuid,
+ &paid);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ GNUNET_free (truth_mime);
+ GNUNET_free (encrypted_truth);
+ return TALER_MHD_reply_with_error (gc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "check challenge payment");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* Create fresh payment identifier (cannot trust client) */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Client-provided payment identifier is unknown.\n");
+ GNUNET_free (truth_mime);
+ GNUNET_free (encrypted_truth);
+ return begin_payment (gc);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ if (! paid)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Payment identifier known. Checking payment with client's payment identifier\n");
+ GNUNET_free (truth_mime);
+ GNUNET_free (encrypted_truth);
+ return begin_payment (gc);
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Payment confirmed\n");
+ break;
+ }
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Request is free of charge\n");
+ }
+ }
+
+ /* We've been paid, now validate response */
+ {
+ /* decrypt encrypted_truth */
+ ANASTASIS_CRYPTO_truth_decrypt (&gc->truth_key,
+ encrypted_truth,
+ encrypted_truth_size,
+ &decrypted_truth,
+ &decrypted_truth_size);
+ GNUNET_free (encrypted_truth);
+ }
+ if (NULL == decrypted_truth)
+ {
+ GNUNET_free (truth_mime);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_EXPECTATION_FAILED,
+ TALER_EC_ANASTASIS_TRUTH_DECRYPTION_FAILED,
+ NULL);
+ }
+
+ /* Special case for secure question: we do not generate a numeric challenge,
+ but check that the hash matches */
+ if (is_question)
+ {
+ if (! gc->have_response)
+ {
+ GNUNET_free (decrypted_truth);
+ GNUNET_free (truth_mime);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED,
+ NULL);
+ }
+
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Absolute rt;
+ uint64_t code;
+ enum ANASTASIS_DB_CodeStatus cs;
+ struct GNUNET_HashCode hc;
+
+ rt = GNUNET_TIME_UNIT_FOREVER_ABS;
+ qs = db->create_challenge_code (db->cls,
+ &gc->truth_uuid,
+ MAX_QUESTION_FREQ,
+ GNUNET_TIME_UNIT_HOURS,
+ INITIAL_RETRY_COUNTER,
+ &rt,
+ &code);
+ if (0 > qs)
+ {
+ GNUNET_break (0 < qs);
+ GNUNET_free (decrypted_truth);
+ GNUNET_free (truth_mime);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "create_challenge_code (for rate limiting)");
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ GNUNET_free (decrypted_truth);
+ GNUNET_free (truth_mime);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_TOO_MANY_REQUESTS,
+ TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED,
+ NULL);
+ }
+ /* decrement trial counter */
+ ANASTASIS_hash_answer (code + 1, /* always use wrong answer */
+ &hc);
+ cs = db->verify_challenge_code (db->cls,
+ &gc->truth_uuid,
+ &hc);
+ switch (cs)
+ {
+ case ANASTASIS_DB_CODE_STATUS_CHALLENGE_CODE_MISMATCH:
+ /* good, what we wanted */
+ break;
+ case ANASTASIS_DB_CODE_STATUS_HARD_ERROR:
+ case ANASTASIS_DB_CODE_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (gc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "verify_challenge_code");
+ case ANASTASIS_DB_CODE_STATUS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_TOO_MANY_REQUESTS,
+ TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED,
+ NULL);
+ case ANASTASIS_DB_CODE_STATUS_VALID_CODE_STORED:
+ /* this should be impossible, we used code+1 */
+ GNUNET_assert (0);
+ }
+ }
+ if ( (decrypted_truth_size != sizeof (challenge_response)) ||
+ (0 != memcmp (&challenge_response,
+ decrypted_truth,
+ decrypted_truth_size)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Wrong answer provided to secure question had %u bytes, wanted %u\n",
+ (unsigned int) decrypted_truth_size,
+ (unsigned int) sizeof (challenge_response));
+ GNUNET_free (decrypted_truth);
+ GNUNET_free (truth_mime);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_ANASTASIS_TRUTH_CHALLENGE_FAILED,
+ NULL);
+ }
+ GNUNET_free (decrypted_truth);
+ GNUNET_free (truth_mime);
+ return return_key_share (&gc->truth_uuid,
+ connection);
+ }
+
+ /* Not security question, check for answer in DB */
+ if (gc->have_response)
+ {
+ enum ANASTASIS_DB_CodeStatus cs;
+
+ GNUNET_free (decrypted_truth);
+ GNUNET_free (truth_mime);
+ cs = db->verify_challenge_code (db->cls,
+ &gc->truth_uuid,
+ &challenge_response);
+ switch (cs)
+ {
+ case ANASTASIS_DB_CODE_STATUS_CHALLENGE_CODE_MISMATCH:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Provided response does not match our stored challenge\n");
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_ANASTASIS_TRUTH_CHALLENGE_FAILED,
+ NULL);
+ case ANASTASIS_DB_CODE_STATUS_HARD_ERROR:
+ case ANASTASIS_DB_CODE_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (gc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "verify_challenge_code");
+ case ANASTASIS_DB_CODE_STATUS_NO_RESULTS:
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "No challenge known (challenge is invalidated after %u requests)\n",
+ INITIAL_RETRY_COUNTER);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_TOO_MANY_REQUESTS,
+ TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED,
+ NULL);
+ case ANASTASIS_DB_CODE_STATUS_VALID_CODE_STORED:
+ return return_key_share (&gc->truth_uuid,
+ connection);
+ }
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+
+ /* Not security question and no answer: use plugin to check if
+ decrypted truth is a valid challenge! */
+ {
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = gc->authorization->validate (gc->authorization->cls,
+ connection,
+ truth_mime,
+ decrypted_truth,
+ decrypted_truth_size);
+ GNUNET_free (truth_mime);
+ switch (ret)
+ {
+ case GNUNET_OK:
+ /* data valid, continued below */
+ break;
+ case GNUNET_NO:
+ /* data invalid, reply was queued */
+ GNUNET_free (decrypted_truth);
+ return MHD_YES;
+ case GNUNET_SYSERR:
+ /* data invalid, reply was NOT queued */
+ GNUNET_free (decrypted_truth);
+ return MHD_NO;
+ }
+ }
+
+ /* Setup challenge and begin authorization process */
+ {
+ struct GNUNET_TIME_Absolute transmission_date;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = db->create_challenge_code (db->cls,
+ &gc->truth_uuid,
+ gc->authorization->code_rotation_period,
+ gc->authorization->code_validity_period,
+ INITIAL_RETRY_COUNTER,
+ &transmission_date,
+ &gc->code);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ GNUNET_free (decrypted_truth);
+ return TALER_MHD_reply_with_error (gc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "store_challenge_code");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* 0 == retry_counter of existing challenge => rate limit exceeded */
+ GNUNET_free (decrypted_truth);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_TOO_MANY_REQUESTS,
+ TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED,
+ NULL);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* challenge code was stored successfully*/
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Created fresh challenge\n");
+ break;
+ }
+
+ if (GNUNET_TIME_absolute_get_duration (transmission_date).rel_value_us <
+ gc->authorization->code_retransmission_frequency.rel_value_us)
+ {
+ /* Too early for a retransmission! */
+ GNUNET_free (decrypted_truth);
+ return TALER_MHD_reply_with_error (gc->connection,
+ MHD_HTTP_ALREADY_REPORTED,
+ TALER_EC_ANASTASIS_TRUTH_CHALLENGE_ACTIVE,
+ NULL);
+ }
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Beginning authorization process\n");
+ gc->as = gc->authorization->start (gc->authorization->cls,
+ &AH_trigger_daemon,
+ NULL,
+ &gc->truth_uuid,
+ gc->code,
+ decrypted_truth,
+ decrypted_truth_size);
+ GNUNET_free (decrypted_truth);
+ if (NULL == gc->as)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (gc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_START_FAILED,
+ NULL);
+ }
+ GNUNET_assert (! gc->in_list);
+ gc->in_list = true;
+ GNUNET_CONTAINER_DLL_insert (gc_head,
+ gc_tail,
+ gc);
+ return run_authorization_process (connection,
+ gc);
+}
diff --git a/src/backend/anastasis-httpd_truth.h b/src/backend/anastasis-httpd_truth.h
new file mode 100644
index 0000000..7a1b95f
--- /dev/null
+++ b/src/backend/anastasis-httpd_truth.h
@@ -0,0 +1,75 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015, 2016, 2021 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file anastasis-httpd_truth.h
+ * @brief functions to handle incoming requests on /truth
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ * @author Christian Grothoff
+ */
+#ifndef ANASTASIS_HTTPD_TRUTH_H
+#define ANASTASIS_HTTPD_TRUTH_H
+#include <microhttpd.h>
+
+
+/**
+ * Prepare all active GET truth requests for system shutdown.
+ */
+void
+AH_truth_shutdown (void);
+
+
+/**
+ * Prepare all active POST truth requests for system shutdown.
+ */
+void
+AH_truth_upload_shutdown (void);
+
+
+/**
+ * Handle a GET to /truth/$UUID
+ *
+ * @param connection the MHD connection to handle
+ * @param truth_uuid the truth UUID
+ * @param con_cls
+ * @return MHD result code
+ */
+MHD_RESULT
+AH_handler_truth_get (
+ struct MHD_Connection *connection,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ struct TM_HandlerContext *hc);
+
+
+/**
+ * Handle a POST to /truth/$UUID.
+ *
+ * @param connection the MHD connection to handle
+ * @param con_cls the connection's closure
+ * @param truth_uuid the truth UUID
+ * @param truth_data truth data
+ * @param truth_data_size number of bytes (left) in @a truth_data
+ * @return MHD result code
+ */
+int
+AH_handler_truth_post (
+ struct MHD_Connection *connection,
+ struct TM_HandlerContext *hc,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ const char *truth_data,
+ size_t *truth_data_size);
+
+#endif
diff --git a/src/backend/anastasis-httpd_truth_upload.c b/src/backend/anastasis-httpd_truth_upload.c
new file mode 100644
index 0000000..9767087
--- /dev/null
+++ b/src/backend/anastasis-httpd_truth_upload.c
@@ -0,0 +1,855 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2019, 2021 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file anastasis-httpd_truth_upload.c
+ * @brief functions to handle incoming POST request on /truth
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "anastasis-httpd.h"
+#include "anastasis_service.h"
+#include "anastasis-httpd_truth.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_rest_lib.h>
+#include <taler/taler_json_lib.h>
+#include <taler/taler_merchant_service.h>
+#include <taler/taler_signatures.h>
+#include "anastasis_authorization_lib.h"
+
+
+/**
+ * Information we track per truth upload.
+ */
+struct TruthUploadContext
+{
+
+ /**
+ * UUID of the truth object we are processing.
+ */
+ struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid;
+
+ /**
+ * Kept in DLL for shutdown handling while suspended.
+ */
+ struct TruthUploadContext *next;
+
+ /**
+ * Kept in DLL for shutdown handling while suspended.
+ */
+ struct TruthUploadContext *prev;
+
+ /**
+ * Used while we are awaiting proposal creation.
+ */
+ struct TALER_MERCHANT_PostOrdersHandle *po;
+
+ /**
+ * Used while we are waiting payment.
+ */
+ struct TALER_MERCHANT_OrderMerchantGetHandle *cpo;
+
+ /**
+ * Post parser context.
+ */
+ void *post_ctx;
+
+ /**
+ * Handle to the client request.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * Incoming JSON, NULL if not yet available.
+ */
+ json_t *json;
+
+ /**
+ * HTTP response code to use on resume, if non-NULL.
+ */
+ struct MHD_Response *resp;
+
+ /**
+ * When should this request time out?
+ */
+ struct GNUNET_TIME_Absolute timeout;
+
+ /**
+ * Fee that is to be paid for this upload.
+ */
+ struct TALER_Amount upload_fee;
+
+ /**
+ * HTTP response code to use on resume, if resp is set.
+ */
+ unsigned int response_code;
+
+ /**
+ * For how many years must the customer still pay?
+ */
+ unsigned int years_to_pay;
+
+};
+
+
+/**
+ * Head of linked list over all truth upload processes
+ */
+static struct TruthUploadContext *tuc_head;
+
+/**
+ * Tail of linked list over all truth upload processes
+ */
+static struct TruthUploadContext *tuc_tail;
+
+
+void
+AH_truth_upload_shutdown (void)
+{
+ struct TruthUploadContext *tuc;
+
+ while (NULL != (tuc = tuc_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (tuc_head,
+ tuc_tail,
+ tuc);
+ if (NULL != tuc->cpo)
+ {
+ TALER_MERCHANT_merchant_order_get_cancel (tuc->cpo);
+ tuc->cpo = NULL;
+ }
+ if (NULL != tuc->po)
+ {
+ TALER_MERCHANT_orders_post_cancel (tuc->po);
+ tuc->po = NULL;
+ }
+ MHD_resume_connection (tuc->connection);
+ }
+}
+
+
+/**
+ * Function called to clean up a `struct TruthUploadContext`.
+ *
+ * @param hc general handler context
+ */
+static void
+cleanup_truth_post (struct TM_HandlerContext *hc)
+{
+ struct TruthUploadContext *tuc = hc->ctx;
+
+ TALER_MHD_parse_post_cleanup_callback (tuc->post_ctx);
+ if (NULL != tuc->po)
+ TALER_MERCHANT_orders_post_cancel (tuc->po);
+ if (NULL != tuc->cpo)
+ TALER_MERCHANT_merchant_order_get_cancel (tuc->cpo);
+ if (NULL != tuc->resp)
+ MHD_destroy_response (tuc->resp);
+ if (NULL != tuc->json)
+ json_decref (tuc->json);
+ GNUNET_free (tuc);
+}
+
+
+/**
+ * Transmit a payment request for @a tuc.
+ *
+ * @param tuc upload context to generate payment request for
+ */
+static void
+make_payment_request (struct TruthUploadContext *tuc)
+{
+ struct MHD_Response *resp;
+
+ /* request payment via Taler */
+ resp = MHD_create_response_from_buffer (0,
+ NULL,
+ MHD_RESPMEM_PERSISTENT);
+ GNUNET_assert (NULL != resp);
+ TALER_MHD_add_global_headers (resp);
+ {
+ char *hdr;
+ const char *pfx;
+ const char *hn;
+
+ if (0 == strncasecmp ("https://",
+ AH_backend_url,
+ strlen ("https://")))
+ {
+ pfx = "taler://";
+ hn = &AH_backend_url[strlen ("https://")];
+ }
+ else if (0 == strncasecmp ("http://",
+ AH_backend_url,
+ strlen ("http://")))
+ {
+ pfx = "taler+http://";
+ hn = &AH_backend_url[strlen ("http://")];
+ }
+ else
+ {
+ /* This invariant holds as per check in anastasis-httpd.c */
+ GNUNET_assert (0);
+ }
+ /* This invariant holds as per check in anastasis-httpd.c */
+ GNUNET_assert (0 != strlen (hn));
+ {
+ char *order_id;
+
+ order_id = GNUNET_STRINGS_data_to_string_alloc (
+ &tuc->truth_uuid,
+ sizeof (tuc->truth_uuid));
+ GNUNET_asprintf (&hdr,
+ "%spay/%s%s/",
+ pfx,
+ hn,
+ order_id);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Returning %u %s\n",
+ MHD_HTTP_PAYMENT_REQUIRED,
+ order_id);
+ GNUNET_free (order_id);
+ }
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ ANASTASIS_HTTP_HEADER_TALER,
+ hdr));
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "TRUTH payment request made: %s\n",
+ hdr);
+ GNUNET_free (hdr);
+ }
+ tuc->resp = resp;
+ tuc->response_code = MHD_HTTP_PAYMENT_REQUIRED;
+}
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * POST /private/orders request to a merchant.
+ *
+ * @param cls our `struct TruthUploadContext`
+ * @param por response details
+ */
+static void
+proposal_cb (void *cls,
+ const struct TALER_MERCHANT_PostOrdersReply *por)
+{
+ struct TruthUploadContext *tuc = cls;
+
+ tuc->po = NULL;
+ GNUNET_CONTAINER_DLL_remove (tuc_head,
+ tuc_tail,
+ tuc);
+ MHD_resume_connection (tuc->connection);
+ AH_trigger_daemon (NULL);
+ if (MHD_HTTP_OK != por->hr.http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Backend returned status %u/%d\n",
+ por->hr.http_status,
+ (int) por->hr.ec);
+ GNUNET_break (0);
+ tuc->resp = TALER_MHD_make_json_pack (
+ "{s:I, s:s, s:I, s:I, s:O?}",
+ "code",
+ (json_int_t) TALER_EC_ANASTASIS_GENERIC_ORDER_CREATE_BACKEND_ERROR,
+ "hint",
+ "Failed to setup order with merchant backend",
+ "backend-ec",
+ (json_int_t) por->hr.ec,
+ "backend-http-status",
+ (json_int_t) por->hr.http_status,
+ "backend-reply",
+ por->hr.reply);
+ GNUNET_assert (NULL != tuc->resp);
+ tuc->response_code = MHD_HTTP_BAD_GATEWAY;
+ return;
+ }
+ make_payment_request (tuc);
+}
+
+
+/**
+ * Callback to process a GET /check-payment request
+ *
+ * @param cls our `struct PolicyUploadContext`
+ * @param hr HTTP response details
+ * @param osr order status
+ */
+static void
+check_payment_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr,
+ const struct TALER_MERCHANT_OrderStatusResponse *osr)
+{
+ struct TruthUploadContext *tuc = cls;
+
+ tuc->cpo = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Checking backend order status returned %u\n",
+ hr->http_status);
+ switch (hr->http_status)
+ {
+ case 0:
+ /* Likely timeout, complain! */
+ tuc->response_code = MHD_HTTP_GATEWAY_TIMEOUT;
+ tuc->resp = TALER_MHD_make_error (
+ TALER_EC_ANASTASIS_GENERIC_BACKEND_TIMEOUT,
+ NULL);
+ break;
+ case MHD_HTTP_OK:
+ switch (osr->status)
+ {
+ case TALER_MERCHANT_OSC_PAID:
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ unsigned int years;
+ struct GNUNET_TIME_Relative paid_until;
+ const json_t *contract;
+ struct TALER_Amount amount;
+ struct GNUNET_JSON_Specification cspec[] = {
+ TALER_JSON_spec_amount ("amount",
+ AH_currency,
+ &amount),
+ GNUNET_JSON_spec_end ()
+ };
+
+ contract = osr->details.paid.contract_terms;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (contract,
+ cspec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ tuc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ tuc->resp = TALER_MHD_make_error (
+ TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
+ "contract terms in database are malformed");
+ break;
+ }
+ years = TALER_amount_divide2 (&amount,
+ &AH_truth_upload_fee);
+ paid_until = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_YEARS,
+ years);
+ /* add 1 week grace period, otherwise if a user
+ wants to pay for 1 year, the first seconds
+ would have passed between making the payment
+ and our subsequent check if +1 year was
+ paid... So we actually say 1 year = 52 weeks
+ on the server, while the client calculates
+ with 365 days. */
+ paid_until = GNUNET_TIME_relative_add (paid_until,
+ GNUNET_TIME_UNIT_WEEKS);
+ qs = db->record_truth_upload_payment (
+ db->cls,
+ &tuc->truth_uuid,
+ &osr->details.paid.deposit_total,
+ paid_until);
+ if (qs <= 0)
+ {
+ GNUNET_break (0);
+ tuc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ tuc->resp = TALER_MHD_make_error (
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "record_truth_upload_payment");
+ break;
+ }
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Payment confirmed, resuming upload\n");
+ break;
+ case TALER_MERCHANT_OSC_UNPAID:
+ case TALER_MERCHANT_OSC_CLAIMED:
+ make_payment_request (tuc);
+ break;
+ }
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ /* Configuration issue, complain! */
+ tuc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ tuc->resp = TALER_MHD_make_json_pack (
+ "{s:I, s:s, s:I, s:I, s:O?}",
+ "code",
+ (json_int_t) TALER_EC_ANASTASIS_GENERIC_PAYMENT_CHECK_UNAUTHORIZED,
+ "hint",
+ TALER_ErrorCode_get_hint (
+ TALER_EC_ANASTASIS_GENERIC_PAYMENT_CHECK_UNAUTHORIZED),
+ "backend-ec",
+ (json_int_t) hr->ec,
+ "backend-http-status",
+ (json_int_t) hr->http_status,
+ "backend-reply",
+ hr->reply);
+ GNUNET_assert (NULL != tuc->resp);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Setup fresh order */
+ {
+ char *order_id;
+ json_t *order;
+
+ order_id = GNUNET_STRINGS_data_to_string_alloc (
+ &tuc->truth_uuid,
+ sizeof(tuc->truth_uuid));
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%u, setting up fresh order %s\n",
+ MHD_HTTP_NOT_FOUND,
+ order_id);
+ order = json_pack ("{s:o, s:s, s:[{s:s,s:I,s:s}], s:s}",
+ "amount",
+ TALER_JSON_from_amount (&tuc->upload_fee),
+ "summary",
+ "Anastasis challenge storage fee",
+ "products",
+ "description", "challenge storage fee",
+ "quantity", (json_int_t) tuc->years_to_pay,
+ "unit", "years",
+
+ "order_id",
+ order_id);
+ GNUNET_free (order_id);
+ tuc->po = TALER_MERCHANT_orders_post2 (AH_ctx,
+ AH_backend_url,
+ order,
+ GNUNET_TIME_UNIT_ZERO,
+ NULL, /* no payment target */
+ 0,
+ NULL, /* no inventory products */
+ 0,
+ NULL, /* no uuids */
+ false, /* do NOT require claim token */
+ &proposal_cb,
+ tuc);
+ AH_trigger_curl ();
+ json_decref (order);
+ return;
+ }
+ default:
+ /* Unexpected backend response */
+ tuc->response_code = MHD_HTTP_BAD_GATEWAY;
+ tuc->resp = TALER_MHD_make_json_pack (
+ "{s:I, s:s, s:I, s:I, s:O?}",
+ "code",
+ (json_int_t) TALER_EC_ANASTASIS_GENERIC_BACKEND_ERROR,
+ "hint",
+ TALER_ErrorCode_get_hint (TALER_EC_ANASTASIS_GENERIC_BACKEND_ERROR),
+ "backend-ec",
+ (json_int_t) hr->ec,
+ "backend-http-status",
+ (json_int_t) hr->http_status,
+ "backend-reply",
+ hr->reply);
+ break;
+ }
+ GNUNET_CONTAINER_DLL_remove (tuc_head,
+ tuc_tail,
+ tuc);
+ MHD_resume_connection (tuc->connection);
+ AH_trigger_daemon (NULL);
+}
+
+
+/**
+ * Helper function used to ask our backend to begin processing a
+ * payment for the truth upload. May perform asynchronous operations
+ * by suspending the connection if required.
+ *
+ * @param tuc context to begin payment for.
+ * @return MHD status code
+ */
+static MHD_RESULT
+begin_payment (struct TruthUploadContext *tuc)
+{
+ char *order_id;
+ struct GNUNET_TIME_Relative timeout;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Checking backend order status...\n");
+ timeout = GNUNET_TIME_absolute_get_remaining (tuc->timeout);
+ order_id = GNUNET_STRINGS_data_to_string_alloc (
+ &tuc->truth_uuid,
+ sizeof (tuc->truth_uuid));
+ tuc->cpo = TALER_MERCHANT_merchant_order_get (AH_ctx,
+ AH_backend_url,
+ order_id,
+ NULL /* our payments are NOT session-bound */,
+ false,
+ timeout,
+ &check_payment_cb,
+ tuc);
+ GNUNET_free (order_id);
+ if (NULL == tuc->cpo)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (tuc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_GENERIC_PAYMENT_CHECK_START_FAILED,
+ "Could not check order status");
+ }
+ GNUNET_CONTAINER_DLL_insert (tuc_head,
+ tuc_tail,
+ tuc);
+ MHD_suspend_connection (tuc->connection);
+ return MHD_YES;
+}
+
+
+int
+AH_handler_truth_post (
+ struct MHD_Connection *connection,
+ struct TM_HandlerContext *hc,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ const char *truth_data,
+ size_t *truth_data_size)
+{
+ struct TruthUploadContext *tuc = hc->ctx;
+ MHD_RESULT ret;
+ int res;
+ struct ANASTASIS_CRYPTO_EncryptedKeyShareP keyshare_data;
+ void *encrypted_truth;
+ size_t encrypted_truth_size;
+ const char *truth_mime;
+ const char *type;
+ enum GNUNET_DB_QueryStatus qs;
+ uint32_t storage_years;
+ struct GNUNET_TIME_Absolute paid_until;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("keyshare_data",
+ &keyshare_data),
+ GNUNET_JSON_spec_string ("type",
+ &type),
+ GNUNET_JSON_spec_varsize ("encrypted_truth",
+ &encrypted_truth,
+ &encrypted_truth_size),
+ GNUNET_JSON_spec_string ("truth_mime",
+ &truth_mime),
+ GNUNET_JSON_spec_uint32 ("storage_duration_years",
+ &storage_years),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (NULL == tuc)
+ {
+ tuc = GNUNET_new (struct TruthUploadContext);
+ tuc->connection = connection;
+ tuc->truth_uuid = *truth_uuid;
+ hc->ctx = tuc;
+ hc->cc = &cleanup_truth_post;
+
+ /* check for excessive upload */
+ {
+ const char *lens;
+ unsigned long len;
+ char dummy;
+
+ lens = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_CONTENT_LENGTH);
+ if ( (NULL == lens) ||
+ (1 != sscanf (lens,
+ "%lu%c",
+ &len,
+ &dummy)) )
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ (NULL == lens)
+ ? TALER_EC_ANASTASIS_GENERIC_MISSING_CONTENT_LENGTH
+ : TALER_EC_ANASTASIS_GENERIC_MALFORMED_CONTENT_LENGTH,
+ NULL);
+ }
+ if (len / 1024 / 1024 >= AH_upload_limit_mb)
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_PAYLOAD_TOO_LARGE,
+ TALER_EC_SYNC_MALFORMED_CONTENT_LENGTH,
+ "Content-length value not acceptable");
+ }
+ }
+
+ {
+ const char *long_poll_timeout_ms;
+
+ long_poll_timeout_ms = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "timeout_ms");
+ if (NULL != long_poll_timeout_ms)
+ {
+ unsigned int timeout;
+
+ if (1 != sscanf (long_poll_timeout_ms,
+ "%u",
+ &timeout))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "timeout_ms (must be non-negative number)");
+ }
+ tuc->timeout
+ = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_MILLISECONDS,
+ timeout));
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Long polling for %u ms enabled\n",
+ timeout);
+ }
+ else
+ {
+ tuc->timeout = GNUNET_TIME_relative_to_absolute (
+ GNUNET_TIME_UNIT_SECONDS);
+ }
+ }
+
+ } /* end 'if (NULL == tuc)' */
+
+ if (NULL != tuc->resp)
+ {
+ /* We generated a response asynchronously, queue that */
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Returning asynchronously generated response with HTTP status %u\n",
+ tuc->response_code);
+ ret = MHD_queue_response (connection,
+ tuc->response_code,
+ tuc->resp);
+ GNUNET_break (MHD_YES == ret);
+ MHD_destroy_response (tuc->resp);
+ tuc->resp = NULL;
+ return ret;
+ }
+
+ if (NULL == tuc->json)
+ {
+ res = TALER_MHD_parse_post_json (connection,
+ &tuc->post_ctx,
+ truth_data,
+ truth_data_size,
+ &tuc->json);
+ if (GNUNET_SYSERR == res)
+ {
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+ if ( (GNUNET_NO == res) ||
+ (NULL == tuc->json) )
+ return MHD_YES;
+ }
+ res = TALER_MHD_parse_json_data (connection,
+ tuc->json,
+ spec);
+ if (GNUNET_SYSERR == res)
+ {
+ GNUNET_break (0);
+ return MHD_NO; /* hard failure */
+ }
+ if (GNUNET_NO == res)
+ {
+ GNUNET_break_op (0);
+ return MHD_YES; /* failure */
+ }
+
+ /* check method is supported */
+ {
+ struct TALER_Amount dummy;
+
+ if ( (0 != strcmp ("question",
+ type)) &&
+ (NULL ==
+ ANASTASIS_authorization_plugin_load (type,
+ AH_cfg,
+ &dummy)) )
+ {
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_ANASTASIS_TRUTH_UPLOAD_METHOD_NOT_SUPPORTED,
+ type);
+ }
+ }
+
+ if (storage_years > ANASTASIS_MAX_YEARS_STORAGE)
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "storage_duration_years");
+ }
+ if (0 == storage_years)
+ storage_years = 1;
+
+ {
+ struct TALER_Amount zero_amount;
+
+ TALER_amount_set_zero (AH_currency,
+ &zero_amount);
+ if (0 != TALER_amount_cmp (&AH_truth_upload_fee,
+ &zero_amount))
+ {
+ struct GNUNET_TIME_Absolute desired_until;
+ enum GNUNET_DB_QueryStatus qs;
+
+ desired_until
+ = GNUNET_TIME_relative_to_absolute (
+ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_YEARS,
+ storage_years));
+ qs = db->check_truth_upload_paid (db->cls,
+ truth_uuid,
+ &paid_until);
+ if (qs < 0)
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL);
+ if ( (0 == qs) ||
+ (paid_until.abs_value_us < desired_until.abs_value_us) )
+ {
+ struct GNUNET_TIME_Absolute now;
+ struct GNUNET_TIME_Relative rem;
+
+ now = GNUNET_TIME_absolute_get ();
+ if (paid_until.abs_value_us < now.abs_value_us)
+ paid_until = now;
+ rem = GNUNET_TIME_absolute_get_difference (paid_until,
+ desired_until);
+ tuc->years_to_pay = rem.rel_value_us
+ / GNUNET_TIME_UNIT_YEARS.rel_value_us;
+ if (0 != (rem.rel_value_us % GNUNET_TIME_UNIT_YEARS.rel_value_us))
+ tuc->years_to_pay++;
+ if (0 >
+ TALER_amount_multiply (&tuc->upload_fee,
+ &AH_truth_upload_fee,
+ tuc->years_to_pay))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "storage_duration_years");
+ }
+ if ( (0 != tuc->upload_fee.fraction) ||
+ (0 != tuc->upload_fee.value) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Truth upload payment required (%d)!\n",
+ qs);
+ return begin_payment (tuc);
+ }
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "TRUTH paid until %s (%d)!\n",
+ GNUNET_STRINGS_relative_time_to_string (
+ GNUNET_TIME_absolute_get_remaining (
+ paid_until),
+ GNUNET_YES),
+ qs);
+ }
+ else
+ {
+ paid_until
+ = GNUNET_TIME_relative_to_absolute (
+ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_YEARS,
+ ANASTASIS_MAX_YEARS_STORAGE));
+ }
+ }
+
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Storing truth until %s!\n",
+ GNUNET_STRINGS_absolute_time_to_string (paid_until));
+ qs = db->store_truth (db->cls,
+ truth_uuid,
+ &keyshare_data,
+ truth_mime,
+ encrypted_truth,
+ encrypted_truth_size,
+ type,
+ GNUNET_TIME_absolute_get_remaining (paid_until));
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
+ "store_truth");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ {
+ void *xtruth;
+ size_t xtruth_size;
+ char *xtruth_mime;
+ char *xmethod;
+ bool ok = false;
+
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ db->get_escrow_challenge (db->cls,
+ truth_uuid,
+ &xtruth,
+ &xtruth_size,
+ &xtruth_mime,
+ &xmethod))
+ {
+ ok = ( (xtruth_size == encrypted_truth_size) &&
+ (0 == strcmp (xmethod,
+ type)) &&
+ (0 == strcmp (truth_mime,
+ xtruth_mime)) &&
+ (0 == memcmp (xtruth,
+ encrypted_truth,
+ xtruth_size)) );
+ GNUNET_free (encrypted_truth);
+ GNUNET_free (xtruth_mime);
+ GNUNET_free (xmethod);
+ }
+ if (! ok)
+ {
+ GNUNET_JSON_parse_free (spec);
+
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_ANASTASIS_TRUTH_UPLOAD_UUID_EXISTS,
+ NULL);
+ }
+ /* idempotency detected, intentional fall through! */
+ }
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ {
+ struct MHD_Response *resp;
+
+ GNUNET_JSON_parse_free (spec);
+ resp = MHD_create_response_from_buffer (0,
+ NULL,
+ MHD_RESPMEM_PERSISTENT);
+ TALER_MHD_add_global_headers (resp);
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_NO_CONTENT,
+ resp);
+ MHD_destroy_response (resp);
+ GNUNET_break (MHD_YES == ret);
+ return ret;
+ }
+ }
+ GNUNET_JSON_parse_free (spec);
+ GNUNET_break (0);
+ return MHD_NO;
+}
diff --git a/src/backend/anastasis.conf b/src/backend/anastasis.conf
new file mode 100644
index 0000000..ddc1a65
--- /dev/null
+++ b/src/backend/anastasis.conf
@@ -0,0 +1,77 @@
+# This file is in the public domain.
+
+# These are default/sample settings for a merchant backend.
+
+
+# General settings for the backend.
+[anastasis]
+
+# Use TCP or UNIX domain sockets?
+SERVE = tcp
+
+# Which HTTP port does the backend listen on? Only used if "SERVE" is 'tcp'.
+PORT = 9977
+
+# Which IP address should we bind to? i.e. 127.0.0.1 or ::1 for loopback.
+# Can also be given as a hostname. We will bind to the wildcard (dual-stack)
+# if left empty. Only used if "SERVE" is 'tcp'.
+# BIND_TO =
+
+
+# Which unix domain path should we bind to? Only used if "SERVE" is 'unix'.
+UNIXPATH = ${ANASTASIS_RUNTIME_DIR}/backend.http
+# What should be the file access permissions (see chmod) for "UNIXPATH"?
+UNIXPATH_MODE = 660
+
+# Which database backend do we use?
+DB = postgres
+
+# Annual fee for an account
+# ANNUAL_FEE = TESTKUDOS:0.1
+
+# Number of policy uploads included in one annual fee payment
+ANNUAL_POLICY_UPLÄOAD_LIMIT = 64
+
+# Insurance
+# INSURANCE = TESTKUDOS:1.0
+
+# Upload limit per backup, in megabytes
+UPLOAD_LIMIT_MB = 16
+
+# Authentication costs
+
+# Cost of authentication by question
+#QUESTION_COST = EUR:0
+
+# Cost of authentication by file (only for testing purposes)
+#FILE_COST = EUR:1
+
+# Cost of authentication by E-Mail
+#EMAIL_COST = EUR:0
+
+# Cost of authentication by SMS
+#SMS_COST = EUR:0
+
+# Cost of authentication by postal
+#POSTAL_COST = EUR:0
+
+# Cost of authentication by video
+#VIDEO_COST = EUR:0
+
+#SMS authentication command which is executed
+#SMSAUTH_COMMAND = some_sms_script.sh
+
+#E-Mail authentication command which is executed
+#EMAILAUTH_COMMAND = some_email_script.sh
+
+# Fulfillment URL of the ANASTASIS service itself.
+FULFILLMENT_URL = taler://fulfillment-success
+
+# Base URL of our payment backend
+# PAYMENT_BACKEND_URL = http://localhost:9976/
+
+# Server salt 16 Byte
+# SERVER_SALT = gUfO1KGOKYIFlFQg
+
+# Supported methods
+SUPPORTED_METHODS = question
diff --git a/src/cli/.gitignore b/src/cli/.gitignore
new file mode 100644
index 0000000..dbf01fa
--- /dev/null
+++ b/src/cli/.gitignore
@@ -0,0 +1,6 @@
+*.log
+anastasis-reducer
+test_reducer_home
+*.trs
+taler-bank.err
+wallet.err
diff --git a/src/cli/Makefile.am b/src/cli/Makefile.am
new file mode 100644
index 0000000..6b8bf23
--- /dev/null
+++ b/src/cli/Makefile.am
@@ -0,0 +1,56 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include
+
+bin_PROGRAMS = \
+ anastasis-reducer
+
+if USE_COVERAGE
+ AM_CFLAGS = --coverage -O0
+ XLIB = -lgcov
+endif
+
+check_SCRIPTS = \
+ test_anastasis_reducer_initialize_state.sh \
+ test_anastasis_reducer_select_continent.sh \
+ test_anastasis_reducer_select_country.sh \
+ test_anastasis_reducer_backup_enter_user_attributes.sh \
+ test_anastasis_reducer_add_authentication.sh \
+ test_anastasis_reducer_done_authentication.sh \
+ test_anastasis_reducer_done_policy_review.sh \
+ test_anastasis_reducer_enter_secret.sh \
+ test_anastasis_reducer_recovery_enter_user_attributes.sh
+
+
+AM_TESTS_ENVIRONMENT=export ANASTASIS_PREFIX=$${ANASTASIS_PREFIX:-@libdir@};export PATH=$${ANASTASIS_PREFIX:-@prefix@}/bin:$$PATH;unset XDG_DATA_HOME;unset XDG_CONFIG_HOME;
+
+TESTS = \
+ $(check_SCRIPTS)
+
+EXTRA_DIST = \
+ $(check_SCRIPTS) \
+ test_reducer.conf \
+ test_anastasis_reducer_1.conf \
+ test_anastasis_reducer_2.conf \
+ test_anastasis_reducer_3.conf \
+ test_anastasis_reducer_4.conf \
+ resources/00-backup.json \
+ resources/01-backup.json \
+ resources/02-backup.json \
+ resources/03-backup.json \
+ resources/04-backup.json \
+ resources/00-recovery.json \
+ resources/01-recovery.json \
+ resources/02-recovery.json
+
+anastasis_reducer_SOURCES = \
+ anastasis-cli-redux.c
+anastasis_reducer_LDADD = \
+ $(top_builddir)/src/util/libanastasisutil.la \
+ $(top_builddir)/src/reducer/libanastasisredux.la \
+ -ltalerjson \
+ -ltalerutil \
+ -lgnunetjson \
+ -lgnunetcurl \
+ -lgnunetutil \
+ -ljansson \
+ $(XLIB)
diff --git a/src/cli/anastasis-cli-redux.c b/src/cli/anastasis-cli-redux.c
new file mode 100644
index 0000000..7b533c2
--- /dev/null
+++ b/src/cli/anastasis-cli-redux.c
@@ -0,0 +1,366 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020,2021 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file cli/anastasis-cli-redux.c
+ * @brief command line tool for our reducer
+ * @author Christian Grothoff
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ */
+
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "anastasis_redux.h"
+#include <taler/taler_util.h>
+#include <taler/taler_error_codes.h>
+#include <taler/taler_json_lib.h>
+#include "anastasis_util_lib.h"
+
+/**
+ * Closure for #GNUNET_CURL_gnunet_scheduler_reschedule().
+ */
+static struct GNUNET_CURL_RescheduleContext *rc;
+
+/**
+ * Curl context for communication with anastasis backend
+ */
+static struct GNUNET_CURL_Context *ctx;
+
+/**
+ * -b option given.
+ */
+static int b_flag;
+
+/**
+ * -r option given.
+ */
+static int r_flag;
+
+/**
+ * Input to -a option given.
+ */
+static char *input;
+
+/**
+ * Output filename, if given.
+ */
+static char *output_filename;
+
+/**
+ * JSON containing previous state
+ */
+static json_t *prev_state;
+
+/**
+ * JSON containing arguments for action
+ */
+static json_t *arguments;
+
+/**
+ * Handle to an ongoing action.
+ */
+static struct ANASTASIS_ReduxAction *ra;
+
+/**
+ * Return value from main.
+ */
+static int global_ret;
+
+
+/**
+ * Persist a json state, report errors.
+ *
+ * @param state to persist
+ * @param filename where to write the state to, NULL for stdout
+ */
+static void
+persist_new_state (json_t *state,
+ const char *filename)
+{
+ if (NULL != filename)
+ {
+ if (0 !=
+ json_dump_file (state,
+ filename,
+ JSON_COMPACT))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not dump state to `%s'\n",
+ filename);
+ return;
+ }
+ return;
+ }
+ {
+ char *state_str = json_dumps (state,
+ JSON_COMPACT);
+ if (-1 >=
+ fprintf (stdout,
+ "%s",
+ state_str))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not dump state to stdout\n");
+ GNUNET_free (state_str);
+ return;
+ }
+ GNUNET_free (state_str);
+ }
+}
+
+
+/**
+ * Function called with the results of #ANASTASIS_backup_action
+ * or #ANASTASIS_recovery_action.
+ *
+ * @param cls closure
+ * @param error_code Error code
+ * @param new_state new state as result
+ */
+static void
+action_cb (void *cls,
+ enum TALER_ErrorCode error_code,
+ json_t *result_state)
+{
+ (void) cls;
+ ra = NULL;
+ if (NULL != result_state)
+ persist_new_state (result_state,
+ output_filename);
+ if (TALER_EC_NONE != error_code)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Redux failed with error %d: %s\n",
+ error_code,
+ TALER_ErrorCode_get_hint (error_code));
+ }
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = (TALER_EC_NONE != error_code) ? 1 : 0;
+}
+
+
+/**
+ * @brief Shutdown the application.
+ *
+ * @param cls closure
+ */
+static void
+shutdown_task (void *cls)
+{
+ (void) cls;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Shutdown initiated\n");
+ if (NULL != ra)
+ {
+ ANASTASIS_redux_action_cancel (ra);
+ ra = NULL;
+ }
+ ANASTASIS_redux_done ();
+ if (NULL != ctx)
+ {
+ GNUNET_CURL_fini (ctx);
+ ctx = NULL;
+ }
+ if (NULL != rc)
+ {
+ GNUNET_CURL_gnunet_rc_destroy (rc);
+ rc = NULL;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Shutdown complete\n");
+}
+
+
+/**
+ * @brief Start the application
+ *
+ * @param cls closure
+ * @param args arguments left
+ * @param cfgfile config file name
+ * @param cfg handle for the configuration file
+ */
+static void
+run (void *cls,
+ char *const *args,
+ const char *cfgfile,
+ const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ (void) cls;
+ json_error_t error;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Starting anastasis-reducer\n");
+ GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
+ NULL);
+ if (b_flag && r_flag)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "We cannot start backup and recovery at the same time!\n");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (r_flag)
+ {
+ json_t *init_state;
+
+ init_state = ANASTASIS_recovery_start (cfg);
+ if (NULL == init_state)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "Failed to create an initial recovery state!\n");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ persist_new_state (init_state,
+ args[0]);
+ json_decref (init_state);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (b_flag)
+ {
+ json_t *init_state;
+
+ init_state = ANASTASIS_backup_start (cfg);
+ if (NULL == init_state)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "Failed to create an initial backup state!\n");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ persist_new_state (init_state,
+ args[0]);
+ json_decref (init_state);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+
+ /* action processing */
+ {
+ const char *action = args[0];
+
+ if (NULL == action)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "You must specify an action as the first argument (or `-b' or `-r')\n");
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "Example: anastasis-reducer back\n");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ args++;
+ if (NULL != input)
+ {
+ arguments = json_loads (input,
+ JSON_DECODE_ANY,
+ &error);
+ if (NULL == arguments)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "Failed to parse arguments on line %u:%u: %s!\n",
+ error.line,
+ error.column,
+ error.text);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ }
+ if (NULL != args[0])
+ {
+ prev_state = json_load_file (args[0],
+ JSON_DECODE_ANY,
+ &error);
+ args++;
+ }
+ else
+ {
+ prev_state = json_loadf (stdin,
+ JSON_DECODE_ANY,
+ &error);
+ }
+ if (NULL == prev_state)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "Failed to parse initial state on line %u:%u: %s!\n",
+ error.line,
+ error.column,
+ error.text);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ output_filename = args[0];
+ /* initialize HTTP client event loop */
+ ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
+ &rc);
+ rc = GNUNET_CURL_gnunet_rc_create (ctx);
+ ANASTASIS_redux_init (ctx);
+ ra = ANASTASIS_redux_action (prev_state,
+ action,
+ arguments,
+ &action_cb,
+ cls);
+ }
+}
+
+
+int
+main (int argc,
+ char *const *argv)
+{
+ /* the available command line options */
+ struct GNUNET_GETOPT_CommandLineOption options[] = {
+ GNUNET_GETOPT_option_flag ('b',
+ "backup",
+ "use reducer to handle states for backup process",
+ &b_flag),
+ GNUNET_GETOPT_option_flag ('r',
+ "restore",
+ "use reducer to handle states for restore process",
+ &r_flag),
+ GNUNET_GETOPT_option_string ('a',
+ "arguments",
+ "JSON",
+ "pass a JSON string containing arguments to reducer",
+ &input),
+
+ GNUNET_GETOPT_OPTION_END
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ /* FIRST get the libtalerutil initialization out
+ of the way. Then throw that one away, and force
+ the SYNC defaults to be used! */
+ (void) TALER_project_data_default ();
+ GNUNET_OS_init (ANASTASIS_project_data_default ());
+ ret = GNUNET_PROGRAM_run (argc,
+ argv,
+ "anastasis-reducer",
+ "This is an application for using Anastasis to handle the states.\n",
+ options,
+ &run,
+ NULL);
+ if (GNUNET_SYSERR == ret)
+ return 3;
+ if (GNUNET_NO == ret)
+ return 0;
+ return global_ret;
+}
+
+
+/* end of anastasis-cli-redux.c */
diff --git a/src/cli/resources/00-backup.json b/src/cli/resources/00-backup.json
new file mode 100644
index 0000000..6e6c320
--- /dev/null
+++ b/src/cli/resources/00-backup.json
@@ -0,0 +1,8 @@
+{
+ "continents": [
+ "Europe",
+ "North America",
+ "Testcontinent"
+ ],
+ "backup_state": "CONTINENT_SELECTING"
+} \ No newline at end of file
diff --git a/src/cli/resources/00-recovery.json b/src/cli/resources/00-recovery.json
new file mode 100644
index 0000000..acff19a
--- /dev/null
+++ b/src/cli/resources/00-recovery.json
@@ -0,0 +1,8 @@
+{
+ "continents": [
+ "Europe",
+ "North America",
+ "Testcontinent"
+ ],
+ "recovery_state": "CONTINENT_SELECTING"
+} \ No newline at end of file
diff --git a/src/cli/resources/01-backup.json b/src/cli/resources/01-backup.json
new file mode 100644
index 0000000..842d3af
--- /dev/null
+++ b/src/cli/resources/01-backup.json
@@ -0,0 +1,41 @@
+{
+ "continents": [
+ "Europe",
+ "North America",
+ "Testcontinent"
+ ],
+ "backup_state": "COUNTRY_SELECTING",
+ "selected_continent": "Testcontinent",
+ "countries": [
+ {
+ "code": "xx",
+ "name": "Testland",
+ "continent": "Testcontinent",
+ "continent_i18n": {
+ "xx": "Testkontinent"
+ },
+ "name_i18n": {
+ "de_DE": "Testlandt",
+ "de_CH": "Testlandi",
+ "fr": "Testpais",
+ "en": "Testland"
+ },
+ "currency": "TESTKUDOS"
+ },
+ {
+ "code": "xy",
+ "name": "Demoland",
+ "continent": "Testcontinent",
+ "continent_i18n": {
+ "xx": "Testkontinent"
+ },
+ "name_i18n": {
+ "de_DE": "Demolandt",
+ "de_CH": "Demolandi",
+ "fr": "Demopais",
+ "en": "Demoland"
+ },
+ "currency": "KUDOS"
+ }
+ ]
+} \ No newline at end of file
diff --git a/src/cli/resources/01-recovery.json b/src/cli/resources/01-recovery.json
new file mode 100644
index 0000000..11aafd3
--- /dev/null
+++ b/src/cli/resources/01-recovery.json
@@ -0,0 +1,41 @@
+{
+ "continents": [
+ "Europe",
+ "North America",
+ "Testcontinent"
+ ],
+ "recovery_state": "COUNTRY_SELECTING",
+ "selected_continent": "Testcontinent",
+ "countries": [
+ {
+ "code": "xx",
+ "name": "Testland",
+ "continent": "Testcontinent",
+ "continent_i18n": {
+ "xx": "Testkontinent"
+ },
+ "name_i18n": {
+ "de_DE": "Testlandt",
+ "de_CH": "Testlandi",
+ "fr": "Testpais",
+ "en": "Testland"
+ },
+ "currency": "TESTKUDOS"
+ },
+ {
+ "code": "xy",
+ "name": "Demoland",
+ "continent": "Testcontinent",
+ "continent_i18n": {
+ "xx": "Testkontinent"
+ },
+ "name_i18n": {
+ "de_DE": "Demolandt",
+ "de_CH": "Demolandi",
+ "fr": "Demopais",
+ "en": "Demoland"
+ },
+ "currency": "KUDOS"
+ }
+ ]
+} \ No newline at end of file
diff --git a/src/cli/resources/02-backup.json b/src/cli/resources/02-backup.json
new file mode 100644
index 0000000..c9bba16
--- /dev/null
+++ b/src/cli/resources/02-backup.json
@@ -0,0 +1,83 @@
+{
+ "continents": [
+ "Europe",
+ "North America",
+ "Testcontinent"
+ ],
+ "backup_state": "USER_ATTRIBUTES_COLLECTING",
+ "selected_continent": "Testcontinent",
+ "countries": [
+ {
+ "code": "xx",
+ "name": "Testland",
+ "continent": "Testcontinent",
+ "continent_i18n": {
+ "xx": "Testkontinent"
+ },
+ "name_i18n": {
+ "de_DE": "Testlandt",
+ "de_CH": "Testlandi",
+ "fr": "Testpais",
+ "en": "Testland"
+ },
+ "currency": "TESTKUDOS"
+ },
+ {
+ "code": "xy",
+ "name": "Demoland",
+ "continent": "Testcontinent",
+ "continent_i18n": {
+ "xx": "Testkontinent"
+ },
+ "name_i18n": {
+ "de_DE": "Demolandt",
+ "de_CH": "Demolandi",
+ "fr": "Demopais",
+ "en": "Demoland"
+ },
+ "currency": "KUDOS"
+ }
+ ],
+ "authentication_providers": {
+ "http://localhost:8086/": {},
+ "http://localhost:8087/": {},
+ "http://localhost:8088/": {},
+ "http://localhost:8089/": {}
+ },
+ "selected_country": "xx",
+ "currencies": [ "TESTKUDOS" ],
+ "required_attributes": [
+ {
+ "type": "string",
+ "name": "full_name",
+ "label": "Full name",
+ "label_i18n": {
+ "de_DE": "Vollstaendiger Name",
+ "de_CH": "Vollstaendiger Name"
+ },
+ "widget": "anastasis_gtk_ia_full_name"
+ },
+ {
+ "type": "date",
+ "name": "birthdate",
+ "label": "Birthdate",
+ "label_i18n": {
+ "de_CH": "Geburtsdatum"
+ },
+ "widget": "anastasis_gtk_ia_birthdate"
+ },
+ {
+ "type": "string",
+ "name": "sq_number",
+ "label": "Square number",
+ "label_i18n":{
+ "de_DE":"Quadratzahl",
+ "de_CH":"Quadratzahl"
+ },
+ "widget": "anastasis_gtk_xx_square",
+ "uuid" : "ed790bca-89bf-11eb-96f2-233996cf644e",
+ "validation-regex": "^[0-9]+$",
+ "validation-logic": "XX_SQUARE_check"
+ }
+ ]
+}
diff --git a/src/cli/resources/02-recovery.json b/src/cli/resources/02-recovery.json
new file mode 100644
index 0000000..79cfd6d
--- /dev/null
+++ b/src/cli/resources/02-recovery.json
@@ -0,0 +1,83 @@
+{
+ "continents": [
+ "Europe",
+ "North America",
+ "Testcontinent"
+ ],
+ "recovery_state": "USER_ATTRIBUTES_COLLECTING",
+ "selected_continent": "Testcontinent",
+ "countries": [
+ {
+ "code": "xx",
+ "name": "Testland",
+ "continent": "Testcontinent",
+ "continent_i18n": {
+ "xx": "Testkontinent"
+ },
+ "name_i18n": {
+ "de_DE": "Testlandt",
+ "de_CH": "Testlandi",
+ "fr": "Testpais",
+ "en": "Testland"
+ },
+ "currency": "TESTKUDOS"
+ },
+ {
+ "code": "xy",
+ "name": "Demoland",
+ "continent": "Testcontinent",
+ "continent_i18n": {
+ "xx": "Testkontinent"
+ },
+ "name_i18n": {
+ "de_DE": "Demolandt",
+ "de_CH": "Demolandi",
+ "fr": "Demopais",
+ "en": "Demoland"
+ },
+ "currency": "KUDOS"
+ }
+ ],
+ "authentication_providers": {
+ "http://localhost:8086/": {},
+ "http://localhost:8087/": {},
+ "http://localhost:8088/": {},
+ "http://localhost:8089/": {}
+ },
+ "selected_country": "xx",
+ "currencies": [ "TESTKUDOS" ],
+ "required_attributes": [
+ {
+ "type": "string",
+ "name": "full_name",
+ "label": "Full name",
+ "label_i18n": {
+ "de_DE": "Vollstaendiger Name",
+ "de_CH": "Vollstaendiger Name"
+ },
+ "widget": "anastasis_gtk_ia_full_name"
+ },
+ {
+ "type": "date",
+ "name": "birthdate",
+ "label": "Birthdate",
+ "label_i18n": {
+ "de_CH": "Geburtsdatum"
+ },
+ "widget": "anastasis_gtk_ia_birthdate"
+ },
+ {
+ "type": "string",
+ "name": "sq_number",
+ "label": "Square number",
+ "label_i18n":{
+ "de_DE":"Quadratzahl",
+ "de_CH":"Quadratzahl"
+ },
+ "widget": "anastasis_gtk_xx_square",
+ "uuid" : "ed790bca-89bf-11eb-96f2-233996cf644e",
+ "validation-regex": "^[0-9]+$",
+ "validation-logic": "XX_SQUARE_check"
+ }
+ ]
+}
diff --git a/src/cli/resources/03-backup.json b/src/cli/resources/03-backup.json
new file mode 100644
index 0000000..9d599d7
--- /dev/null
+++ b/src/cli/resources/03-backup.json
@@ -0,0 +1,155 @@
+{
+ "continents": [
+ "Europe",
+ "North America",
+ "Testcontinent"
+ ],
+ "backup_state": "AUTHENTICATIONS_EDITING",
+ "selected_continent": "Testcontinent",
+ "countries": [
+ {
+ "code": "xx",
+ "name": "Testland",
+ "continent": "Testcontinent",
+ "continent_i18n": {
+ "xx": "Testkontinent"
+ },
+ "name_i18n": {
+ "de_DE": "Testlandt",
+ "de_CH": "Testlandi",
+ "fr": "Testpais",
+ "en": "Testland"
+ },
+ "currency": "TESTKUDOS"
+ },
+ {
+ "code": "xy",
+ "name": "Demoland",
+ "continent": "Testcontinent",
+ "continent_i18n": {
+ "xx": "Testkontinent"
+ },
+ "name_i18n": {
+ "de_DE": "Demolandt",
+ "de_CH": "Demolandi",
+ "fr": "Demopais",
+ "en": "Demoland"
+ },
+ "currency": "KUDOS"
+ }
+ ],
+ "authentication_providers": {
+ "http://localhost:8086/": {
+ "methods": [
+ {
+ "type": "question",
+ "usage_fee": "TESTKUDOS:0"
+ }
+ ],
+ "annual_fee": "TESTKUDOS:4.99",
+ "truth_upload_fee": "TESTKUDOS:0.01",
+ "liability_limit": "TESTKUDOS:1",
+ "truth_lifetime": {
+ "d_ms": 63115200000
+ },
+ "currency": "TESTKUDOS",
+ "business_name": "Data loss #1 Inc.",
+ "storage_limit_in_megabytes": 1,
+ "salt": "F0HEYJQW81ZAZ3VYMZHFG8T1Z0"
+ },
+ "http://localhost:8087/": {
+ "methods": [
+ {
+ "type": "question",
+ "usage_fee": "TESTKUDOS:0"
+ }
+ ],
+ "annual_fee": "TESTKUDOS:4.99",
+ "truth_upload_fee": "TESTKUDOS:0.01",
+ "liability_limit": "TESTKUDOS:1",
+ "truth_lifetime": {
+ "d_ms": 63115200000
+ },
+ "currency": "TESTKUDOS",
+ "business_name": "Data loss #2 Inc.",
+ "storage_limit_in_megabytes": 1,
+ "salt": "D378FWXHJB8JHPQFQRZGGV9PWG"
+ },
+ "http://localhost:8088/": {
+ "methods": [
+ {
+ "type": "question",
+ "usage_fee": "TESTKUDOS:0"
+ }
+ ],
+ "annual_fee": "TESTKUDOS:4.99",
+ "truth_upload_fee": "TESTKUDOS:0.01",
+ "liability_limit": "TESTKUDOS:1",
+ "truth_lifetime": {
+ "d_ms": 63115200000
+ },
+ "currency": "TESTKUDOS",
+ "business_name": "Data loss #3 Inc.",
+ "storage_limit_in_megabytes": 1,
+ "salt": "7W9W4A4TTWSWRPJ76RNDPJHSPR"
+ },
+ "http://localhost:8089/": {
+ "methods": [
+ {
+ "type": "question",
+ "usage_fee": "TESTKUDOS:0"
+ }
+ ],
+ "annual_fee": "TESTKUDOS:4.99",
+ "truth_upload_fee": "TESTKUDOS:0.01",
+ "liability_limit": "TESTKUDOS:1",
+ "truth_lifetime": {
+ "d_ms": 63115200000
+ },
+ "currency": "TESTKUDOS",
+ "business_name": "Data loss #4 Inc.",
+ "storage_limit_in_megabytes": 1,
+ "salt": "PN0VJF6KDSBYN40SGRCEXPB07M"
+ }
+ },
+ "selected_country": "xx",
+ "currencies": ["TESTKUDOS"],
+ "required_attributes": [
+ {
+ "type": "string",
+ "name": "full_name",
+ "label": "Full name",
+ "label_i18n": {
+ "de_DE": "Vollstaendiger Name",
+ "de_CH": "Vollstaendiger Name"
+ },
+ "widget": "anastasis_gtk_ia_full_name"
+ },
+ {
+ "type": "date",
+ "name": "birthdate",
+ "label": "Birthdate",
+ "label_i18n": {
+ "de_CH": "Geburtsdatum"
+ },
+ "widget": "anastasis_gtk_ia_birthdate"
+ },
+ {
+ "type": "string",
+ "name": "ahv_number",
+ "label": "AHV number",
+ "label_i18n": {
+ "de_DE": "AHV-Nummer",
+ "de_CH": "AHV-Nummer"
+ },
+ "widget": "anastasis_gtk_ia_ahv",
+ "validation-regex": "^(756).[0-9]{4}.[0-9]{4}.[0-9]{2}|(756)[0-9]{10}$",
+ "validation-logic": "CH_AVH_check"
+ }
+ ],
+ "identity_attributes": {
+ "full_name": "Max Musterman",
+ "sq_number": 4,
+ "birthdate": "2000-01-01"
+ }
+}
diff --git a/src/cli/resources/04-backup.json b/src/cli/resources/04-backup.json
new file mode 100644
index 0000000..15c329a
--- /dev/null
+++ b/src/cli/resources/04-backup.json
@@ -0,0 +1,172 @@
+{
+ "continents": [
+ "Europe",
+ "North America",
+ "Testcontinent"
+ ],
+ "backup_state": "AUTHENTICATIONS_EDITING",
+ "selected_continent": "Testcontinent",
+ "countries": [
+ {
+ "code": "xx",
+ "name": "Testland",
+ "continent": "Testcontinent",
+ "continent_i18n": {
+ "xx": "Testkontinent"
+ },
+ "name_i18n": {
+ "de_DE": "Testlandt",
+ "de_CH": "Testlandi",
+ "fr": "Testpais",
+ "en": "Testland"
+ },
+ "currency": "TESTKUDOS"
+ },
+ {
+ "code": "xy",
+ "name": "Demoland",
+ "continent": "Testcontinent",
+ "continent_i18n": {
+ "xx": "Testkontinent"
+ },
+ "name_i18n": {
+ "de_DE": "Demolandt",
+ "de_CH": "Demolandi",
+ "fr": "Demopais",
+ "en": "Demoland"
+ },
+ "currency": "KUDOS"
+ }
+ ],
+ "authentication_providers": {
+ "http://localhost:8086/": {
+ "methods": [
+ {
+ "type": "question",
+ "usage_fee": "TESTKUDOS:0"
+ }
+ ],
+ "annual_fee": "TESTKUDOS:4.99",
+ "truth_upload_fee": "TESTKUDOS:0.01",
+ "liability_limit": "TESTKUDOS:1",
+ "truth_lifetime": {
+ "d_ms": 63115200000
+ },
+ "currency": "TESTKUDOS",
+ "business_name": "Data loss #1 Inc.",
+ "storage_limit_in_megabytes": 1,
+ "salt": "F0HEYJQW81ZAZ3VYMZHFG8T1Z0"
+ },
+ "http://localhost:8087/": {
+ "methods": [
+ {
+ "type": "question",
+ "usage_fee": "TESTKUDOS:0"
+ }
+ ],
+ "annual_fee": "TESTKUDOS:4.99",
+ "truth_upload_fee": "TESTKUDOS:0.01",
+ "liability_limit": "TESTKUDOS:1",
+ "truth_lifetime": {
+ "d_ms": 63115200000
+ },
+ "currency": "TESTKUDOS",
+ "business_name": "Data loss #2 Inc.",
+ "storage_limit_in_megabytes": 1,
+ "salt": "D378FWXHJB8JHPQFQRZGGV9PWG"
+ },
+ "http://localhost:8088/": {
+ "methods": [
+ {
+ "type": "question",
+ "usage_fee": "TESTKUDOS:0"
+ }
+ ],
+ "annual_fee": "TESTKUDOS:4.99",
+ "truth_upload_fee": "TESTKUDOS:0.01",
+ "liability_limit": "TESTKUDOS:1",
+ "truth_lifetime": {
+ "d_ms": 63115200000
+ },
+ "currency": "TESTKUDOS",
+ "business_name": "Data loss #3 Inc.",
+ "storage_limit_in_megabytes": 1,
+ "salt": "7W9W4A4TTWSWRPJ76RNDPJHSPR"
+ },
+ "http://localhost:8089/": {
+ "methods": [
+ {
+ "type": "question",
+ "usage_fee": "TESTKUDOS:0"
+ }
+ ],
+ "annual_fee": "TESTKUDOS:4.99",
+ "truth_upload_fee": "TESTKUDOS:0.01",
+ "liability_limit": "TESTKUDOS:1",
+ "truth_lifetime": {
+ "d_ms": 63115200000
+ },
+ "currency": "TESTKUDOS",
+ "business_name": "Data loss #4 Inc.",
+ "storage_limit_in_megabytes": 1,
+ "salt": "PN0VJF6KDSBYN40SGRCEXPB07M"
+ }
+ },
+ "selected_country": "xx",
+ "currencies": [ "TESTKUDOS" ],
+ "required_attributes": [
+ {
+ "type": "string",
+ "name": "full_name",
+ "label": "Full name",
+ "label_i18n": {
+ "de_DE": "Vollstaendiger Name",
+ "de_CH": "Vollstaendiger Name"
+ },
+ "widget": "anastasis_gtk_ia_full_name"
+ },
+ {
+ "type": "date",
+ "name": "birthdate",
+ "label": "Birthdate",
+ "label_i18n": {
+ "de_CH": "Geburtsdatum"
+ },
+ "widget": "anastasis_gtk_ia_birthdate"
+ },
+ {
+ "type": "string",
+ "name": "ahv_number",
+ "label": "AHV number",
+ "label_i18n": {
+ "de_DE": "AHV-Nummer",
+ "de_CH": "AHV-Nummer"
+ },
+ "widget": "anastasis_gtk_ia_ahv",
+ "validation-regex": "^(756).[0-9]{4}.[0-9]{4}.[0-9]{2}|(756)[0-9]{10}$",
+ "validation-logic": "CH_AVH_check"
+ }
+ ],
+ "identity_attributes": {
+ "full_name": "Max Musterman",
+ "sq_number": 4,
+ "birthdate": "2000-01-01"
+ },
+ "authentication_methods": [
+ {
+ "type": "question",
+ "instructions": "What's your name?",
+ "challenge": "Hans"
+ },
+ {
+ "type": "question",
+ "instructions": "What's your X name?",
+ "challenge": "Hansx"
+ },
+ {
+ "type": "question",
+ "instructions": "Where do you live?",
+ "challenge": "Mars"
+ }
+ ]
+}
diff --git a/src/cli/resources/05-backup.json b/src/cli/resources/05-backup.json
new file mode 100644
index 0000000..c0ce8ae
--- /dev/null
+++ b/src/cli/resources/05-backup.json
@@ -0,0 +1,213 @@
+{
+ "continents": [
+ "Europe",
+ "North America",
+ "Testcontinent"
+ ],
+ "backup_state": "POLICIES_REVIEWING",
+ "selected_continent": "Testcontinent",
+ "countries": [
+ {
+ "code": "xx",
+ "name": "Testland",
+ "continent": "Testcontinent",
+ "continent_i18n": {
+ "xx": "Testkontinent"
+ },
+ "name_i18n": {
+ "de_DE": "Testlandt",
+ "de_CH": "Testlandi",
+ "fr": "Testpais",
+ "en": "Testland"
+ },
+ "currency": "TESTKUDOS"
+ },
+ {
+ "code": "xy",
+ "name": "Demoland",
+ "continent": "Testcontinent",
+ "continent_i18n": {
+ "xx": "Testkontinent"
+ },
+ "name_i18n": {
+ "de_DE": "Demolandt",
+ "de_CH": "Demolandi",
+ "fr": "Demopais",
+ "en": "Demoland"
+ },
+ "currency": "KUDOS"
+ }
+ ],
+ "authentication_providers": {
+ "http://localhost:8086/": {
+ "methods": [
+ {
+ "type": "question",
+ "usage_fee": "TESTKUDOS:0"
+ }
+ ],
+ "annual_fee": "TESTKUDOS:4.99",
+ "truth_upload_fee": "TESTKUDOS:0.01",
+ "liability_limit": "TESTKUDOS:1",
+ "truth_lifetime": {
+ "d_ms": 63115200000
+ },
+ "currency": "TESTKUDOS",
+ "business_name": "Data loss #1 Inc.",
+ "storage_limit_in_megabytes": 1,
+ "salt": "F0HEYJQW81ZAZ3VYMZHFG8T1Z0"
+ },
+ "http://localhost:8087/": {
+ "methods": [
+ {
+ "type": "question",
+ "usage_fee": "TESTKUDOS:0"
+ }
+ ],
+ "annual_fee": "TESTKUDOS:4.99",
+ "truth_upload_fee": "TESTKUDOS:0.01",
+ "liability_limit": "TESTKUDOS:1",
+ "truth_lifetime": {
+ "d_ms": 63115200000
+ },
+ "currency": "TESTKUDOS",
+ "business_name": "Data loss #2 Inc.",
+ "storage_limit_in_megabytes": 1,
+ "salt": "D378FWXHJB8JHPQFQRZGGV9PWG"
+ },
+ "http://localhost:8088/": {
+ "methods": [
+ {
+ "type": "question",
+ "usage_fee": "TESTKUDOS:0"
+ }
+ ],
+ "annual_fee": "TESTKUDOS:4.99",
+ "truth_upload_fee": "TESTKUDOS:0.01",
+ "liability_limit": "TESTKUDOS:1",
+ "truth_lifetime": {
+ "d_ms": 63115200000
+ },
+ "currency": "TESTKUDOS",
+ "business_name": "Data loss #3 Inc.",
+ "storage_limit_in_megabytes": 1,
+ "salt": "7W9W4A4TTWSWRPJ76RNDPJHSPR"
+ },
+ "http://localhost:8089/": {
+ "methods": [
+ {
+ "type": "question",
+ "usage_fee": "TESTKUDOS:0"
+ }
+ ],
+ "annual_fee": "TESTKUDOS:4.99",
+ "truth_upload_fee": "TESTKUDOS:0.01",
+ "liability_limit": "TESTKUDOS:1",
+ "truth_lifetime": {
+ "d_ms": 63115200000
+ },
+ "currency": "TESTKUDOS",
+ "business_name": "Data loss #4 Inc.",
+ "storage_limit_in_megabytes": 1,
+ "salt": "PN0VJF6KDSBYN40SGRCEXPB07M"
+ }
+ },
+ "selected_country": "xx",
+ "currencies": [ "TESTKUDOS" ],
+ "required_attributes": [
+ {
+ "type": "string",
+ "name": "full_name",
+ "label": "Full name",
+ "label_i18n": {
+ "de_DE": "Vollstaendiger Name",
+ "de_CH": "Vollstaendiger Name"
+ },
+ "widget": "anastasis_gtk_ia_full_name"
+ },
+ {
+ "type": "date",
+ "name": "birthdate",
+ "label": "Birthdate",
+ "label_i18n": {
+ "de_CH": "Geburtsdatum"
+ },
+ "widget": "anastasis_gtk_ia_birthdate"
+ },
+ {
+ "type": "string",
+ "name": "ahv_number",
+ "label": "AHV number",
+ "label_i18n": {
+ "de_DE": "AHV-Nummer",
+ "de_CH": "AHV-Nummer"
+ },
+ "widget": "anastasis_gtk_ia_ahv",
+ "validation-regex": "^(756).[0-9]{4}.[0-9]{4}.[0-9]{2}|(756)[0-9]{10}$",
+ "validation-logic": "CH_AVH_check"
+ }
+ ],
+ "identity_attributes": {
+ "full_name": "Max Musterman",
+ "sq_number": 4,
+ "birthdate": "2000-01-01"
+ },
+ "authentication_methods": [
+ {
+ "type": "question",
+ "instructions": "What's your name?",
+ "challenge": "Hans"
+ },
+ {
+ "type": "question",
+ "instructions": "What's your X name?",
+ "challenge": "Hansx"
+ },
+ {
+ "type": "question",
+ "instructions": "Where do you live?",
+ "challenge": "Mars"
+ }
+ ],
+ "policies": [
+ {
+ "recovery_cost": "TESTKUDOS:0",
+ "methods": [
+ {
+ "authentication_method": 0,
+ "provider": "http://localhost:8089/"
+ },
+ {
+ "authentication_method": 1,
+ "provider": "http://localhost:8088/"
+ }
+ ]
+ },
+ {
+ "recovery_cost": "TESTKUDOS:0",
+ "methods": [
+ {
+ "authentication_method": 0,
+ "provider": "http://localhost:8089/"
+ },
+ {
+ "authentication_method": 2,
+ "provider": "http://localhost:8088/"
+ }
+ ]
+ },
+ {
+ "recovery_cost": "TESTKUDOS:0",
+ "methods": [
+ {
+ "authentication_method": 1,
+ "provider": "http://localhost:8089/"
+ },
+ {
+ "authentication_method": 2,
+ "provider": "http://localhost:8088/"
+ }
+ ]
+ }
+ ]
+}
diff --git a/src/cli/resources/06-backup.json b/src/cli/resources/06-backup.json
new file mode 100644
index 0000000..d1f0b9e
--- /dev/null
+++ b/src/cli/resources/06-backup.json
@@ -0,0 +1,223 @@
+{
+ "continents": [
+ "Europe",
+ "North America",
+ "Testcontinent"
+ ],
+ "backup_state": "SECRET_EDITING",
+ "selected_continent": "Testcontinent",
+ "countries": [
+ {
+ "code": "xx",
+ "name": "Testland",
+ "continent": "Testcontinent",
+ "continent_i18n": {
+ "xx": "Testkontinent"
+ },
+ "name_i18n": {
+ "de_DE": "Testlandt",
+ "de_CH": "Testlandi",
+ "fr": "Testpais",
+ "en": "Testland"
+ },
+ "currency": "TESTKUDOS"
+ },
+ {
+ "code": "xy",
+ "name": "Demoland",
+ "continent": "Testcontinent",
+ "continent_i18n": {
+ "xx": "Testkontinent"
+ },
+ "name_i18n": {
+ "de_DE": "Demolandt",
+ "de_CH": "Demolandi",
+ "fr": "Demopais",
+ "en": "Demoland"
+ },
+ "currency": "KUDOS"
+ }
+ ],
+ "authentication_providers": {
+ "http://localhost:8086/": {
+ "methods": [
+ {
+ "type": "question",
+ "usage_fee": "TESTKUDOS:0"
+ }
+ ],
+ "annual_fee": "TESTKUDOS:4.99",
+ "truth_upload_fee": "TESTKUDOS:0.01",
+ "liability_limit": "TESTKUDOS:1",
+ "truth_lifetime": {
+ "d_ms": 63115200000
+ },
+ "currency": "TESTKUDOS",
+ "business_name": "Data loss #1 Inc.",
+ "storage_limit_in_megabytes": 1,
+ "salt": "F0HEYJQW81ZAZ3VYMZHFG8T1Z0"
+ },
+ "http://localhost:8087/": {
+ "methods": [
+ {
+ "type": "question",
+ "usage_fee": "TESTKUDOS:0"
+ }
+ ],
+ "annual_fee": "TESTKUDOS:4.99",
+ "truth_upload_fee": "TESTKUDOS:0.01",
+ "liability_limit": "TESTKUDOS:1",
+ "truth_lifetime": {
+ "d_ms": 63115200000
+ },
+ "currency": "TESTKUDOS",
+ "business_name": "Data loss #2 Inc.",
+ "storage_limit_in_megabytes": 1,
+ "salt": "D378FWXHJB8JHPQFQRZGGV9PWG"
+ },
+ "http://localhost:8088/": {
+ "methods": [
+ {
+ "type": "question",
+ "usage_fee": "TESTKUDOS:0"
+ }
+ ],
+ "annual_fee": "TESTKUDOS:4.99",
+ "truth_upload_fee": "TESTKUDOS:0.01",
+ "liability_limit": "TESTKUDOS:1",
+ "truth_lifetime": {
+ "d_ms": 63115200000
+ },
+ "currency": "TESTKUDOS",
+ "business_name": "Data loss #3 Inc.",
+ "storage_limit_in_megabytes": 1,
+ "salt": "7W9W4A4TTWSWRPJ76RNDPJHSPR"
+ },
+ "http://localhost:8089/": {
+ "methods": [
+ {
+ "type": "question",
+ "usage_fee": "TESTKUDOS:0"
+ }
+ ],
+ "annual_fee": "TESTKUDOS:4.99",
+ "truth_upload_fee": "TESTKUDOS:0.01",
+ "liability_limit": "TESTKUDOS:1",
+ "truth_lifetime": {
+ "d_ms": 63115200000
+ },
+ "currency": "TESTKUDOS",
+ "business_name": "Data loss #4 Inc.",
+ "storage_limit_in_megabytes": 1,
+ "salt": "PN0VJF6KDSBYN40SGRCEXPB07M"
+ }
+ },
+ "selected_country": "xx",
+ "currencies": ["TESTKUDOS"],
+ "required_attributes": [
+ {
+ "type": "string",
+ "name": "full_name",
+ "label": "Full name",
+ "label_i18n": {
+ "de_DE": "Vollstaendiger Name",
+ "de_CH": "Vollstaendiger Name"
+ },
+ "widget": "anastasis_gtk_ia_full_name"
+ },
+ {
+ "type": "date",
+ "name": "birthdate",
+ "label": "Birthdate",
+ "label_i18n": {
+ "de_CH": "Geburtsdatum"
+ },
+ "widget": "anastasis_gtk_ia_birthdate"
+ },
+ {
+ "type": "string",
+ "name": "ahv_number",
+ "label": "AHV number",
+ "label_i18n": {
+ "de_DE": "AHV-Nummer",
+ "de_CH": "AHV-Nummer"
+ },
+ "widget": "anastasis_gtk_ia_ahv",
+ "validation-regex": "^(756).[0-9]{4}.[0-9]{4}.[0-9]{2}|(756)[0-9]{10}$",
+ "validation-logic": "CH_AVH_check"
+ }
+ ],
+ "identity_attributes": {
+ "full_name": "Max Musterman",
+ "ahv_number": "756.9217.0769.85",
+ "birth_year": 2000,
+ "birth_month": 1,
+ "birth_day": 1
+ },
+ "authentication_methods": [
+ {
+ "type": "question",
+ "instructions": "What's your name?",
+ "challenge": "Hans"
+ },
+ {
+ "type": "question",
+ "instructions": "What's your X name?",
+ "challenge": "Hansx"
+ },
+ {
+ "type": "question",
+ "instructions": "Where do you live?",
+ "challenge": "Mars"
+ }
+ ],
+ "policy_providers" : [
+ {
+ "provider_url": "http://localhost:8089/"
+ },
+ {
+ "provider_url": "http://localhost:8089/"
+ }
+ ],
+ "policies": [
+ {
+ "recovery_cost": "TESTKUDOS:0",
+ "methods": [
+ {
+ "authentication_method": 0,
+ "provider": "http://localhost:8089/"
+ },
+ {
+ "authentication_method": 1,
+ "provider": "http://localhost:8088/"
+ }
+ ]
+ },
+ {
+ "recovery_cost": "TESTKUDOS:0",
+ "methods": [
+ {
+ "authentication_method": 0,
+ "provider": "http://localhost:8089/"
+ },
+ {
+ "authentication_method": 2,
+ "provider": "http://localhost:8088/"
+ }
+ ]
+ },
+ {
+ "recovery_cost": "TESTKUDOS:0",
+ "methods": [
+ {
+ "authentication_method": 1,
+ "provider": "http://localhost:8089/"
+ },
+ {
+ "authentication_method": 2,
+ "provider": "http://localhost:8088/"
+ }
+ ]
+ }
+ ]
+}
diff --git a/src/cli/test_anastasis_reducer_1.conf b/src/cli/test_anastasis_reducer_1.conf
new file mode 100644
index 0000000..6a9704d
--- /dev/null
+++ b/src/cli/test_anastasis_reducer_1.conf
@@ -0,0 +1,9 @@
+@INLINE@ test_reducer.conf
+
+[anastasis]
+PORT = 8086
+SERVER_SALT = AUfO1KGOKYIFlFQg
+BUSINESS_NAME = "Data loss #1 Inc."
+
+[stasis-postgres]
+CONFIG = postgres:///anastasischeck1
diff --git a/src/cli/test_anastasis_reducer_2.conf b/src/cli/test_anastasis_reducer_2.conf
new file mode 100644
index 0000000..f909ade
--- /dev/null
+++ b/src/cli/test_anastasis_reducer_2.conf
@@ -0,0 +1,9 @@
+@INLINE@ test_reducer.conf
+
+[anastasis]
+PORT = 8087
+SERVER_SALT = BUfO1KGOKYIFlFQg
+BUSINESS_NAME = "Data loss #2 Inc."
+
+[stasis-postgres]
+CONFIG = postgres:///anastasischeck2
diff --git a/src/cli/test_anastasis_reducer_3.conf b/src/cli/test_anastasis_reducer_3.conf
new file mode 100644
index 0000000..63c38ff
--- /dev/null
+++ b/src/cli/test_anastasis_reducer_3.conf
@@ -0,0 +1,9 @@
+@INLINE@ test_reducer.conf
+
+[anastasis]
+PORT = 8088
+SERVER_SALT = CUfO1KGOKYIFlFQg
+BUSINESS_NAME = "Data loss #3 Inc."
+
+[stasis-postgres]
+CONFIG = postgres:///anastasischeck3
diff --git a/src/cli/test_anastasis_reducer_4.conf b/src/cli/test_anastasis_reducer_4.conf
new file mode 100644
index 0000000..a6d590e
--- /dev/null
+++ b/src/cli/test_anastasis_reducer_4.conf
@@ -0,0 +1,9 @@
+@INLINE@ test_reducer.conf
+
+[anastasis]
+PORT = 8089
+SERVER_SALT = DUfO1KGOKYIFlFQg
+BUSINESS_NAME = "Data loss #4 Inc."
+
+[stasis-postgres]
+CONFIG = postgres:///anastasischeck4
diff --git a/src/cli/test_anastasis_reducer_add_authentication.sh b/src/cli/test_anastasis_reducer_add_authentication.sh
new file mode 100755
index 0000000..7d69076
--- /dev/null
+++ b/src/cli/test_anastasis_reducer_add_authentication.sh
@@ -0,0 +1,134 @@
+#!/bin/bash
+
+set -eu
+
+# Exit, with status code "skip" (no 'real' failure)
+function exit_skip() {
+ echo " SKIP: $1"
+ exit 77
+}
+
+# Exit, with error message (hard failure)
+function exit_fail() {
+ echo " ERROR: $1"
+ exit 1
+}
+
+# Cleanup to run whenever we exit
+function cleanup()
+{
+ for n in `jobs -p`
+ do
+ kill $n 2> /dev/null || true
+ done
+ rm -f $TFILE $SFILE
+ wait
+}
+
+SFILE=`mktemp test_reducer_stateXXXXXX`
+TFILE=`mktemp test_reducer_stateXXXXXX`
+
+# Install cleanup handler (except for kill -9)
+trap cleanup EXIT
+
+# Check we can actually run
+echo -n "Testing for jq"
+jq -h > /dev/null || exit_skip "jq required"
+echo " FOUND"
+
+echo -n "Testing for anastasis-reducer ..."
+anastasis-reducer -h > /dev/null || exit_skip "anastasis-reducer required"
+echo " FOUND"
+
+echo -n "Test add authentication ..."
+
+# First method
+anastasis-reducer -a \
+ '{"authentication_method": {
+ "type": "question",
+ "instructions": "What is your name?",
+ "challenge": "91GPWWR"
+ } }' \
+ add_authentication resources/03-backup.json $TFILE
+
+STATE=`jq -r -e .backup_state < $TFILE`
+if test "$STATE" != "AUTHENTICATIONS_EDITING"
+then
+ exit_fail "Expected new state to be 'AUTHENTICATIONS_EDITING', got '$STATE'"
+fi
+
+ARRAY_LENGTH=`jq -r -e '.authentication_methods | length' < $TFILE`
+if test $ARRAY_LENGTH != 1
+then
+ exit_fail "Expected array length to be 1, got '$ARRAY_LENGTH'"
+fi
+
+echo -n "."
+# Second method
+anastasis-reducer -a \
+ '{"authentication_method": {
+ "type": "question",
+ "instructions": "How old are you?",
+ "challenge": "64S36"
+ }}' \
+ add_authentication $TFILE $SFILE
+
+STATE=`jq -r -e .backup_state < $SFILE`
+if test "$STATE" != "AUTHENTICATIONS_EDITING"
+then
+ exit_fail "Expected new state to be 'AUTHENTICATIONS_EDITING', got '$STATE'"
+fi
+
+ARRAY_LENGTH=`jq -r -e '.authentication_methods | length' < $SFILE`
+if test $ARRAY_LENGTH != 2
+then
+ exit_fail "Expected array length to be 2, got '$ARRAY_LENGTH'"
+fi
+
+echo -n "."
+
+# Third method
+anastasis-reducer -a \
+ '{"authentication_method": {
+ "type": "question",
+ "instructions": "Where do you live?",
+ "challenge": "9NGQ4WR"
+ }}' \
+ add_authentication $SFILE $TFILE
+
+STATE=`jq -r -e .backup_state < $TFILE`
+if test "$STATE" != "AUTHENTICATIONS_EDITING"
+then
+ exit_fail "Expected new state to be 'AUTHENTICATIONS_EDITING', got '$STATE'"
+fi
+
+ARRAY_LENGTH=`jq -r -e '.authentication_methods | length' < $TFILE`
+if test $ARRAY_LENGTH != 3
+then
+ exit_fail "Expected array length to be 3, got '$ARRAY_LENGTH'"
+fi
+
+echo " OK"
+
+
+echo -n "Test delete authentication ..."
+
+anastasis-reducer -a \
+ '{"authentication_method": 2 }' \
+ delete_authentication $TFILE $SFILE
+
+STATE=`jq -r -e .backup_state < $SFILE`
+if test "$STATE" != "AUTHENTICATIONS_EDITING"
+then
+ exit_fail "Expected new state to be 'AUTHENTICATIONS_EDITING', got '$STATE'"
+fi
+
+ARRAY_LENGTH=`jq -r -e '.authentication_methods | length' < $SFILE`
+if test $ARRAY_LENGTH != 2
+then
+ exit_fail "Expected array length to be 2, got '$ARRAY_LENGTH'"
+fi
+
+echo " OK"
+
+exit 0
diff --git a/src/cli/test_anastasis_reducer_backup_enter_user_attributes.sh b/src/cli/test_anastasis_reducer_backup_enter_user_attributes.sh
new file mode 100755
index 0000000..433438e
--- /dev/null
+++ b/src/cli/test_anastasis_reducer_backup_enter_user_attributes.sh
@@ -0,0 +1,140 @@
+#!/bin/bash
+
+set -eu
+
+# Exit, with status code "skip" (no 'real' failure)
+function exit_skip() {
+ echo " SKIP: $1"
+ exit 77
+}
+
+# Exit, with error message (hard failure)
+function exit_fail() {
+ echo " ERROR: $1"
+ exit 1
+}
+
+# Cleanup to run whenever we exit
+function cleanup()
+{
+ for n in `jobs -p`
+ do
+ kill $n 2> /dev/null || true
+ done
+ wait
+}
+
+CONF_1="test_anastasis_reducer_1.conf"
+CONF_2="test_anastasis_reducer_2.conf"
+CONF_3="test_anastasis_reducer_3.conf"
+CONF_4="test_anastasis_reducer_4.conf"
+TFILE=`mktemp test_reducer_stateXXXXXX`
+
+# Install cleanup handler (except for kill -9)
+trap cleanup EXIT
+
+# Check we can actually run
+echo -n "Testing for jq"
+jq -h > /dev/null || exit_skip "jq required"
+echo " FOUND"
+
+echo -n "Testing for anastasis-reducer ..."
+anastasis-reducer -h > /dev/null || exit_skip "anastasis-reducer required"
+echo " FOUND"
+
+echo -n "Testing for anastasis-httpd"
+anastasis-httpd -h >/dev/null </dev/null || exit_skip " MISSING"
+echo " FOUND"
+
+
+# Name of the Postgres database we will use for the script.
+# Will be dropped, do NOT use anything that might be used
+# elsewhere
+TARGET_DB_1=`anastasis-config -c $CONF_1 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"`
+TARGET_DB_2=`anastasis-config -c $CONF_2 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"`
+TARGET_DB_3=`anastasis-config -c $CONF_3 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"`
+TARGET_DB_4=`anastasis-config -c $CONF_4 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"`
+
+echo -n "Initialize anastasis database ..."
+dropdb $TARGET_DB_1 >/dev/null 2>/dev/null || true
+createdb $TARGET_DB_1 || exit_skip "Could not create database $TARGET_DB_1"
+anastasis-dbinit -c $CONF_1 2> anastasis-dbinit_1.log
+dropdb $TARGET_DB_2 >/dev/null 2>/dev/null || true
+createdb $TARGET_DB_2 || exit_skip "Could not create database $TARGET_DB_2"
+anastasis-dbinit -c $CONF_2 2> anastasis-dbinit_2.log
+dropdb $TARGET_DB_3 >/dev/null 2>/dev/null || true
+createdb $TARGET_DB_3 || exit_skip "Could not create database $TARGET_DB_3"
+anastasis-dbinit -c $CONF_3 2> anastasis-dbinit_3.log
+dropdb $TARGET_DB_4 >/dev/null 2>/dev/null || true
+createdb $TARGET_DB_4 || exit_skip "Could not create database $TARGET_DB_4"
+anastasis-dbinit -c $CONF_4 2> anastasis-dbinit_4.log
+
+echo " OK"
+
+echo -n "Launching anastasis service ..."
+anastasis-httpd -c $CONF_1 2> anastasis-httpd_1.log &
+anastasis-httpd -c $CONF_2 2> anastasis-httpd_2.log &
+anastasis-httpd -c $CONF_3 2> anastasis-httpd_3.log &
+anastasis-httpd -c $CONF_4 2> anastasis-httpd_4.log &
+
+# Wait for anastasis service to be available
+for n in `seq 1 50`
+do
+ echo -n "."
+ sleep 0.1
+ OK=0
+ # anastasis_01
+ wget http://localhost:8086/ -o /dev/null -O /dev/null >/dev/null || continue
+ # anastasis_02
+ wget http://localhost:8087/ -o /dev/null -O /dev/null >/dev/null || continue
+ # anastasis_03
+ wget http://localhost:8088/ -o /dev/null -O /dev/null >/dev/null || continue
+ # anastasis_04
+ wget http://localhost:8089/ -o /dev/null -O /dev/null >/dev/null || continue
+ OK=1
+ break
+done
+
+if [ 1 != $OK ]
+then
+ exit_skip "Failed to launch anastasis services"
+fi
+echo " OK"
+
+# Test user attributes collection in a backup state
+echo -n "Test user attributes collection in a backup state ..."
+
+anastasis-reducer -L WARNING -a \
+ '{"identity_attributes": {
+ "full_name": "Max Musterman",
+ "sq_number": "4",
+ "birthdate": "2000-01-01"}}' \
+ enter_user_attributes resources/02-backup.json $TFILE
+
+STATE=`jq -r -e .backup_state < $TFILE`
+if test "$STATE" != "AUTHENTICATIONS_EDITING"
+then
+ exit_fail "Expected new state to be 'AUTHENTICATIONS_EDITING', got '$STATE'"
+fi
+
+SELECTED_COUNTRY=`jq -r -e .selected_country < $TFILE`
+if test "$SELECTED_COUNTRY" != "xx"
+then
+ exit_fail "Expected selected country to be 'xx', got '$SELECTED_COUNTRY'"
+fi
+
+echo "OK"
+
+echo -n "Test user attributes collection in a recovery state ..."
+anastasis-reducer -a \
+ '{"identity_attributes": {
+ "full_name": "Max Musterman",
+ "sq_number": "4",
+ "birthdate": "2000-01-01"}}' \
+ enter_user_attributes resources/02-recovery.json $TFILE 2> /dev/null && exit_fail "Expected recovery to fail due to lacking policy data"
+
+echo "OK"
+
+rm -f $TFILE
+
+exit 0
diff --git a/src/cli/test_anastasis_reducer_done_authentication.sh b/src/cli/test_anastasis_reducer_done_authentication.sh
new file mode 100755
index 0000000..87c738c
--- /dev/null
+++ b/src/cli/test_anastasis_reducer_done_authentication.sh
@@ -0,0 +1,65 @@
+#!/bin/bash
+
+set -eu
+
+# Exit, with status code "skip" (no 'real' failure)
+function exit_skip() {
+ echo " SKIP: $1"
+ exit 77
+}
+
+# Exit, with error message (hard failure)
+function exit_fail() {
+ echo " ERROR: $1"
+ exit 1
+}
+
+# Cleanup to run whenever we exit
+function cleanup()
+{
+ for n in `jobs -p`
+ do
+ kill $n 2> /dev/null || true
+ done
+ rm -f $TFILE
+ wait
+}
+
+# Install cleanup handler (except for kill -9)
+TFILE=`mktemp test_reducer_stateXXXXXX`
+trap cleanup EXIT
+
+# Check we can actually run
+echo -n "Testing for jq ..."
+jq -h > /dev/null || exit_skip "jq required"
+echo " FOUND"
+
+echo -n "Testing for anastasis-reducer ..."
+anastasis-reducer -h > /dev/null || exit_skip "anastasis-reducer required"
+echo " FOUND"
+
+
+echo -n "Test failing done authentication (next) ..."
+anastasis-reducer next resources/03-backup.json $TFILE 2> /dev/null && exit_fail "Should have failed without challenges"
+
+echo " OK"
+
+
+echo -n "Test done authentication (next) ..."
+anastasis-reducer next resources/04-backup.json $TFILE
+
+STATE=`jq -r -e .backup_state < $TFILE`
+if test "$STATE" != "POLICIES_REVIEWING"
+then
+ exit_fail "Expected new state to be AUTHENTICATIONS_EDITING, got $STATE"
+fi
+
+ARRAY_LENGTH=`jq -r -e '.policies | length' < $TFILE`
+if test $ARRAY_LENGTH -lt 3
+then
+ exit_fail "Expected policy array length to be >= 3, got $ARRAY_LENGTH"
+fi
+
+echo " OK"
+
+exit 0
diff --git a/src/cli/test_anastasis_reducer_done_policy_review.sh b/src/cli/test_anastasis_reducer_done_policy_review.sh
new file mode 100755
index 0000000..7052067
--- /dev/null
+++ b/src/cli/test_anastasis_reducer_done_policy_review.sh
@@ -0,0 +1,105 @@
+#!/bin/bash
+
+set -eu
+
+# Exit, with status code "skip" (no 'real' failure)
+function exit_skip() {
+ echo " SKIP: $1"
+ exit 77
+}
+
+# Exit, with error message (hard failure)
+function exit_fail() {
+ echo " ERROR: $1"
+ exit 1
+}
+
+# Cleanup to run whenever we exit
+function cleanup()
+{
+ for n in `jobs -p`
+ do
+ kill $n 2> /dev/null || true
+ done
+ rm -f $TFILE
+ wait
+}
+
+# Install cleanup handler (except for kill -9)
+TFILE=`mktemp test_reducer_stateXXXXXX`
+trap cleanup EXIT
+
+
+# Check we can actually run
+echo -n "Testing for jq ..."
+jq -h > /dev/null || exit_skip "jq required"
+echo " FOUND"
+
+echo -n "Testing for anastasis-reducer ..."
+anastasis-reducer -h > /dev/null || exit_skip "anastasis-reducer required"
+echo " FOUND"
+
+echo -n "Test done policy review (next) in a backup state ..."
+anastasis-reducer next resources/05-backup.json $TFILE
+
+STATE=`jq -r -e .backup_state < $TFILE`
+if test "$STATE" != "SECRET_EDITING"
+then
+ exit_fail "Expected new state to be 'SECRET_EDITING', got '$STATE'"
+fi
+
+ARRAY_LENGTH=`jq -r -e '.authentication_methods | length' < $TFILE`
+if test $ARRAY_LENGTH -lt 3
+then
+ exit_fail "Expected auth methods array length to be >= 3, got $ARRAY_LENGTH"
+fi
+
+ARRAY_LENGTH=`jq -r -e '.policies | length' < $TFILE`
+if test $ARRAY_LENGTH -lt 3
+then
+ exit_fail "Expected policies array length to be >= 3, got $ARRAY_LENGTH"
+fi
+
+echo " OK"
+
+
+
+echo -n "Test adding policy ..."
+anastasis-reducer -a \
+ '{ "policy" : [
+ { "authentication_method" : 1,
+ "provider" : "http://localhost:8088/" },
+ { "authentication_method" : 1,
+ "provider" : "http://localhost:8089/" }
+ ] }' \
+ add_policy \
+ resources/05-backup.json \
+ $TFILE 2> /dev/null
+
+ARRAY_LENGTH=`jq -r -e '.policies | length' < $TFILE`
+if test $ARRAY_LENGTH -lt 4
+then
+ exit_fail "Expected policy array length to be >= 4, got $ARRAY_LENGTH"
+fi
+
+echo " OK"
+
+
+echo -n "Test deleting policy ..."
+anastasis-reducer -a \
+ '{ "policy_index" : 2 }' \
+ delete_policy \
+ resources/05-backup.json \
+ $TFILE 2> /dev/null
+
+ARRAY_LENGTH=`jq -r -e '.policies | length' < $TFILE`
+if test $ARRAY_LENGTH -ge 3
+then
+ exit_fail "Expected policy array length to be < 3, got $ARRAY_LENGTH"
+fi
+
+echo " OK"
+
+
+
+exit 0
diff --git a/src/cli/test_anastasis_reducer_enter_secret.sh b/src/cli/test_anastasis_reducer_enter_secret.sh
new file mode 100755
index 0000000..dadd8d0
--- /dev/null
+++ b/src/cli/test_anastasis_reducer_enter_secret.sh
@@ -0,0 +1,417 @@
+#!/bin/bash
+## Coloring style Text shell script
+COLOR='\033[0;35m'
+NOCOLOR='\033[0m'
+BOLD="$(tput bold)"
+NORM="$(tput sgr0)"
+
+set -eu
+
+# Exit, with status code "skip" (no 'real' failure)
+function exit_skip() {
+ echo " SKIP: $1"
+ exit 77
+}
+
+# Exit, with error message (hard failure)
+function exit_fail() {
+ echo " FAIL: $1"
+ exit 1
+}
+
+# Cleanup to run whenever we exit
+function cleanup()
+{
+ for n in `jobs -p`
+ do
+ kill $n 2> /dev/null || true
+ done
+ rm -rf $CONF $WALLET_DB $TFILE $UFILE $TMP_DIR
+ wait
+}
+
+CONF_1="test_anastasis_reducer_1.conf"
+CONF_2="test_anastasis_reducer_2.conf"
+CONF_3="test_anastasis_reducer_3.conf"
+CONF_4="test_anastasis_reducer_4.conf"
+
+# Exchange configuration file will be edited, so we create one
+# from the template.
+CONF=`mktemp test_reducerXXXXXX.conf`
+cp test_reducer.conf $CONF
+
+TMP_DIR=`mktemp -d keys-tmp-XXXXXX`
+WALLET_DB=`mktemp test_reducer_walletXXXXXX.json`
+TFILE=`mktemp test_reducer_statePPXXXXXX`
+UFILE=`mktemp test_reducer_stateBFXXXXXX`
+
+# Install cleanup handler (except for kill -9)
+trap cleanup EXIT
+
+# Check we can actually run
+echo -n "Testing for jq"
+jq -h > /dev/null || exit_skip "jq required"
+echo " FOUND"
+echo -n "Testing for anastasis-reducer ..."
+anastasis-reducer -h > /dev/null || exit_skip "anastasis-reducer required"
+echo " FOUND"
+
+echo -n "Testing for taler"
+taler-exchange-httpd -h > /dev/null || exit_skip " taler-exchange required"
+taler-merchant-httpd -h > /dev/null || exit_skip " taler-merchant required"
+echo " FOUND"
+
+echo -n "Testing for taler-bank-manage"
+taler-bank-manage --help >/dev/null </dev/null || exit_skip " MISSING"
+echo " FOUND"
+echo -n "Testing for taler-wallet-cli"
+taler-wallet-cli -v >/dev/null </dev/null || exit_skip " MISSING"
+echo " FOUND"
+
+echo -n "Testing for anastasis-httpd"
+anastasis-httpd -h >/dev/null </dev/null || exit_skip " MISSING"
+echo " FOUND"
+
+echo -n "Initialize anastasis database ..."
+# Name of the Postgres database we will use for the script.
+# Will be dropped, do NOT use anything that might be used
+# elsewhere
+TARGET_DB_1=`anastasis-config -c $CONF_1 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"`
+TARGET_DB_2=`anastasis-config -c $CONF_2 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"`
+TARGET_DB_3=`anastasis-config -c $CONF_3 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"`
+TARGET_DB_4=`anastasis-config -c $CONF_4 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"`
+
+dropdb $TARGET_DB_1 >/dev/null 2>/dev/null || true
+createdb $TARGET_DB_1 || exit_skip "Could not create database $TARGET_DB_1"
+anastasis-dbinit -c $CONF_1 2> anastasis-dbinit_1.log
+dropdb $TARGET_DB_2 >/dev/null 2>/dev/null || true
+createdb $TARGET_DB_2 || exit_skip "Could not create database $TARGET_DB_2"
+anastasis-dbinit -c $CONF_2 2> anastasis-dbinit_2.log
+dropdb $TARGET_DB_3 >/dev/null 2>/dev/null || true
+createdb $TARGET_DB_3 || exit_skip "Could not create database $TARGET_DB_3"
+anastasis-dbinit -c $CONF_3 2> anastasis-dbinit_3.log
+dropdb $TARGET_DB_4 >/dev/null 2>/dev/null || true
+createdb $TARGET_DB_4 || exit_skip "Could not create database $TARGET_DB_4"
+anastasis-dbinit -c $CONF_4 2> anastasis-dbinit_4.log
+
+echo " OK"
+
+echo -n "Generating Taler auditor, exchange and merchant configurations ..."
+
+DATA_DIR=`taler-config -f -c $CONF -s PATHS -o TALER_HOME`
+rm -rf $DATA_DIR
+
+# obtain key configuration data
+MASTER_PRIV_FILE=`taler-config -f -c $CONF -s EXCHANGE -o MASTER_PRIV_FILE`
+MASTER_PRIV_DIR=`dirname $MASTER_PRIV_FILE`
+mkdir -p $MASTER_PRIV_DIR
+gnunet-ecc -g1 $MASTER_PRIV_FILE > /dev/null 2> /dev/null
+MASTER_PUB=`gnunet-ecc -p $MASTER_PRIV_FILE`
+EXCHANGE_URL=`taler-config -c $CONF -s EXCHANGE -o BASE_URL`
+MERCHANT_PORT=`taler-config -c $CONF -s MERCHANT -o PORT`
+MERCHANT_URL=http://localhost:${MERCHANT_PORT}/
+BANK_PORT=`taler-config -c $CONF -s BANK -o HTTP_PORT`
+BANK_URL=http://localhost:${BANK_PORT}/
+AUDITOR_URL=http://localhost:8083/
+AUDITOR_PRIV_FILE=`taler-config -f -c $CONF -s AUDITOR -o AUDITOR_PRIV_FILE`
+AUDITOR_PRIV_DIR=`dirname $AUDITOR_PRIV_FILE`
+mkdir -p $AUDITOR_PRIV_DIR
+gnunet-ecc -g1 $AUDITOR_PRIV_FILE > /dev/null 2> /dev/null
+AUDITOR_PUB=`gnunet-ecc -p $AUDITOR_PRIV_FILE`
+
+# patch configuration
+TALER_DB=talercheck
+taler-config -c $CONF -s exchange -o MASTER_PUBLIC_KEY -V $MASTER_PUB
+taler-config -c $CONF -s merchant-exchange-default -o MASTER_KEY -V $MASTER_PUB
+taler-config -c $CONF -s exchangedb-postgres -o CONFIG -V postgres:///$TALER_DB
+taler-config -c $CONF -s auditordb-postgres -o CONFIG -V postgres:///$TALER_DB
+taler-config -c $CONF -s merchantdb-postgres -o CONFIG -V postgres:///$TALER_DB
+taler-config -c $CONF -s bank -o database -V postgres:///$TALER_DB
+taler-config -c $CONF -s exchange -o KEYDIR -V "${TMP_DIR}/keydir/"
+taler-config -c $CONF -s exchange -o REVOCATION_DIR -V "${TMP_DIR}/revdir/"
+
+echo " OK"
+
+echo -n "Setting up exchange ..."
+
+# reset database
+dropdb $TALER_DB >/dev/null 2>/dev/null || true
+createdb $TALER_DB || exit_skip "Could not create database $TALER_DB"
+taler-exchange-dbinit -c $CONF
+taler-merchant-dbinit -c $CONF
+taler-auditor-dbinit -c $CONF
+taler-auditor-exchange -c $CONF -m $MASTER_PUB -u $EXCHANGE_URL
+
+echo " OK"
+
+# Launch services
+echo -n "Launching taler services ..."
+taler-bank-manage-testing $CONF postgres:///$TALER_DB serve > taler-bank.log 2> taler-bank.err &
+taler-exchange-secmod-eddsa -c $CONF 2> taler-exchange-secmod-eddsa.log &
+taler-exchange-secmod-rsa -c $CONF 2> taler-exchange-secmod-rsa.log &
+taler-exchange-httpd -c $CONF 2> taler-exchange-httpd.log &
+taler-merchant-httpd -c $CONF -L INFO 2> taler-merchant-httpd.log &
+taler-exchange-wirewatch -c $CONF 2> taler-exchange-wirewatch.log &
+taler-auditor-httpd -L INFO -c $CONF 2> taler-auditor-httpd.log &
+
+echo " OK"
+
+echo -n "Launching anastasis services ..."
+PREFIX="" #valgrind
+$PREFIX anastasis-httpd -c $CONF_1 2> anastasis-httpd_1.log &
+$PREFIX anastasis-httpd -c $CONF_2 2> anastasis-httpd_2.log &
+$PREFIX anastasis-httpd -c $CONF_3 2> anastasis-httpd_3.log &
+$PREFIX anastasis-httpd -c $CONF_4 2> anastasis-httpd_4.log &
+
+# Wait for bank to be available (usually the slowest)
+for n in `seq 1 50`
+do
+ echo -n "."
+ sleep 0.2
+ OK=0
+ # bank
+ wget --tries=1 --timeout=1 http://localhost:8082/ -o /dev/null -O /dev/null >/dev/null || continue
+ OK=1
+ break
+done
+
+if [ 1 != $OK ]
+then
+ exit_skip "Failed to launch services (bank)"
+fi
+
+# Wait for all other taler services to be available
+for n in `seq 1 50`
+do
+ echo -n "."
+ sleep 0.1
+ OK=0
+ # exchange
+ wget --tries=1 --timeout=1 http://localhost:8081/seed -o /dev/null -O /dev/null >/dev/null || continue
+ # merchant
+ wget --tries=1 --timeout=1 http://localhost:9966/ -o /dev/null -O /dev/null >/dev/null || continue
+ # auditor
+ wget --tries=1 --timeout=1 http://localhost:8083/ -o /dev/null -O /dev/null >/dev/null || continue
+ OK=1
+ break
+done
+
+if [ 1 != $OK ]
+then
+ exit_skip "Failed to launch taler services"
+fi
+
+echo "OK"
+
+echo -n "Setting up keys ..."
+taler-exchange-offline -c $CONF \
+ download \
+ sign \
+ enable-account payto://x-taler-bank/localhost/Exchange \
+ enable-auditor $AUDITOR_PUB $AUDITOR_URL "TESTKUDOS Auditor" \
+ wire-fee now x-taler-bank TESTKUDOS:0.01 TESTKUDOS:0.01 \
+ upload &> taler-exchange-offline.log
+
+echo -n "."
+
+for n in `seq 1 3`
+do
+ echo -n "."
+ OK=0
+ wget --tries=1 --timeout=1 http://localhost:8081/keys -o /dev/null -O /dev/null >/dev/null || continue
+ OK=1
+ break
+done
+
+if [ 1 != $OK ]
+then
+ exit_skip "Failed to setup keys"
+fi
+
+echo " OK"
+
+echo -n "Setting up auditor signatures ..."
+taler-auditor-offline -c $CONF \
+ download sign upload &> taler-auditor-offline.log
+echo " OK"
+
+echo -n "Waiting for anastasis services ..."
+
+# Wait for anastasis services to be available
+for n in `seq 1 50`
+do
+ echo -n "."
+ sleep 0.1
+ OK=0
+ # anastasis_01
+ wget --tries=1 --timeout=1 http://localhost:8086/ -o /dev/null -O /dev/null >/dev/null || continue
+ # anastasis_02
+ wget --tries=1 --timeout=1 http://localhost:8087/ -o /dev/null -O /dev/null >/dev/null || continue
+ # anastasis_03
+ wget --tries=1 --timeout=1 http://localhost:8088/ -o /dev/null -O /dev/null >/dev/null || continue
+ # anastasis_04
+ wget --tries=1 --timeout=1 http://localhost:8089/ -o /dev/null -O /dev/null >/dev/null || continue
+ OK=1
+ break
+done
+
+if [ 1 != $OK ]
+then
+ exit_skip "Failed to launch anastasis services"
+fi
+echo "OK"
+
+echo -n "Configuring merchant instance ..."
+# Setup merchant
+
+curl -H "Content-Type: application/json" -X POST -d '{"auth":{"method":"external"},"payto_uris":["payto://x-taler-bank/localhost/43"],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_ms" : 3600000},"default_pay_delay":{"d_ms": 3600000}}' http://localhost:9966/private/instances
+
+
+echo " DONE"
+
+echo -en $COLOR$BOLD"Test enter secret in a backup state ..."$NORM$NOCOLOR
+
+$PREFIX anastasis-reducer -a \
+ '{"secret": { "value" : "veryhardtoguesssecret", "mime" : "text/plain" } }' \
+ enter_secret resources/06-backup.json $TFILE
+
+STATE=`jq -r -e .backup_state < $TFILE`
+if test "$STATE" != "SECRET_EDITING"
+then
+ jq -e . $TFILE
+ exit_fail "Expected new state to be 'SECRET_EDITING', got '$STATE'"
+fi
+
+echo " DONE"
+echo -en $COLOR$BOLD"Test expiration change ..."$NORM$NOCOLOR
+
+MILLIS=`date '+%s'`000
+# Use 156 days into the future to get 1 year
+MILLIS=`expr $MILLIS + 13478400000`
+
+$PREFIX anastasis-reducer -a \
+ "$(jq -n '
+ {"expiration": { "t_ms" : $MSEC } }' \
+ --argjson MSEC $MILLIS
+ )" \
+ update_expiration $TFILE $UFILE
+
+STATE=`jq -r -e .backup_state < $UFILE`
+if test "$STATE" != "SECRET_EDITING"
+then
+ jq -e . $UFILE
+ exit_fail "Expected new state to be 'SECRET_EDITING', got '$STATE'"
+fi
+
+FEES=`jq -r -e '.upload_fees[0].fee' < $UFILE`
+# 4x 4.99 for annual fees, plus 4x0.01 for truth uploads
+if test "$FEES" != "TESTKUDOS:20"
+then
+ jq -e . $TFILE
+ exit_fail "Expected upload fees to be 'TESTKUDOS:20', got '$FEES'"
+fi
+
+
+echo " DONE"
+echo -en $COLOR$BOLD"Test advance to payment ..."$NORM$NOCOLOR
+
+$PREFIX anastasis-reducer next $UFILE $TFILE
+
+STATE=`jq -r -e .backup_state < $TFILE`
+if test "$STATE" != "TRUTHS_PAYING"
+then
+ jq -e . $TFILE
+ exit_fail "Expected new state to be 'TRUTHS_PAYING', got '$STATE'"
+fi
+
+TMETHOD=`jq -r -e '.policies[0].methods[0].truth.type' < $TFILE`
+if test $TMETHOD != "question"
+then
+ exit_fail "Expected method to be >='question', got $TMETHOD"
+fi
+
+echo " OK"
+#Pay
+
+echo -en $COLOR$BOLD"Withdrawing amount to wallet ..."$NORM$NOCOLOR
+
+rm $WALLET_DB
+taler-wallet-cli --no-throttle --wallet-db=$WALLET_DB api 'withdrawTestBalance' \
+ "$(jq -n '
+ {
+ amount: "TESTKUDOS:40",
+ bankBaseUrl: $BANK_URL,
+ exchangeBaseUrl: $EXCHANGE_URL
+ }' \
+ --arg BANK_URL "$BANK_URL" \
+ --arg EXCHANGE_URL "$EXCHANGE_URL"
+ )" 2>wallet.err >wallet.log
+taler-wallet-cli --wallet-db=$WALLET_DB run-until-done 2>wallet.err >wallet.log
+
+echo " OK"
+
+echo -en $COLOR$BOLD"Making payments for truth uploads ... "$NORM$NOCOLOR
+OBJECT_SIZE=`jq -r -e '.payments | length' < $TFILE`
+for ((INDEX=0; INDEX < $OBJECT_SIZE; INDEX++))
+do
+ PAY_URI=`jq --argjson INDEX $INDEX -r -e '.payments[$INDEX]' < $TFILE`
+ # run wallet CLI
+ echo -n "$INDEX"
+ taler-wallet-cli --wallet-db=$WALLET_DB handle-uri $PAY_URI -y 2>wallet.err >wallet.log
+ echo -n ","
+done
+echo " OK"
+echo -e $COLOR$BOLD"Running wallet run-pending..."$NORM$NOCOLOR
+taler-wallet-cli --wallet-db=$WALLET_DB run-pending 2>wallet.err >wallet.log
+echo -e $COLOR$BOLD"Payments done"$NORM$NOCOLOR
+
+
+echo -en $COLOR$BOLD"Try to upload again ..."$NORM$NOCOLOR
+$PREFIX anastasis-reducer pay $TFILE $UFILE
+mv $UFILE $TFILE
+echo " OK"
+
+
+STATE=`jq -r -e .backup_state < $TFILE`
+if test "$STATE" != "POLICIES_PAYING"
+then
+ exit_fail "Expected new state to be 'POLICIES_PAYING', got '$STATE'"
+fi
+
+export TFILE
+export UFILE
+
+echo -en $COLOR$BOLD"Making payments for policy uploads ... "$NORM$NOCOLOR
+OBJECT_SIZE=`jq -r -e '.policy_payment_requests | length' < $TFILE`
+for ((INDEX=0; INDEX < $OBJECT_SIZE; INDEX++))
+do
+ PAY_URI=`jq --argjson INDEX $INDEX -r -e '.policy_payment_requests[$INDEX].payto' < $TFILE`
+ # run wallet CLI
+ export PAY_URI
+ echo -n "$INDEX"
+ taler-wallet-cli --wallet-db=$WALLET_DB handle-uri $PAY_URI -y 2>wallet.err >wallet.log
+ echo -n ","
+done
+echo " OK"
+echo -e $COLOR$BOLD"Running wallet run-pending..."$NORM$NOCOLOR
+taler-wallet-cli --wallet-db=$WALLET_DB run-pending 2>wallet.err >wallet.log
+echo -e $COLOR$BOLD"Payments done"$NORM$NOCOLOR
+
+echo -en $COLOR$BOLD"Try to upload again ..."$NORM$NOCOLOR
+$PREFIX anastasis-reducer pay $TFILE $UFILE
+
+echo " OK"
+
+echo -n "Final checks ..."
+
+STATE=`jq -r -e .backup_state < $UFILE`
+if test "$STATE" != "BACKUP_FINISHED"
+then
+ exit_fail "Expected new state to be BACKUP_FINISHED, got $STATE"
+fi
+
+jq -r -e .core_secret < $UFILE > /dev/null && exit_fail "'core_secret' was not cleared upon success"
+
+echo " OK"
+
+exit 0
diff --git a/src/cli/test_anastasis_reducer_initialize_state.sh b/src/cli/test_anastasis_reducer_initialize_state.sh
new file mode 100755
index 0000000..9dc0c59
--- /dev/null
+++ b/src/cli/test_anastasis_reducer_initialize_state.sh
@@ -0,0 +1,64 @@
+#!/bin/bash
+
+set -eu
+
+# Exit, with status code "skip" (no 'real' failure)
+function exit_skip() {
+ echo " SKIP: $1"
+ exit 77
+}
+
+# Exit, with error message (hard failure)
+function exit_fail() {
+ echo " FAIL: $1"
+ exit 1
+}
+
+# Cleanup to run whenever we exit
+function cleanup()
+{
+ for n in `jobs -p`
+ do
+ kill $n 2> /dev/null || true
+ done
+ rm -f $SFILE $TFILE
+ wait
+}
+
+# Install cleanup handler (except for kill -9)
+SFILE=`mktemp test_reducer_stateXXXXXX`
+TFILE=`mktemp test_reducer_stateXXXXXX`
+
+trap cleanup EXIT
+
+# Check we can actually run
+echo -n "Testing for jq ..."
+jq -h > /dev/null || exit_skip "jq required"
+echo " FOUND"
+echo -n "Testing for anastasis-reducer ..."
+anastasis-reducer -h > /dev/null || exit_skip "anastasis-reducer required"
+echo " FOUND"
+echo -n "Test initialization of a backup state ..."
+anastasis-reducer -b $SFILE
+
+STATE=`jq -r -e .backup_state < $SFILE`
+if test "$STATE" != "CONTINENT_SELECTING"
+then
+ exit_fail "Expected initial state to be CONTINENT_SELECTING, got $STATE"
+fi
+jq -e .continents[0] < $SFILE > /dev/null || exit_fail "Expected initial state to include continents"
+
+echo " OK"
+
+echo -n "Test initialization of a recovery state ..."
+anastasis-reducer -r $TFILE
+
+STATE=`jq -r -e .recovery_state < $TFILE`
+if test "$STATE" != "CONTINENT_SELECTING"
+then
+ exit_fail "Expected initial state to be CONTINENT_SELECTING, got $STATE"
+fi
+jq -e .continents[0] < $TFILE > /dev/null || exit_fail "Expected initial state to include continents"
+echo " OK"
+
+exit 0
diff --git a/src/cli/test_anastasis_reducer_recovery_enter_user_attributes.sh b/src/cli/test_anastasis_reducer_recovery_enter_user_attributes.sh
new file mode 100755
index 0000000..d0562e2
--- /dev/null
+++ b/src/cli/test_anastasis_reducer_recovery_enter_user_attributes.sh
@@ -0,0 +1,516 @@
+#!/bin/bash
+
+set -eu
+
+# Exit, with status code "skip" (no 'real' failure)
+function exit_skip() {
+ echo " SKIP: $1"
+ exit 77
+}
+
+# Exit, with error message (hard failure)
+function exit_fail() {
+ echo " FAIL: $1"
+ exit 1
+}
+
+# Cleanup to run whenever we exit
+function cleanup()
+{
+ for n in `jobs -p`
+ do
+ kill $n 2> /dev/null || true
+ done
+ rm -rf $CONF $WALLET_DB $R1FILE $R2FILE $B1FILE $B2FILE $TMP_DIR
+ wait
+}
+
+
+CONF_1="test_anastasis_reducer_1.conf"
+CONF_2="test_anastasis_reducer_2.conf"
+CONF_3="test_anastasis_reducer_3.conf"
+CONF_4="test_anastasis_reducer_4.conf"
+
+
+# Exchange configuration file will be edited, so we create one
+# from the template.
+CONF=`mktemp test_reducerXXXXXX.conf`
+cp test_reducer.conf $CONF
+
+TMP_DIR=`mktemp -d keys-tmp-XXXXXX`
+WALLET_DB=`mktemp test_reducer_walletXXXXXX.json`
+B1FILE=`mktemp test_reducer_stateB1XXXXXX`
+B2FILE=`mktemp test_reducer_stateB2XXXXXX`
+R1FILE=`mktemp test_reducer_stateR1XXXXXX`
+R2FILE=`mktemp test_reducer_stateR2XXXXXX`
+
+# Install cleanup handler (except for kill -9)
+trap cleanup EXIT
+
+# Check we can actually run
+echo -n "Testing for jq"
+jq -h > /dev/null || exit_skip "jq required"
+echo " FOUND"
+echo -n "Testing for anastasis-reducer ..."
+anastasis-reducer -h > /dev/null || exit_skip "anastasis-reducer required"
+echo " FOUND"
+
+echo -n "Testing for taler"
+taler-exchange-httpd -h > /dev/null || exit_skip " taler-exchange required"
+taler-merchant-httpd -h > /dev/null || exit_skip " taler-merchant required"
+echo " FOUND"
+
+echo -n "Testing for taler-bank-manage"
+taler-bank-manage --help >/dev/null </dev/null || exit_skip " MISSING"
+echo " FOUND"
+echo -n "Testing for taler-wallet-cli"
+taler-wallet-cli -v >/dev/null </dev/null || exit_skip " MISSING"
+echo " FOUND"
+
+echo -n "Testing for anastasis-httpd"
+anastasis-httpd -h >/dev/null </dev/null || exit_skip " MISSING"
+echo " FOUND"
+
+echo -n "Initialize anastasis database ..."
+# Name of the Postgres database we will use for the script.
+# Will be dropped, do NOT use anything that might be used
+# elsewhere
+TARGET_DB_1=`anastasis-config -c $CONF_1 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"`
+TARGET_DB_2=`anastasis-config -c $CONF_2 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"`
+TARGET_DB_3=`anastasis-config -c $CONF_3 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"`
+TARGET_DB_4=`anastasis-config -c $CONF_4 -s stasis-postgres -o CONFIG | sed -e "s/^postgres:\/\/\///"`
+
+dropdb $TARGET_DB_1 >/dev/null 2>/dev/null || true
+createdb $TARGET_DB_1 || exit_skip "Could not create database $TARGET_DB_1"
+anastasis-dbinit -c $CONF_1 2> anastasis-dbinit_1.log
+dropdb $TARGET_DB_2 >/dev/null 2>/dev/null || true
+createdb $TARGET_DB_2 || exit_skip "Could not create database $TARGET_DB_2"
+anastasis-dbinit -c $CONF_2 2> anastasis-dbinit_2.log
+dropdb $TARGET_DB_3 >/dev/null 2>/dev/null || true
+createdb $TARGET_DB_3 || exit_skip "Could not create database $TARGET_DB_3"
+anastasis-dbinit -c $CONF_3 2> anastasis-dbinit_3.log
+dropdb $TARGET_DB_4 >/dev/null 2>/dev/null || true
+createdb $TARGET_DB_4 || exit_skip "Could not create database $TARGET_DB_4"
+anastasis-dbinit -c $CONF_4 2> anastasis-dbinit_4.log
+
+echo " OK"
+
+echo -n "Generating Taler auditor, exchange and merchant configurations ..."
+
+DATA_DIR=`taler-config -f -c $CONF -s PATHS -o TALER_HOME`
+rm -rf $DATA_DIR
+
+# obtain key configuration data
+MASTER_PRIV_FILE=`taler-config -f -c $CONF -s EXCHANGE -o MASTER_PRIV_FILE`
+MASTER_PRIV_DIR=`dirname $MASTER_PRIV_FILE`
+mkdir -p $MASTER_PRIV_DIR
+gnunet-ecc -g1 $MASTER_PRIV_FILE > /dev/null 2> /dev/null
+MASTER_PUB=`gnunet-ecc -p $MASTER_PRIV_FILE`
+EXCHANGE_URL=`taler-config -c $CONF -s EXCHANGE -o BASE_URL`
+MERCHANT_PORT=`taler-config -c $CONF -s MERCHANT -o PORT`
+MERCHANT_URL=http://localhost:${MERCHANT_PORT}/
+BANK_PORT=`taler-config -c $CONF -s BANK -o HTTP_PORT`
+BANK_URL=http://localhost:${BANK_PORT}/
+AUDITOR_URL=http://localhost:8083/
+AUDITOR_PRIV_FILE=`taler-config -f -c $CONF -s AUDITOR -o AUDITOR_PRIV_FILE`
+AUDITOR_PRIV_DIR=`dirname $AUDITOR_PRIV_FILE`
+mkdir -p $AUDITOR_PRIV_DIR
+gnunet-ecc -g1 $AUDITOR_PRIV_FILE > /dev/null 2> /dev/null
+AUDITOR_PUB=`gnunet-ecc -p $AUDITOR_PRIV_FILE`
+
+# patch configuration
+TALER_DB=talercheck
+taler-config -c $CONF -s exchange -o MASTER_PUBLIC_KEY -V $MASTER_PUB
+taler-config -c $CONF -s merchant-exchange-default -o MASTER_KEY -V $MASTER_PUB
+taler-config -c $CONF -s exchangedb-postgres -o CONFIG -V postgres:///$TALER_DB
+taler-config -c $CONF -s auditordb-postgres -o CONFIG -V postgres:///$TALER_DB
+taler-config -c $CONF -s merchantdb-postgres -o CONFIG -V postgres:///$TALER_DB
+taler-config -c $CONF -s bank -o database -V postgres:///$TALER_DB
+taler-config -c $CONF -s exchange -o KEYDIR -V "${TMP_DIR}/keydir/"
+taler-config -c $CONF -s exchange -o REVOCATION_DIR -V "${TMP_DIR}/revdir/"
+
+echo " OK"
+
+echo -n "Setting up exchange ..."
+
+# reset database
+dropdb $TALER_DB >/dev/null 2>/dev/null || true
+createdb $TALER_DB || exit_skip "Could not create database $TALER_DB"
+taler-exchange-dbinit -c $CONF
+taler-merchant-dbinit -c $CONF
+taler-auditor-dbinit -c $CONF
+taler-auditor-exchange -c $CONF -m $MASTER_PUB -u $EXCHANGE_URL
+
+echo " OK"
+
+# Launch services
+echo -n "Launching taler services ..."
+taler-bank-manage-testing $CONF postgres:///$TALER_DB serve > taler-bank.log 2> taler-bank.err &
+taler-exchange-secmod-eddsa -c $CONF 2> taler-exchange-secmod-eddsa.log &
+taler-exchange-secmod-rsa -c $CONF 2> taler-exchange-secmod-rsa.log &
+taler-exchange-httpd -c $CONF 2> taler-exchange-httpd.log &
+taler-merchant-httpd -c $CONF -L INFO 2> taler-merchant-httpd.log &
+taler-exchange-wirewatch -c $CONF 2> taler-exchange-wirewatch.log &
+taler-auditor-httpd -L INFO -c $CONF 2> taler-auditor-httpd.log &
+
+echo " OK"
+
+echo -n "Launching anastasis services ..."
+PREFIX="" #valgrind
+$PREFIX anastasis-httpd -c $CONF_1 2> anastasis-httpd_1.log &
+$PREFIX anastasis-httpd -c $CONF_2 2> anastasis-httpd_2.log &
+$PREFIX anastasis-httpd -c $CONF_3 2> anastasis-httpd_3.log &
+$PREFIX anastasis-httpd -c $CONF_4 2> anastasis-httpd_4.log &
+
+# Wait for bank to be available (usually the slowest)
+for n in `seq 1 50`
+do
+ echo -n "."
+ sleep 0.2
+ OK=0
+ # bank
+ wget --tries=1 --timeout=1 http://localhost:8082/ -o /dev/null -O /dev/null >/dev/null || continue
+ OK=1
+ break
+done
+
+if [ 1 != $OK ]
+then
+ exit_skip "Failed to launch services (bank)"
+fi
+
+# Wait for all other taler services to be available
+for n in `seq 1 50`
+do
+ echo -n "."
+ sleep 0.1
+ OK=0
+ # exchange
+ wget --tries=1 --timeout=1 http://localhost:8081/seed -o /dev/null -O /dev/null >/dev/null || continue
+ # merchant
+ wget --tries=1 --timeout=1 http://localhost:9966/ -o /dev/null -O /dev/null >/dev/null || continue
+ # auditor
+ wget --tries=1 --timeout=1 http://localhost:8083/ -o /dev/null -O /dev/null >/dev/null || continue
+ OK=1
+ break
+done
+
+if [ 1 != $OK ]
+then
+ exit_skip "Failed to launch taler services"
+fi
+
+echo "OK"
+
+echo -n "Setting up keys ..."
+taler-exchange-offline -c $CONF \
+ download \
+ sign \
+ enable-account payto://x-taler-bank/localhost/Exchange \
+ enable-auditor $AUDITOR_PUB $AUDITOR_URL "TESTKUDOS Auditor" \
+ wire-fee now x-taler-bank TESTKUDOS:0.01 TESTKUDOS:0.01 \
+ upload &> taler-exchange-offline.log
+
+echo -n "."
+
+for n in `seq 1 3`
+do
+ echo -n "."
+ OK=0
+ wget --tries=1 --timeout=1 http://localhost:8081/keys -o /dev/null -O /dev/null >/dev/null || continue
+ OK=1
+ break
+done
+
+if [ 1 != $OK ]
+then
+ exit_skip "Failed to setup keys"
+fi
+
+echo " OK"
+
+echo -n "Setting up auditor signatures ..."
+taler-auditor-offline -c $CONF \
+ download sign upload &> taler-auditor-offline.log
+echo " OK"
+
+echo -n "Waiting for anastasis services ..."
+
+# Wait for anastasis services to be available
+for n in `seq 1 50`
+do
+ echo -n "."
+ sleep 0.1
+ OK=0
+ # anastasis_01
+ wget --tries=1 --timeout=1 http://localhost:8086/ -o /dev/null -O /dev/null >/dev/null || continue
+ # anastasis_02
+ wget --tries=1 --timeout=1 http://localhost:8087/ -o /dev/null -O /dev/null >/dev/null || continue
+ # anastasis_03
+ wget --tries=1 --timeout=1 http://localhost:8088/ -o /dev/null -O /dev/null >/dev/null || continue
+ # anastasis_04
+ wget --tries=1 --timeout=1 http://localhost:8089/ -o /dev/null -O /dev/null >/dev/null || continue
+ OK=1
+ break
+done
+
+if [ 1 != $OK ]
+then
+ exit_skip "Failed to launch anastasis services"
+fi
+echo "OK"
+
+echo -n "Configuring merchant instance ..."
+# Setup merchant
+
+curl -H "Content-Type: application/json" -X POST -d '{"auth":{"method":"external"},"payto_uris":["payto://x-taler-bank/localhost/43"],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_ms" : 3600000},"default_pay_delay":{"d_ms": 3600000}}' http://localhost:9966/private/instances
+
+
+echo " DONE"
+
+echo -n "Running backup logic ...,"
+anastasis-reducer -b > $B1FILE
+echo -n "."
+anastasis-reducer -a \
+ '{"continent": "Testcontinent"}' \
+ select_continent < $B1FILE > $B2FILE
+echo -n "."
+anastasis-reducer -a \
+ '{"country_code": "xx",
+ "currencies":["TESTKUDOS"]}' \
+ select_country < $B2FILE > $B1FILE
+echo -n "."
+anastasis-reducer -a \
+ '{"identity_attributes": {
+ "full_name": "Max Musterman",
+ "sq_number": "4",
+ "birthdate": "2000-01-01"}}' \
+ enter_user_attributes < $B1FILE > $B2FILE
+echo -n ","
+# "91GPWWR" encodes "Hans"
+anastasis-reducer -a \
+ '{"authentication_method": {
+ "type": "question",
+ "instructions": "What is your name?",
+ "challenge": "91GPWWR"
+ } }' \
+ add_authentication < $B2FILE > $B1FILE
+echo -n "."
+# "64S36" encodes "123"
+anastasis-reducer -a \
+ '{"authentication_method": {
+ "type": "question",
+ "instructions": "How old are you?",
+ "challenge": "64S36"
+ } }' \
+ add_authentication < $B1FILE > $B2FILE
+echo -n "."
+# "9NGQ4WR" encodes "Mars"
+anastasis-reducer -a \
+ '{"authentication_method": {
+ "type": "question",
+ "instructions": "Where do you live?",
+ "challenge": "9NGQ4WR"
+ } }' \
+ add_authentication < $B2FILE > $B1FILE
+echo -n "."
+# Finished adding authentication methods
+anastasis-reducer \
+ next < $B1FILE > $B2FILE
+
+
+echo -n ","
+# Finished policy review
+anastasis-reducer \
+ next < $B2FILE > $B1FILE
+echo -n "."
+
+# Note: 'secret' must here be a Crockford base32-encoded value
+anastasis-reducer -a \
+ '{"secret": { "value" : "VERYHARDT0GVESSSECRET", "mime" : "text/plain" }}' \
+ enter_secret < $B1FILE > $B2FILE
+mv $B2FILE $B1FILE
+anastasis-reducer next $B1FILE $B2FILE
+echo " OK"
+
+
+echo -n "Preparing wallet"
+rm $WALLET_DB
+taler-wallet-cli --no-throttle --wallet-db=$WALLET_DB api 'withdrawTestBalance' \
+ "$(jq -n '
+ {
+ amount: "TESTKUDOS:100",
+ bankBaseUrl: $BANK_URL,
+ exchangeBaseUrl: $EXCHANGE_URL
+ }' \
+ --arg BANK_URL "$BANK_URL" \
+ --arg EXCHANGE_URL "$EXCHANGE_URL"
+ )" 2> /dev/null >/dev/null
+taler-wallet-cli --wallet-db=$WALLET_DB run-until-done 2>/dev/null >/dev/null
+echo " OK"
+
+echo -en "Making payments for truth uploads ... "
+OBJECT_SIZE=`jq -r -e '.payments | length' < $B2FILE`
+for ((INDEX=0; INDEX < $OBJECT_SIZE; INDEX++))
+do
+ PAY_URI=`jq --argjson INDEX $INDEX -r -e '.payments[$INDEX]' < $B2FILE`
+ # run wallet CLI
+ echo -n "$INDEX"
+ taler-wallet-cli --wallet-db=$WALLET_DB handle-uri $PAY_URI -y 2>/dev/null >/dev/null
+ echo -n ", "
+done
+echo "OK"
+echo -e "Running wallet run-pending..."
+taler-wallet-cli --wallet-db=$WALLET_DB run-pending 2>/dev/null >/dev/null
+echo -e "Payments done"
+
+export B2FILE
+export B1FILE
+
+echo -en "Try to upload again ..."
+$PREFIX anastasis-reducer pay $B2FILE $B1FILE
+mv $B1FILE $B2FILE
+echo " OK"
+
+echo -en "Making payments for policy uploads ... "
+OBJECT_SIZE=`jq -r -e '.policy_payment_requests | length' < $B2FILE`
+for ((INDEX=0; INDEX < $OBJECT_SIZE; INDEX++))
+do
+ PAY_URI=`jq --argjson INDEX $INDEX -r -e '.policy_payment_requests[$INDEX].payto' < $B2FILE`
+ # run wallet CLI
+ echo -n "$INDEX"
+ taler-wallet-cli --wallet-db=$WALLET_DB handle-uri $PAY_URI -y 2>/dev/null >/dev/null
+ echo -n ", "
+done
+echo " OK"
+echo -en "Running wallet run-pending..."
+taler-wallet-cli --wallet-db=$WALLET_DB run-pending 2>/dev/null >/dev/null
+echo -e " payments DONE"
+
+echo -en "Try to upload again ..."
+anastasis-reducer \
+ pay < $B2FILE > $B1FILE
+echo " OK: Backup finished"
+echo -n "Final backup checks ..."
+STATE=`jq -r -e .backup_state < $B1FILE`
+if test "$STATE" != "BACKUP_FINISHED"
+then
+ exit_fail "Expected new state to be 'BACKUP_FINISHED', got '$STATE'"
+fi
+
+jq -r -e .core_secret < $B1FILE > /dev/null && exit_fail "'core_secret' was not cleared upon success"
+
+echo " OK"
+
+echo -n "Running recovery basic logic ..."
+anastasis-reducer -r > $R1FILE
+anastasis-reducer -a \
+ '{"continent": "Testcontinent"}' \
+ select_continent < $R1FILE > $R2FILE
+anastasis-reducer -a \
+ '{"country_code": "xx",
+ "currencies":["TESTKUDOS"]}' \
+ select_country < $R2FILE > $R1FILE
+anastasis-reducer -a '{"identity_attributes": { "full_name": "Max Musterman", "sq_number": "4", "birthdate": "2000-01-01" }}' enter_user_attributes < $R1FILE > $R2FILE
+
+
+STATE=`jq -r -e .recovery_state < $R2FILE`
+if test "$STATE" != "SECRET_SELECTING"
+then
+ exit_fail "Expected new state to be 'SECRET_SELECTING', got '$STATE'"
+fi
+echo " OK"
+
+echo -n "Selecting default secret"
+mv $R2FILE $R1FILE
+anastasis-reducer next < $R1FILE > $R2FILE
+
+STATE=`jq -r -e .recovery_state < $R2FILE`
+if test "$STATE" != "CHALLENGE_SELECTING"
+then
+ exit_fail "Expected new state to be 'CHALLENGE_SELECTING', got '$STATE'"
+fi
+echo " OK"
+
+echo -n "Running challenge logic ..."
+
+UUID0=`jq -r -e .recovery_information.challenges[0].uuid < $R2FILE`
+UUID1=`jq -r -e .recovery_information.challenges[1].uuid < $R2FILE`
+UUID2=`jq -r -e .recovery_information.challenges[2].uuid < $R2FILE`
+UUID0Q=`jq -r -e .recovery_information.challenges[0].instructions < $R2FILE`
+UUID1Q=`jq -r -e .recovery_information.challenges[1].instructions < $R2FILE`
+UUID2Q=`jq -r -e .recovery_information.challenges[2].instructions < $R2FILE`
+
+if test "$UUID2Q" = 'How old are you?'
+then
+ AGE_UUID=$UUID2
+elif test "$UUID1Q" = 'How old are you?'
+then
+ AGE_UUID=$UUID1
+else
+ AGE_UUID=$UUID0
+fi
+
+if test "$UUID2Q" = 'What is your name?'
+then
+ NAME_UUID=$UUID2
+elif test "$UUID1Q" = 'What is your name?'
+then
+ NAME_UUID=$UUID1
+else
+ NAME_UUID=$UUID0
+fi
+
+anastasis-reducer -a \
+ "$(jq -n '
+ {
+ uuid: $UUID
+ }' \
+ --arg UUID "$NAME_UUID"
+ )" \
+ select_challenge < $R2FILE > $R1FILE
+
+anastasis-reducer -a '{"answer": "Hans"}' \
+ solve_challenge < $R1FILE > $R2FILE
+
+anastasis-reducer -a \
+ "$(jq -n '
+ {
+ uuid: $UUID
+ }' \
+ --arg UUID "$AGE_UUID"
+ )" \
+ select_challenge < $R2FILE > $R1FILE
+
+anastasis-reducer -a '{"answer": "123"}' \
+ solve_challenge < $R1FILE > $R2FILE
+
+echo " OK"
+
+echo -n "Checking recovered secret ..."
+# finally: check here that we recovered the secret...
+
+STATE=`jq -r -e .recovery_state < $R2FILE`
+if test "$STATE" != "RECOVERY_FINISHED"
+then
+ jq -e . $R2FILE
+ exit_fail "Expected new state to be 'RECOVERY_FINISHED', got '$STATE'"
+fi
+
+SECRET=`jq -r -e .core_secret.value < $R2FILE`
+if test "$SECRET" != "VERYHARDT0GVESSSECRET"
+then
+ jq -e . $R2FILE
+ exit_fail "Expected recovered secret to be 'VERYHARDT0GVESSSECRET', got '$SECRET'"
+fi
+
+MIME=`jq -r -e .core_secret.mime < $R2FILE`
+if test "$MIME" != "text/plain"
+then
+ jq -e . $R2FILE
+ exit_fail "Expected recovered mime to be 'text/plain', got '$MIME'"
+fi
+
+echo " OK"
+
+exit 0
diff --git a/src/cli/test_anastasis_reducer_select_continent.sh b/src/cli/test_anastasis_reducer_select_continent.sh
new file mode 100755
index 0000000..4cd8a84
--- /dev/null
+++ b/src/cli/test_anastasis_reducer_select_continent.sh
@@ -0,0 +1,116 @@
+#!/bin/bash
+
+set -eu
+
+# Exit, with status code "skip" (no 'real' failure)
+function exit_skip() {
+ echo " SKIP: $1"
+ exit 77
+}
+
+# Exit, with error message (hard failure)
+function exit_fail() {
+ echo " FAIL: $1"
+ exit 1
+}
+
+# Cleanup to run whenever we exit
+function cleanup()
+{
+ for n in `jobs -p`
+ do
+ kill $n 2> /dev/null || true
+ done
+ rm -f $SFILE $TFILE
+ wait
+}
+
+# Install cleanup handler (except for kill -9)
+SFILE=`mktemp test_reducer_stateXXXXXX`
+TFILE=`mktemp test_reducer_stateXXXXXX`
+
+trap cleanup EXIT
+
+# Check we can actually run
+echo -n "Testing for jq ..."
+jq -h > /dev/null || exit_skip "jq required"
+echo " FOUND"
+echo -n "Testing for anastasis-reducer ..."
+anastasis-reducer -h > /dev/null || exit_skip "anastasis-reducer required"
+echo " FOUND"
+
+# Test continent selection in a backup state
+echo -n "Test continent selection in a backup state ..."
+anastasis-reducer -a '{"continent": "Testcontinent"}' select_continent resources/00-backup.json $TFILE
+
+STATE=`jq -r -e .backup_state < $TFILE`
+if test "$STATE" != "COUNTRY_SELECTING"
+then
+ exit_fail "Expected new state to be COUNTRY_SELECTING, got $STATE"
+fi
+SELECTED_CONTINENT=`jq -r -e .selected_continent < $TFILE`
+if test "$SELECTED_CONTINENT" != "Testcontinent"
+then
+ exit_fail "Expected selected continent to be Testcontinent, got $SELECTED_CONTINENT"
+fi
+COUNTRIES=`jq -r -e .countries < $TFILE`
+if test "$COUNTRIES" == NULL
+then
+ exit_fail "Expected country array (countries) not to be NULL"
+fi
+echo " OK"
+
+
+echo -n "Test invalid continent selection ..."
+anastasis-reducer -a '{"continent": "Pangaia"}' select_continent resources/00-recovery.json $TFILE 2> /dev/null \
+ && exit_fail "Expected selection to fail. Check '$TFILE'"
+
+echo " OK"
+
+echo -n "Test continent selection in a recovery state ..."
+anastasis-reducer -a '{"continent": "Testcontinent"}' select_continent resources/00-recovery.json $TFILE
+
+STATE=`jq -r -e .recovery_state < $TFILE`
+if test "$STATE" != "COUNTRY_SELECTING"
+then
+ exit_fail "Expected new state to be COUNTRY_SELECTING, got $STATE"
+fi
+jq -e .countries[0] < $TFILE > /dev/null || exit_fail "Expected new state to include countries"
+jq -e .countries[0].code < $TFILE > /dev/null || exit_fail "Expected new state to include countries with code"
+jq -e .countries[0].continent < $TFILE > /dev/null || exit_fail "Expected new state to include countries with continent"
+jq -e .countries[0].name < $TFILE > /dev/null || exit_fail "Expected new state to include countries with name"
+jq -e .countries[0].currency < $TFILE > /dev/null || exit_fail "Expected new state to include countries with currency"
+
+SELECTED_CONTINENT=`jq -r -e .selected_continent < $TFILE`
+if test "$SELECTED_CONTINENT" != "Testcontinent"
+then
+ exit_fail "Expected selected continent to be 'Testcontinent', got $SELECTED_CONTINENT"
+fi
+
+COUNTRIES=`jq -r -e .countries < $TFILE`
+if test "$COUNTRIES" == NULL
+then
+ exit_fail "Expected country array (countries) not to be NULL"
+fi
+jq -e .countries[0] < $TFILE > /dev/null || exit_fail "Expected new state to include countries"
+jq -e .countries[0].code < $TFILE > /dev/null || exit_fail "Expected new state to include countries with code"
+jq -e .countries[0].continent < $TFILE > /dev/null || exit_fail "Expected new state to include countries with continent"
+jq -e .countries[0].name < $TFILE > /dev/null || exit_fail "Expected new state to include countries with name"
+jq -e .countries[0].currency < $TFILE > /dev/null || exit_fail "Expected new state to include countries with currency"
+
+echo " OK"
+
+
+# Test missing arguments in a recovery state
+echo -n "Test bogus country selection in a recovery state ..."
+anastasis-reducer -a '{"country": "Germany"}' select_continent resources/00-recovery.json $TFILE 2> /dev/null && exit_fail "Expected state transition to fail, but it worked, check $TFILE"
+
+echo " OK"
+
+# Test continent selection in a recovery state
+echo -n "Test bogus continent selection in a recovery state ..."
+anastasis-reducer -a '{"continent": "Germany"}' select_continent resources/00-recovery.json $TFILE 2> /dev/null && exit_fail "Expected state transition to fail, but it worked, check $TFILE"
+
+echo " OK"
+
+exit 0
diff --git a/src/cli/test_anastasis_reducer_select_country.sh b/src/cli/test_anastasis_reducer_select_country.sh
new file mode 100755
index 0000000..db17052
--- /dev/null
+++ b/src/cli/test_anastasis_reducer_select_country.sh
@@ -0,0 +1,144 @@
+#!/bin/bash
+
+set -eu
+
+# Exit, with status code "skip" (no 'real' failure)
+function exit_skip() {
+ echo " SKIP: $1"
+ exit 77
+}
+
+# Exit, with error message (hard failure)
+function exit_fail() {
+ echo " FAIL: $1"
+ exit 1
+}
+
+# Cleanup to run whenever we exit
+function cleanup()
+{
+ for n in `jobs -p`
+ do
+ kill $n 2> /dev/null || true
+ done
+ rm -f $TFILE
+ wait
+}
+
+
+
+TFILE=`mktemp test_reducer_stateXXXXXX`
+
+# Install cleanup handler (except for kill -9)
+trap cleanup EXIT
+
+# Check we can actually run
+echo -n "Testing for jq"
+jq -h > /dev/null || exit_skip "jq required"
+echo " FOUND"
+
+echo -n "Testing for anastasis-reducer ..."
+anastasis-reducer -h > /dev/null || exit_skip "anastasis-reducer required"
+echo " FOUND"
+
+
+
+# Test continent re-selection
+echo -n "Test continent re-selection ..."
+anastasis-reducer -a '{"continent": "Europe"}' select_continent resources/01-recovery.json $TFILE
+
+echo -n "."
+
+
+STATE=`jq -r -e .recovery_state < $TFILE`
+if test "$STATE" != "COUNTRY_SELECTING"
+then
+ exit_fail "Expected new state to be COUNTRY_SELECTING, got $STATE"
+fi
+
+echo -n "."
+
+jq -e .countries[0] < $TFILE > /dev/null || exit_fail "Expected new state to include countries"
+jq -e .countries[0].code < $TFILE > /dev/null || exit_fail "Expected new state to include countries with code"
+jq -e .countries[0].continent < $TFILE > /dev/null || exit_fail "Expected new state to include countries with continent"
+jq -e .countries[0].name < $TFILE > /dev/null || exit_fail "Expected new state to include countries with name"
+jq -e .countries[0].currency < $TFILE > /dev/null || exit_fail "Expected new state to include countries with currency"
+
+SELECTED_CONTINENT=`jq -r -e .selected_continent < $TFILE`
+if test "$SELECTED_CONTINENT" != "Europe"
+then
+ exit_fail "Expected selected continent to be 'Testcontinent', got $SELECTED_CONTINENT"
+fi
+
+echo " OK"
+
+
+echo -n "Test invalid continent re-selection ..."
+anastasis-reducer -a '{"continent": "Pangaia"}' select_continent resources/00-recovery.json $TFILE 2> /dev/null \
+ && exit_fail "Expected selection to fail. Check '$TFILE'"
+
+echo " OK"
+
+
+echo -n "Test NX country selection ..."
+
+anastasis-reducer -a \
+ '{"country_code": "zz",
+ "currencies": ["EUR" ]}' \
+ select_country \
+ resources/01-backup.json $TFILE 2> /dev/null \
+ && exit_fail "Expected selection to fail. Check '$TFILE'"
+
+echo " OK"
+
+echo -n "Test invalid country selection for continent ..."
+
+anastasis-reducer -a \
+ '{"country_code": "de",
+ "currencies":["EUR"]}' \
+ select_country \
+ resources/01-backup.json $TFILE 2> /dev/null \
+ && exit_fail "Expected selection to fail. Check '$TFILE'"
+
+echo " OK"
+
+echo -n "Test country selection ..."
+
+anastasis-reducer -a \
+ '{"country_code": "xx",
+ "currencies":["TESTKUDOS"]}' \
+ select_country resources/01-backup.json $TFILE
+
+STATE=`jq -r -e .backup_state < $TFILE`
+if test "$STATE" != "USER_ATTRIBUTES_COLLECTING"
+then
+ exit_fail "Expected new state to be 'USER_ATTRIBUTES_COLLECTING', got '$STATE'"
+fi
+echo -n "."
+SELECTED_COUNTRY=`jq -r -e .selected_country < $TFILE`
+if test "$SELECTED_COUNTRY" != "xx"
+then
+ exit_fail "Expected selected country to be 'xx', got '$SELECTED_COUNTRY'"
+fi
+echo -n "."
+SELECTED_CURRENCY=`jq -r -e .currencies[0] < $TFILE`
+if test "$SELECTED_CURRENCY" != "TESTKUDOS"
+then
+ exit_fail "Expected selected currency to be 'TESTKUDOS', got '$SELECTED_CURRENCY'"
+fi
+echo -n "."
+REQ_ATTRIBUTES=`jq -r -e .required_attributes < $TFILE`
+if test "$REQ_ATTRIBUTES" == NULL
+then
+ exit_fail "Expected required attributes array not to be NULL"
+fi
+echo -n "."
+AUTH_PROVIDERS=`jq -r -e .authentication_providers < $TFILE`
+if test "$AUTH_PROVIDERS" == NULL
+then
+ exit_fail "Expected authentication_providers array not to be NULL"
+fi
+
+echo " OK"
+
+exit 0
diff --git a/src/cli/test_reducer.conf b/src/cli/test_reducer.conf
new file mode 100644
index 0000000..a4baaed
--- /dev/null
+++ b/src/cli/test_reducer.conf
@@ -0,0 +1,197 @@
+[PATHS]
+TALER_HOME = ${PWD}/test_reducer_home/
+TALER_DATA_HOME = $TALER_HOME/.local/share/taler/
+TALER_CONFIG_HOME = $TALER_HOME/.config/taler/
+TALER_CACHE_HOME = $TALER_HOME/.cache/taler/
+TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/${USER:-}/taler-system-runtime/
+
+[taler]
+CURRENCY = TESTKUDOS
+CURRENCY_ROUND_UNIT = TESTKUDOS:0.01
+
+[anastasis]
+DB = postgres
+PAYMENT_BACKEND_URL = http://localhost:9966/
+ANNUAL_FEE = TESTKUDOS:4.99
+TRUTH_UPLOAD_FEE = TESTKUDOS:0.01
+UPLOAD_LIMIT_MB = 1
+ANNUAL_POLICY_UPLOAD_LIMIT = 128
+INSURANCE = TESTKUDOS:1.0
+
+[authorization-question]
+COST = TESTKUDOS:0.0
+
+
+[exchange]
+MAX_KEYS_CACHING = forever
+DB = postgres
+MASTER_PRIV_FILE = ${TALER_DATA_HOME}/exchange/offline-keys/master.priv
+SERVE = tcp
+UNIXPATH = ${TALER_RUNTIME_DIR}/exchange.http
+UNIXPATH_MODE = 660
+PORT = 8081
+BASE_URL = http://localhost:8081/
+SIGNKEY_DURATION = 2 weeks
+SIGNKEY_LEGAL_DURATION = 2 years
+LEGAL_DURATION = 2 years
+LOOKAHEAD_SIGN = 3 weeks 1 day
+LOOKAHEAD_PROVIDE = 2 weeks 1 day
+KEYDIR = ${TALER_DATA_HOME}/exchange/live-keys/
+REVOCATION_DIR = ${TALER_DATA_HOME}/exchange/revocations/
+TERMS_ETAG = 0
+PRIVACY_ETAG = 0
+
+[merchant]
+SERVE = tcp
+PORT = 9966
+UNIXPATH = ${TALER_RUNTIME_DIR}/merchant.http
+UNIXPATH_MODE = 660
+DEFAULT_WIRE_FEE_AMORTIZATION = 1
+DB = postgres
+WIREFORMAT = default
+# Set very low, so we can be sure that the database generated
+# will contain wire transfers "ready" for the aggregator.
+WIRE_TRANSFER_DELAY = 1 minute
+DEFAULT_PAY_DEADLINE = 1 day
+DEFAULT_MAX_DEPOSIT_FEE = TESTKUDOS:0.1
+KEYFILE = ${TALER_DATA_HOME}/merchant/merchant.priv
+DEFAULT_MAX_WIRE_FEE = TESTKUDOS:0.10
+
+# Ensure that merchant reports EVERY deposit confirmation to auditor
+FORCE_AUDIT = YES
+
+[auditor]
+DB = postgres
+AUDITOR_PRIV_FILE = ${TALER_DATA_HOME}/auditor/offline-keys/auditor.priv
+SERVE = tcp
+UNIXPATH = ${TALER_RUNTIME_DIR}/exchange.http
+UNIXPATH_MODE = 660
+PORT = 8083
+AUDITOR_URL = http://localhost:8083/
+TINY_AMOUNT = TESTKUDOS:0.01
+AUDITOR_PRIV_FILE = ${TALER_DATA_HOME}/auditor/offline-keys/auditor.priv
+BASE_URL = "http://localhost:8083/"
+
+[bank]
+DATABASE = postgres:///taler-auditor-basedb
+MAX_DEBT = TESTKUDOS:50.0
+MAX_DEBT_BANK = TESTKUDOS:100000.0
+HTTP_PORT = 8082
+SUGGESTED_EXCHANGE = http://localhost:8081/
+SUGGESTED_EXCHANGE_PAYTO = payto://x-taler-bank/localhost/2
+ALLOW_REGISTRATIONS = YES
+SERVE = http
+
+[exchangedb]
+IDLE_RESERVE_EXPIRATION_TIME = 4 weeks
+LEGAL_RESERVE_EXPIRATION_TIME = 7 years
+
+[exchange-account-1]
+PAYTO_URI = payto://x-taler-bank/localhost/Exchange
+enable_debit = yes
+enable_credit = yes
+WIRE_GATEWAY_URL = "http://localhost:8082/taler-wire-gateway/Exchange/"
+WIRE_GATEWAY_AUTH_METHOD = basic
+USERNAME = Exchange
+PASSWORD = x
+
+[merchant-exchange-default]
+EXCHANGE_BASE_URL = http://localhost:8081/
+CURRENCY = TESTKUDOS
+
+[payments-generator]
+currency = TESTKUDOS
+instance = default
+bank = http://localhost:8082/
+merchant = http://localhost:9966/
+exchange_admin = http://localhost:18080/
+exchange-admin = http://localhost:18080/
+exchange = http://localhost:8081/
+
+[coin_kudos_ct_1]
+value = TESTKUDOS:0.01
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = TESTKUDOS:0.01
+fee_deposit = TESTKUDOS:0.01
+fee_refresh = TESTKUDOS:0.01
+fee_refund = TESTKUDOS:0.01
+rsa_keysize = 1024
+
+[coin_kudos_ct_10]
+value = TESTKUDOS:0.10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = TESTKUDOS:0.01
+fee_deposit = TESTKUDOS:0.01
+fee_refresh = TESTKUDOS:0.03
+fee_refund = TESTKUDOS:0.01
+rsa_keysize = 1024
+
+[coin_kudos_1]
+value = TESTKUDOS:1
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = TESTKUDOS:0.02
+fee_deposit = TESTKUDOS:0.02
+fee_refresh = TESTKUDOS:0.03
+fee_refund = TESTKUDOS:0.01
+rsa_keysize = 1024
+
+[coin_kudos_2]
+value = TESTKUDOS:2
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = TESTKUDOS:0.03
+fee_deposit = TESTKUDOS:0.03
+fee_refresh = TESTKUDOS:0.04
+fee_refund = TESTKUDOS:0.02
+rsa_keysize = 1024
+
+[coin_kudos_4]
+value = TESTKUDOS:4
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = TESTKUDOS:0.03
+fee_deposit = TESTKUDOS:0.03
+fee_refresh = TESTKUDOS:0.04
+fee_refund = TESTKUDOS:0.02
+rsa_keysize = 1024
+
+[coin_kudos_5]
+value = TESTKUDOS:5
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = TESTKUDOS:0.01
+fee_deposit = TESTKUDOS:0.01
+fee_refresh = TESTKUDOS:0.03
+fee_refund = TESTKUDOS:0.01
+rsa_keysize = 1024
+
+[coin_kudos_8]
+value = TESTKUDOS:8
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = TESTKUDOS:0.05
+fee_deposit = TESTKUDOS:0.02
+fee_refresh = TESTKUDOS:0.03
+fee_refund = TESTKUDOS:0.04
+rsa_keysize = 1024
+
+[coin_kudos_10]
+value = TESTKUDOS:10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = TESTKUDOS:0.01
+fee_deposit = TESTKUDOS:0.01
+fee_refresh = TESTKUDOS:0.03
+fee_refund = TESTKUDOS:0.01
+rsa_keysize = 1024
diff --git a/src/cli/user-details-example.json b/src/cli/user-details-example.json
new file mode 100644
index 0000000..3eb1f7a
--- /dev/null
+++ b/src/cli/user-details-example.json
@@ -0,0 +1,6 @@
+{
+ "first-name":"John",
+ "last-name":"Wayne",
+ "birthdate":"01-01-1901",
+ "social-security-number":"123456789"
+} \ No newline at end of file
diff --git a/src/include/Makefile.am b/src/include/Makefile.am
new file mode 100644
index 0000000..f9f6548
--- /dev/null
+++ b/src/include/Makefile.am
@@ -0,0 +1,15 @@
+# This Makefile.am is in the public domain
+anastasisincludedir = $(includedir)/anastasis
+
+anastasisinclude_HEADERS = \
+ platform.h gettext.h \
+ anastasis_database_plugin.h \
+ anastasis_service.h \
+ anastasis_error_codes.h \
+ anastasis_database_lib.h \
+ anastasis_util_lib.h \
+ anastasis_crypto_lib.h \
+ anastasis_redux.h \
+ anastasis_authorization_plugin.h \
+ anastasis_authorization_lib.h \
+ anastasis.h
diff --git a/src/include/anastasis.h b/src/include/anastasis.h
new file mode 100644
index 0000000..1591106
--- /dev/null
+++ b/src/include/anastasis.h
@@ -0,0 +1,986 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 2021 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file include/anastasis.h
+ * @brief anastasis high-level client api
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ */
+#ifndef ANASTASIS_H
+#define ANASTASIS_H
+
+#include "anastasis_service.h"
+#include <taler/taler_json_lib.h>
+#include <gnunet/gnunet_util_lib.h>
+#include <stdbool.h>
+
+
+/* ********************* Recovery api *********************** */
+
+/**
+ * Defines the instructions for a challenge, what does the user have
+ * to do to fulfill the challenge. Also defines the method and other
+ * information for the challenge like a link for the video indent or a
+ * information to which address an e-mail was sent.
+ */
+struct ANASTASIS_Challenge;
+
+
+/**
+ * Defines the instructions for a challenge, what does the user have
+ * to do to fulfill the challenge. Also defines the method and other
+ * information for the challenge like a link for the video indent or a
+ * information to which address an e-mail was sent.
+ */
+struct ANASTASIS_ChallengeDetails
+{
+
+ /**
+ * UUID which identifies this challenge
+ */
+ struct ANASTASIS_CRYPTO_TruthUUIDP uuid;
+
+ /**
+ * Which type is this challenge (E-Mail, Security Question, SMS...)
+ */
+ const char *type;
+
+ /**
+ * Defines the base URL of the Anastasis provider used for the challenge.
+ */
+ const char *provider_url;
+
+ /**
+ * Instructions for solving the challenge (generic, set client-side
+ * when challenge was established).
+ */
+ const char *instructions;
+
+ /**
+ * true if challenged was already solved, else false.
+ */
+ bool solved;
+
+};
+
+
+/**
+ * Return public details about a challenge.
+ *
+ * @param challenge the challenge to inspect
+ * @return public details about the challenge
+ */
+const struct ANASTASIS_ChallengeDetails *
+ANASTASIS_challenge_get_details (struct ANASTASIS_Challenge *challenge);
+
+
+/**
+ * Possible outcomes of trying to start a challenge operation.
+ */
+enum ANASTASIS_ChallengeStatus
+{
+
+ /**
+ * The challenge has been solved.
+ */
+ ANASTASIS_CHALLENGE_STATUS_SOLVED,
+
+ /**
+ * Instructions for how to solve the challenge are provided. Also
+ * used if the answer we provided was wrong (or if no answer was
+ * provided, but one is needed).
+ */
+ ANASTASIS_CHALLENGE_STATUS_INSTRUCTIONS,
+
+ /**
+ * A redirection URL needed to solve the challenge is provided. Also
+ * used if the answer we provided was wrong (or if no answer was
+ * provided, but one is needed).
+ */
+ ANASTASIS_CHALLENGE_STATUS_REDIRECT_FOR_AUTHENTICATION,
+
+ /**
+ * Payment is required before the challenge can be answered.
+ */
+ ANASTASIS_CHALLENGE_STATUS_PAYMENT_REQUIRED,
+
+ /**
+ * We encountered an error talking to the Anastasis service.
+ */
+ ANASTASIS_CHALLENGE_STATUS_SERVER_FAILURE,
+
+ /**
+ * The server does not know this truth.
+ */
+ ANASTASIS_CHALLENGE_STATUS_TRUTH_UNKNOWN,
+
+ /**
+ * The rate limit for solving the challenge was exceeded.
+ */
+ ANASTASIS_CHALLENGE_STATUS_RATE_LIMIT_EXCEEDED
+
+};
+
+
+/**
+ * Response from an #ANASTASIS_challenge_start() operation.
+ */
+struct ANASTASIS_ChallengeStartResponse
+{
+ /**
+ * What is our status on satisfying this challenge. Determines @e details.
+ */
+ enum ANASTASIS_ChallengeStatus cs;
+
+ /**
+ * Which challenge is this about?
+ */
+ struct ANASTASIS_Challenge *challenge;
+
+ /**
+ * Details depending on @e cs
+ */
+ union
+ {
+
+ /**
+ * Challenge details provided if
+ * @e cs is #ANASTASIS_CHALLENGE_STATUS_INSTRUCTIONS.
+ */
+ struct
+ {
+
+ /**
+ * Response with server-side instructions for the user.
+ */
+ const void *body;
+
+ /**
+ * Mime type of the data in @e body.
+ */
+ const char *content_type;
+
+ /**
+ * Number of bytes in @e body
+ */
+ size_t body_size;
+
+ /**
+ * HTTP status returned by the server. #MHD_HTTP_ALREADY_REPORTED
+ * if the server did already send the challenge to the user,
+ * #MHD_HTTP_FORBIDDEN if the answer was wrong (or missing).
+ */
+ unsigned int http_status;
+ } open_challenge;
+
+
+ /**
+ * Response with URL to redirect the user to, if
+ * @e cs is #ANASTASIS_CHALLENGE_STATUS_REDIRECT_FOR_AUTHENTICATION.
+ */
+ const char *redirect_url;
+
+ /**
+ * Response with instructions for how to pay, if
+ * @e cs is #ANASTASIS_CHALLENGE_STATUS_PAYMENT_REQUIRED.
+ */
+ struct
+ {
+
+ /**
+ * "taler://pay" URI with details how to pay for the challenge.
+ */
+ const char *taler_pay_uri;
+
+ /**
+ * Payment secret from @e taler_pay_uri.
+ */
+ struct ANASTASIS_PaymentSecretP payment_secret;
+
+ } payment_required;
+
+
+ /**
+ * Response with details about a server-side failure, if
+ * @e cs is #ANASTASIS_CHALLENGE_STATUS_SERVER_FAILURE.
+ */
+ struct
+ {
+
+ /**
+ * HTTP status returned by the server.
+ */
+ unsigned int http_status;
+
+ /**
+ * Taler-specific error code.
+ */
+ enum TALER_ErrorCode ec;
+
+ } server_failure;
+
+ } details;
+};
+
+
+/**
+ * Defines a callback for the response status for a challenge start
+ * operation.
+ *
+ * @param cls closure
+ * @param csr response details
+ */
+typedef void
+(*ANASTASIS_AnswerFeedback)(
+ void *cls,
+ const struct ANASTASIS_ChallengeStartResponse *csr);
+
+
+/**
+ * User starts a challenge which reponds out of bounds (E-Mail, SMS,
+ * Postal..) If the challenge is zero cost, the challenge
+ * instructions will be sent to the client. If the challenge needs
+ * payment a payment link is sent to the client. After payment the
+ * challenge start method has to be called again.
+ *
+ * @param c reference to the escrow challenge which is started
+ * @param psp payment secret, NULL if no payment was yet made
+ * @param timeout how long to wait for payment
+ * @param hashed_answer answer to the challenge, NULL if we have none yet
+ * @param af reference to the answerfeedback which is passed back to the user
+ * @param af_cls closure for @a af
+ * @return #GNUNET_OK if the challenge was successfully started
+ */
+int
+ANASTASIS_challenge_start (struct ANASTASIS_Challenge *c,
+ const struct ANASTASIS_PaymentSecretP *psp,
+ struct GNUNET_TIME_Relative timeout,
+ const struct GNUNET_HashCode *hashed_answer,
+ ANASTASIS_AnswerFeedback af,
+ void *af_cls);
+
+
+/**
+ * Challenge answer for a security question. Is referenced to
+ * a challenge and sends back an AnswerFeedback. Convenience
+ * wrapper around #ANASTASIS_challenge_start that hashes @a answer
+ * for security questions.
+ *
+ * @param c reference to the challenge which is answered
+ * @param psp information about payment made for the recovery
+ * @param timeout how long to wait for payment
+ * @param answer user input instruction defines which input is needed
+ * @param af reference to the answerfeedback which is passed back to the user
+ * @param af_cls closure for @a af
+ * @return #GNUNET_OK on success
+ */
+int
+ANASTASIS_challenge_answer (struct ANASTASIS_Challenge *c,
+ const struct ANASTASIS_PaymentSecretP *psp,
+ struct GNUNET_TIME_Relative timeout,
+ const char *answer,
+ ANASTASIS_AnswerFeedback af,
+ void *af_cls);
+
+
+/**
+ * Challenge answer from the user like input SMS TAN or e-mail wpin. Is
+ * referenced to a challenge and sends back an AnswerFeedback.
+ * Convenience wrapper around #ANASTASIS_challenge_start that hashes
+ * numeric (unsalted) @a answer. Variant for numeric answers.
+ *
+ * @param c reference to the challenge which is answered
+ * @param psp information about payment made for the recovery
+ * @param timeout how long to wait for payment
+ * @param answer user input instruction defines which input is needed
+ * @param af reference to the answerfeedback which is passed back to the user
+ * @param af_cls closure for @a af
+ * @return #GNUNET_OK on success
+ */
+int
+ANASTASIS_challenge_answer2 (struct ANASTASIS_Challenge *c,
+ const struct ANASTASIS_PaymentSecretP *psp,
+ struct GNUNET_TIME_Relative timeout,
+ uint64_t answer,
+ ANASTASIS_AnswerFeedback af,
+ void *af_cls);
+
+
+/**
+ * Abort answering challenge.
+ *
+ * @param c reference to the escrow challenge which was started
+ */
+void
+ANASTASIS_challenge_abort (struct ANASTASIS_Challenge *c);
+
+
+/**
+ * Defines a Decryption Policy with multiple escrow methods
+ */
+struct ANASTASIS_DecryptionPolicy
+{
+ /**
+ * Array of challenges needed to solve for this decryption policy.
+ */
+ struct ANASTASIS_Challenge **challenges;
+
+ /**
+ * Length of the @a challenges in this policy.
+ */
+ unsigned int challenges_length;
+
+};
+
+
+/**
+ * Defines the recovery information (possible policies and version of the recovery document)
+ */
+struct ANASTASIS_RecoveryInformation
+{
+
+ /**
+ * Array of @e dps_len policies that would allow recovery of the core secret.
+ */
+ struct ANASTASIS_DecryptionPolicy **dps;
+
+ /**
+ * Array of all @e cs_len challenges to be solved (for any of the policies).
+ */
+ struct ANASTASIS_Challenge **cs;
+
+ /**
+ * Name of the secret being recovered, possibly NULL.
+ */
+ const char *secret_name;
+
+ /**
+ * Length of the @e dps array.
+ */
+ unsigned int dps_len;
+
+ /**
+ * Length of the @e cs array.
+ */
+ unsigned int cs_len;
+
+ /**
+ * Actual recovery document version obtained.
+ */
+ unsigned int version;
+};
+
+
+/**
+ * Callback which passes back the recovery document and its possible
+ * policies. Also passes back the version of the document for the user
+ * to check.
+ *
+ * @param cls closure for the callback
+ * @param ri recovery information struct which contains the policies
+ */
+typedef void
+(*ANASTASIS_PolicyCallback)(void *cls,
+ const struct ANASTASIS_RecoveryInformation *ri);
+
+
+/**
+ * Possible outcomes of a recovery process.
+ */
+enum ANASTASIS_RecoveryStatus
+{
+
+ /**
+ * Recovery succeeded.
+ */
+ ANASTASIS_RS_SUCCESS = 0,
+
+ /**
+ * The HTTP download of the policy failed.
+ */
+ ANASTASIS_RS_POLICY_DOWNLOAD_FAILED,
+
+ /**
+ * We did not get a valid policy document.
+ */
+ ANASTASIS_RS_POLICY_DOWNLOAD_NO_POLICY,
+
+ /**
+ * The decompressed policy document was too big for available memory.
+ */
+ ANASTASIS_RS_POLICY_DOWNLOAD_TOO_BIG,
+
+ /**
+ * The decrypted policy document was not compressed.
+ */
+ ANASTASIS_RS_POLICY_DOWNLOAD_INVALID_COMPRESSION,
+
+ /**
+ * The decompressed policy document was not in JSON.
+ */
+ ANASTASIS_RS_POLICY_DOWNLOAD_NO_JSON,
+
+ /**
+ * The decompressed policy document was in malformed JSON.
+ */
+ ANASTASIS_RS_POLICY_MALFORMED_JSON,
+
+ /**
+ * The Anastasis server reported a transient error.
+ */
+ ANASTASIS_RS_POLICY_SERVER_ERROR,
+
+ /**
+ * The Anastasis server no longer has a policy (likely expired).
+ */
+ ANASTASIS_RS_POLICY_GONE,
+
+ /**
+ * The Anastasis server reported that the account is unknown.
+ */
+ ANASTASIS_RS_POLICY_UNKNOWN
+};
+
+
+/**
+ * This function is called whenever the recovery process ends.
+ * On success, the secret is returned in @a secret.
+ *
+ * @param cls closure
+ * @param ec error code
+ * @param secret contains the core secret which is passed to the user
+ * @param secret_size defines the size of the core secret
+ */
+typedef void
+(*ANASTASIS_CoreSecretCallback)(void *cls,
+ enum ANASTASIS_RecoveryStatus rc,
+ const void *secret,
+ size_t secret_size);
+
+
+/**
+ * stores provider URIs, identity key material, decrypted recovery document (internally!)
+ */
+struct ANASTASIS_Recovery;
+
+
+/**
+ * Starts the recovery process by opening callbacks for the coresecret and a policy callback. A list of
+ * providers is checked for policies and passed back to the client.
+ *
+ * @param ctx context for making HTTP requests
+ * @param id_data contains the users identity, (user account on providers)
+ * @param version defines the version which will be downloaded NULL for latest version
+ * @param anastasis_provider_url NULL terminated list of possible provider urls
+ * @param provider_salt the server salt
+ * @param pc opens the policy call back which holds the downloaded version and the policies
+ * @param pc_cls closure for callback
+ * @param csc core secret callback is opened, with this the core secert is passed to the client after the authentication
+ * @param csc_cls handle for the callback
+ * @return recovery operation handle
+ */
+struct ANASTASIS_Recovery *
+ANASTASIS_recovery_begin (
+ struct GNUNET_CURL_Context *ctx,
+ const json_t *id_data,
+ unsigned int version,
+ const char *anastasis_provider_url,
+ const struct ANASTASIS_CRYPTO_ProviderSaltP *provider_salt,
+ ANASTASIS_PolicyCallback pc,
+ void *pc_cls,
+ ANASTASIS_CoreSecretCallback csc,
+ void *csc_cls);
+
+
+/**
+ * Serialize recovery operation state and returning it.
+ * The recovery MAY still continue, applications should call
+ * #ANASTASIS_recovery_abort() to truly end the recovery.
+ *
+ * @param r recovery operation to suspend.
+ * @return JSON serialized state of @a r
+ */
+json_t *
+ANASTASIS_recovery_serialize (const struct ANASTASIS_Recovery *r);
+
+
+/**
+ * Deserialize recovery operation.
+ *
+ * @param ctx context for making HTTP requests
+ * @param input result from #ANASTASIS_recovery_serialize()
+ * @param pc opens the policy call back which holds the downloaded version and the policies
+ * @param pc_cls closure for callback
+ * @param csc core secret callback is opened, with this the core secert is passed to the client after the authentication
+ * @param csc_cls handle for the callback
+ * @return recovery operation handle
+ */
+struct ANASTASIS_Recovery *
+ANASTASIS_recovery_deserialize (struct GNUNET_CURL_Context *ctx,
+ const json_t *input,
+ ANASTASIS_PolicyCallback pc,
+ void *pc_cls,
+ ANASTASIS_CoreSecretCallback csc,
+ void *csc_cls);
+
+
+/**
+ * Cancels the recovery process
+ *
+ * @param r handle to the recovery struct
+ */
+void
+ANASTASIS_recovery_abort (struct ANASTASIS_Recovery *r);
+
+
+/* ************************* Backup API ***************************** */
+
+
+/**
+ * Represents a truth object, which is a key share and the respective
+ * challenge to be solved with an Anastasis provider to recover the
+ * key share.
+ */
+struct ANASTASIS_Truth;
+
+
+/**
+ * Extracts truth data from JSON.
+ *
+ * @param json JSON encoding to decode; truth returned ONLY valid as long
+ * as the JSON remains valid (do not decref until the truth
+ * is truly finished)
+ * @return decoded truth object, NULL on error
+ */
+struct ANASTASIS_Truth *
+ANASTASIS_truth_from_json (const json_t *json);
+
+
+/**
+ * Returns JSON-encoded truth data.
+ * Creates a policy with a set of truth's. Creates the policy key
+ * with the different key shares from the @a truths. The policy key
+ * will then be used to encrypt/decrypt the escrow master key.
+ *
+ * @param t object to return JSON encoding for
+ * @return JSON encoding of @a t
+ */
+json_t *
+ANASTASIS_truth_to_json (const struct ANASTASIS_Truth *t);
+
+
+/**
+ * Handle for the operation to establish a truth object by sharing
+ * an encrypted key share with an Anastasis provider.
+ */
+struct ANASTASIS_TruthUpload;
+
+
+/**
+ * Upload result information. The resulting truth object can be used
+ * to create policies. If payment is required, the @a taler_pay_url
+ * is returned and the operation must be retried after payment.
+ * Callee MUST free @a t using ANASTASIS_truth_free().
+ *
+ * @param cls closure for callback
+ * @param t truth object to create policies, NULL on failure
+ * @param ud upload details, useful to continue in case of errors, NULL on success
+ */
+typedef void
+(*ANASTASIS_TruthCallback)(void *cls,
+ struct ANASTASIS_Truth *t,
+ const struct ANASTASIS_UploadDetails *ud);
+
+
+/**
+ * Uploads truth data to an escrow provider. The resulting truth object
+ * is returned via the @a tc function. If payment is required, it is
+ * requested via the @a tcp callback.
+ *
+ * @param ctx the CURL context used to connect to the backend
+ * @param user_id user identifier derived from user data and backend salt
+ * @param type defines the type of the challenge (secure question, sms, email)
+ * @param instructions depending on @a type! usually only for security question/answer!
+ * @param mime_type format of the challenge
+ * @param provider_salt the providers salt
+ * @param truth_data contains the truth for this challenge i.e. phone number, email address
+ * @param truth_data_size size of the @a truth_data
+ * @param payment_years_requested for how many years would the client like the service to store the truth?
+ * @param pay_timeout how long to wait for payment
+ * @param tc opens the truth callback which contains the status of the upload
+ * @param tc_cls closure for the @a tc callback
+ */
+struct ANASTASIS_TruthUpload *
+ANASTASIS_truth_upload (
+ struct GNUNET_CURL_Context *ctx,
+ const struct ANASTASIS_CRYPTO_UserIdentifierP *user_id,
+ const char *provider_url,
+ const char *type,
+ const char *instructions,
+ const char *mime_type,
+ const struct ANASTASIS_CRYPTO_ProviderSaltP *provider_salt,
+ const void *truth_data,
+ size_t truth_data_size,
+ uint32_t payment_years_requested,
+ struct GNUNET_TIME_Relative pay_timeout,
+ ANASTASIS_TruthCallback tc,
+ void *tc_cls);
+
+
+/**
+ * Retries upload of truth data to an escrow provider. The resulting
+ * truth object is returned via the @a tc function. If payment is
+ * required, it is requested via the @a tcp callback.
+ *
+ * @param ctx the CURL context used to connect to the backend
+ * @param user_id user identifier derived from user data and backend salt
+ * @param type defines the type of the challenge (secure question, sms, email)
+ * @param instructions depending on @a type! usually only for security question/answer!
+ * @param mime_type format of the challenge
+ * @param provider_salt the providers salt
+ * @param truth_data contains the truth for this challenge i.e. phone number, email address
+ * @param truth_data_size size of the @a truth_data
+ * @param payment_years_requested for how many years would the client like the service to store the truth?
+ * @param pay_timeout how long to wait for payment
+ * @param nonce nonce to use for symmetric encryption
+ * @param uuid truth UUID to use
+ * @param salt salt to use to hash security questions
+ * @param truth_key symmetric encryption key to use to encrypt @a truth_data
+ * @param key_share share of the overall key to store in this truth object
+ * @param tc opens the truth callback which contains the status of the upload
+ * @param tc_cls closure for the @a tc callback
+ */
+struct ANASTASIS_TruthUpload *
+ANASTASIS_truth_upload2 (
+ struct GNUNET_CURL_Context *ctx,
+ const struct ANASTASIS_CRYPTO_UserIdentifierP *user_id,
+ const char *provider_url,
+ const char *type,
+ const char *instructions,
+ const char *mime_type,
+ const struct ANASTASIS_CRYPTO_ProviderSaltP *provider_salt,
+ const void *truth_data,
+ size_t truth_data_size,
+ uint32_t payment_years_requested,
+ struct GNUNET_TIME_Relative pay_timeout,
+ const struct ANASTASIS_CRYPTO_NonceP *nonce,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid,
+ const struct ANASTASIS_CRYPTO_QuestionSaltP *salt,
+ const struct ANASTASIS_CRYPTO_TruthKeyP *truth_key,
+ const struct ANASTASIS_CRYPTO_KeyShareP *key_share,
+ ANASTASIS_TruthCallback tc,
+ void *tc_cls);
+
+
+/**
+ * Retries upload of truth data to an escrow provider using an
+ * existing truth object. If payment is required, it is requested via
+ * the @a tc callback.
+ *
+ * @param ctx the CURL context used to connect to the backend
+ * @param user_id user identifier derived from user data and backend salt
+ * @param[in] t truth details, reference is consumed
+ * @param truth_data contains the truth for this challenge i.e. phone number, email address
+ * @param truth_data_size size of the @a truth_data
+ * @param payment_years_requested for how many years would the client like the service to store the truth?
+ * @param pay_timeout how long to wait for payment
+ * @param tc opens the truth callback which contains the status of the upload
+ * @param tc_cls closure for the @a tc callback
+ */
+struct ANASTASIS_TruthUpload *
+ANASTASIS_truth_upload3 (struct GNUNET_CURL_Context *ctx,
+ const struct ANASTASIS_CRYPTO_UserIdentifierP *user_id,
+ struct ANASTASIS_Truth *t,
+ const void *truth_data,
+ size_t truth_data_size,
+ uint32_t payment_years_requested,
+ struct GNUNET_TIME_Relative pay_timeout,
+ ANASTASIS_TruthCallback tc,
+ void *tc_cls);
+
+
+/**
+ * Cancels a truth upload process.
+ *
+ * @param tu handle for the upload
+ */
+void
+ANASTASIS_truth_upload_cancel (struct ANASTASIS_TruthUpload *tu);
+
+
+/**
+ * Free's the truth object which was returned to a #ANASTASIS_TruthCallback.
+ *
+ * @param t object to clean up
+ */
+void
+ANASTASIS_truth_free (struct ANASTASIS_Truth *t);
+
+
+/**
+ * Policy object, representing a set of truths (and thus challenges
+ * to satisfy) to recover a secret.
+ */
+struct ANASTASIS_Policy;
+
+
+/**
+ * Creates a policy with a set of truth's. Creates the policy key
+ * with the different key shares from the @a truths. The policy key
+ * will then be used to encrypt/decrypt the escrow master key.
+ *
+ * @param truths array of truths which are stored on different providers
+ * @param truths_len length of the @a truths array
+ */
+struct ANASTASIS_Policy *
+ANASTASIS_policy_create (const struct ANASTASIS_Truth *truths[],
+ unsigned int truths_len);
+
+
+/**
+ * Destroys a policy object.
+ *
+ * @param p handle for the policy to destroy
+ */
+void
+ANASTASIS_policy_destroy (struct ANASTASIS_Policy *p);
+
+
+/**
+ * Information about a provider requesting payment for storing a policy.
+ */
+struct ANASTASIS_SharePaymentRequest
+{
+ /**
+ * Payment request URL.
+ */
+ const char *payment_request_url;
+
+ /**
+ * Base URL of the provider requesting payment.
+ */
+ const char *provider_url;
+
+ /**
+ * The payment secret (aka order ID) extracted from the @e payment_request_url.
+ */
+ struct ANASTASIS_PaymentSecretP payment_secret;
+};
+
+
+/**
+ * Result of uploading share data.
+ */
+enum ANASTASIS_ShareStatus
+{
+ /**
+ * Upload successful.
+ */
+ ANASTASIS_SHARE_STATUS_SUCCESS = 0,
+
+ /**
+ * Upload requires payment.
+ */
+ ANASTASIS_SHARE_STATUS_PAYMENT_REQUIRED,
+
+ /**
+ * Failure to upload secret share at the provider.
+ */
+ ANASTASIS_SHARE_STATUS_PROVIDER_FAILED
+};
+
+
+/**
+ * Per-provider status upon successful backup.
+ */
+struct ANASTASIS_ProviderSuccessStatus
+{
+ /**
+ * Base URL of the provider.
+ */
+ const char *provider_url;
+
+ /**
+ * When will the policy expire?
+ */
+ struct GNUNET_TIME_Absolute policy_expiration;
+
+ /**
+ * Version number of the policy at the provider.
+ */
+ unsigned long long policy_version;
+
+};
+
+
+/**
+ * Complete result of a secret sharing operation.
+ */
+struct ANASTASIS_ShareResult
+{
+ /**
+ * Status of the share secret operation.
+ */
+ enum ANASTASIS_ShareStatus ss;
+
+ /**
+ * Details about the result, depending on @e ss.
+ */
+ union
+ {
+
+ struct
+ {
+
+ /**
+ * Array of status details for each provider.
+ */
+ const struct ANASTASIS_ProviderSuccessStatus *pss;
+
+ /**
+ * Length of the @e policy_version and @e provider_urls arrays.
+ */
+ unsigned int num_providers;
+
+ } success;
+
+ struct
+ {
+ /**
+ * Array of URLs with requested payments.
+ */
+ struct ANASTASIS_SharePaymentRequest *payment_requests;
+
+ /**
+ * Length of the payment_requests array.
+ */
+ unsigned int payment_requests_length;
+ } payment_required;
+
+ struct
+ {
+ /**
+ * Base URL of the failed provider.
+ */
+ const char *provider_url;
+
+ /**
+ * HTTP status returned by the provider.
+ */
+ unsigned int http_status;
+
+ /**
+ * Upload status of the provider.
+ */
+ enum ANASTASIS_UploadStatus ec;
+
+
+ } provider_failure;
+
+ } details;
+
+};
+
+
+/**
+ * Function called with the results of a #ANASTASIS_secret_share().
+ *
+ * @param cls closure
+ * @param sr share result
+ */
+typedef void
+(*ANASTASIS_ShareResultCallback)(void *cls,
+ const struct ANASTASIS_ShareResult *sr);
+
+
+/**
+ * Defines a recovery document upload process (recovery document
+ * consists of multiple policies)
+ */
+struct ANASTASIS_SecretShare;
+
+
+/**
+ * Details of a past payment
+ */
+struct ANASTASIS_ProviderDetails
+{
+ /**
+ * URL of the provider backend.
+ */
+ const char *provider_url;
+
+ /**
+ * Payment order ID / secret of a past payment.
+ */
+ struct ANASTASIS_PaymentSecretP payment_secret;
+
+ /**
+ * Server salt. Points into a truth object from which we got the
+ * salt.
+ */
+ struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt;
+};
+
+
+/**
+ * Creates a recovery document with the created policies and uploads it to
+ * all servers.
+ *
+ * @param ctx the CURL context used to connect to the backend
+ * @param id_data used to create a account identifier on the escrow provider
+ * @param providers array of providers with URLs to upload the policies to
+ * @param pss_length length of the @a providers array
+ * @param policies list of policies which are included in this recovery document
+ * @param policies_length length of the @a policies array
+ * @param payment_years_requested for how many years would the client like the service to store the truth?
+ * @param pay_timeout how long to wait for payment
+ * @param spc payment callback is opened to pay the upload
+ * @param spc_cls closure for the @a spc payment callback
+ * @param src callback for the upload process
+ * @param src_cls closure for the @a src upload callback
+ * @param secret_name name of the core secret
+ * @param core_secret input of the user which is secured by anastasis e.g. (wallet private key)
+ * @param core_secret_size size of the @a core_secret
+ * @return NULL on error
+ */
+struct ANASTASIS_SecretShare *
+ANASTASIS_secret_share (struct GNUNET_CURL_Context *ctx,
+ const json_t *id_data,
+ const struct ANASTASIS_ProviderDetails providers[],
+ unsigned int pss_length,
+ const struct ANASTASIS_Policy *policies[],
+ unsigned int policies_len,
+ uint32_t payment_years_requested,
+ struct GNUNET_TIME_Relative pay_timeout,
+ ANASTASIS_ShareResultCallback src,
+ void *src_cls,
+ const char *secret_name,
+ const void *core_secret,
+ size_t core_secret_size);
+
+
+/**
+ * Cancels a secret share request.
+ *
+ * @param ss handle to the request
+ */
+void
+ANASTASIS_secret_share_cancel (struct ANASTASIS_SecretShare *ss);
+
+
+#endif
diff --git a/src/include/anastasis_authorization_lib.h b/src/include/anastasis_authorization_lib.h
new file mode 100644
index 0000000..80740be
--- /dev/null
+++ b/src/include/anastasis_authorization_lib.h
@@ -0,0 +1,52 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2019, 2021 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file include/anastasis_authorization_lib.h
+ * @brief database plugin loader
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ * @author Christian Grothoff
+ */
+#ifndef ANASTASIS_AUTHORIZATION_LIB_H
+#define ANASTASIS_AUTHORIZATION_LIB_H
+
+#include "anastasis_authorization_plugin.h"
+
+/**
+ * Load authorization plugin.
+ *
+ * @param method name of the method to load
+ * @param AH_cfg configuration to use
+ * @param[out] set to the cost for using the plugin during recovery
+ * @return #GNUNET_OK on success
+ */
+struct ANASTASIS_AuthorizationPlugin *
+ANASTASIS_authorization_plugin_load (
+ const char *method,
+ const struct GNUNET_CONFIGURATION_Handle *AH_cfg,
+ struct TALER_Amount *cost);
+
+
+/**
+ * shutdown all loaded plugins.
+ *
+ * @param void
+ */
+void
+ANASTASIS_authorization_plugin_shutdown (void);
+
+#endif
+/* end of anastasis_authorization_lib.h */
diff --git a/src/include/anastasis_authorization_plugin.h b/src/include/anastasis_authorization_plugin.h
new file mode 100644
index 0000000..5985f52
--- /dev/null
+++ b/src/include/anastasis_authorization_plugin.h
@@ -0,0 +1,183 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2019 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file include/anastasis_authorization_plugin.h
+ * @brief authorization access for Anastasis
+ * @author Christian Grothoff
+ */
+#ifndef ANASTASIS_AUTHORIZATION_PLUGIN_H
+#define ANASTASIS_AUTHORIZATION_PLUGIN_H
+
+#include "anastasis_service.h"
+#include <taler/taler_util.h>
+
+
+/**
+ * Plugin-specific state for an authorization operation.
+ */
+struct ANASTASIS_AUTHORIZATION_State;
+
+
+/**
+ * Enumeration values indicating the various possible
+ * outcomes of the plugin's `process` function.
+ */
+enum ANASTASIS_AUTHORIZATION_Result
+{
+ /**
+ * We successfully sent the authorization challenge
+ * and queued a reply to MHD.
+ */
+ ANASTASIS_AUTHORIZATION_RES_SUCCESS = 0,
+
+ /**
+ * We failed to transmit the authorization challenge,
+ * but successfully queued a failure response to MHD.
+ */
+ ANASTASIS_AUTHORIZATION_RES_FAILED = 1,
+
+ /**
+ * The plugin suspended the MHD connection as it needs some more
+ * time to do its (asynchronous) work before we can proceed. The
+ * plugin will resume the MHD connection when its work is done, and
+ * then the `process` function should be called again.
+ */
+ ANASTASIS_AUTHORIZATION_RES_SUSPENDED = 2,
+
+ /**
+ * The plugin tried to queue a reply on the MHD connection and
+ * failed to do so. We should return #MHD_NO to MHD to cause the
+ * HTTP connection to be closed without any reply.
+ *
+ * However, we were successful at transmitting the challenge,
+ * so the challenge should be marked as sent.
+ */
+ ANASTASIS_AUTHORIZATION_RES_SUCCESS_REPLY_FAILED = 4,
+
+ /**
+ * The plugin tried to queue a reply on the MHD connection and
+ * failed to do so. We should return #MHD_NO to MHD to cause the
+ * HTTP connection to be closed without any reply.
+ *
+ * Additionally, we failed to transmit the challenge.
+ */
+ ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED = 5
+};
+
+
+/**
+ * Handle to interact with a authorization backend.
+ */
+struct ANASTASIS_AuthorizationPlugin
+{
+
+ /**
+ * Closure for all callbacks.
+ */
+ void *cls;
+
+ /**
+ * How long should a generated challenge be valid for this type of method.
+ */
+ struct GNUNET_TIME_Relative code_validity_period;
+
+ /**
+ * How long before we should rotate a challenge for this type of method.
+ */
+ struct GNUNET_TIME_Relative code_rotation_period;
+
+ /**
+ * How long before we should retransmit a code.
+ */
+ struct GNUNET_TIME_Relative code_retransmission_frequency;
+
+ /**
+ * Validate @a data is a well-formed input into the challenge method,
+ * i.e. @a data is a well-formed phone number for sending an SMS, or
+ * a well-formed e-mail address for sending an e-mail. Not expected to
+ * check that the phone number or e-mail account actually exists.
+ *
+ * To be possibly used before issuing a 402 payment required to the client.
+ *
+ * @param cls closure
+ * @param connection HTTP client request (for queuing response)
+ * @param truth_mime mime type of @e data
+ * @param data input to validate (i.e. is it a valid phone number, etc.)
+ * @param data_length number of bytes in @a data
+ * @return #GNUNET_OK if @a data is valid,
+ * #GNUNET_NO if @a data is invalid and a reply was successfully queued on @a connection
+ * #GNUNET_SYSERR if @a data invalid but we failed to queue a reply on @a connection
+ */
+ enum GNUNET_GenericReturnValue
+ (*validate)(void *cls,
+ struct MHD_Connection *connection,
+ const char *truth_mime,
+ const char *data,
+ size_t data_length);
+
+
+ /**
+ * Begin issuing authentication challenge to user based on @a data.
+ * I.e. start to send SMS or e-mail or launch video identification,
+ * or at least setup our authorization state (actual processing
+ * may also be startedin the @e process function).
+ *
+ * @param cls closure
+ * @param trigger function to call when we made progress
+ * @param trigger_cls closure for @a trigger
+ * @param truth_public_key Identifier of the challenge, to be (if possible) included in the
+ * interaction with the user
+ * @param code secret code that the user has to provide back to satisfy the challenge in
+ * the main anastasis protocol
+ * @param auth_command authentication command which is executed
+ * @param data input to validate (i.e. is it a valid phone number, etc.)
+ * @return state to track progress on the authorization operation, NULL on failure
+ */
+ struct ANASTASIS_AUTHORIZATION_State *
+ (*start)(void *cls,
+ GNUNET_SCHEDULER_TaskCallback trigger,
+ void *trigger_cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_public_key,
+ uint64_t code,
+ const void *data,
+ size_t data_length);
+
+
+ /**
+ * Continue issuing authentication challenge to user based on @a data.
+ * I.e. check if the transmission of the challenge via SMS or e-mail
+ * has completed and/or manipulate @a connection to redirect the client
+ * to a video identification site.
+ *
+ * @param as authorization state
+ * @param connection HTTP client request (for queuing response, such as redirection to video portal)
+ * @return state of the request
+ */
+ enum ANASTASIS_AUTHORIZATION_Result
+ (*process)(struct ANASTASIS_AUTHORIZATION_State *as,
+ struct MHD_Connection *connection);
+
+
+ /**
+ * Free internal state associated with @a as.
+ *
+ * @param as state to clean up
+ */
+ void
+ (*cleanup)(struct ANASTASIS_AUTHORIZATION_State *as);
+
+};
+#endif
diff --git a/src/include/anastasis_crypto_lib.h b/src/include/anastasis_crypto_lib.h
new file mode 100644
index 0000000..bf29b27
--- /dev/null
+++ b/src/include/anastasis_crypto_lib.h
@@ -0,0 +1,533 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 2021 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/anastasis_cryto_lib.h
+ * @brief anastasis crypto api
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ */
+#include <jansson.h>
+#include <gnunet/gnunet_crypto_lib.h>
+
+
+/**
+ * Server to client: this is the policy version.
+ */
+#define ANASTASIS_HTTP_HEADER_POLICY_VERSION "Anastasis-Version"
+
+/**
+ * Server to client: this is the policy expiration time.
+ */
+#define ANASTASIS_HTTP_HEADER_POLICY_EXPIRATION "Anastasis-Policy-Expiration"
+
+/**
+ * Client to server: use this to decrypt the truth.
+ */
+#define ANASTASIS_HTTP_HEADER_TRUTH_DECRYPTION_KEY \
+ "Anastasis-Truth-Decryption-Key"
+
+/**
+ * Client to server: I paid using this payment secret.
+ */
+#define ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER "Anastasis-Payment-Identifier"
+
+/**
+ * Client to server: I am authorized to update this policy, or
+ * server to client: I prove this is a valid policy.
+ */
+#define ANASTASIS_HTTP_HEADER_POLICY_SIGNATURE "Anastasis-Policy-Signature"
+
+/**
+ * Server to client: Taler Payto-URI.
+ */
+#define ANASTASIS_HTTP_HEADER_TALER "Taler"
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * An EdDSA public key that is used to identify a user's account.
+ */
+struct ANASTASIS_CRYPTO_AccountPublicKeyP
+{
+ struct GNUNET_CRYPTO_EddsaPublicKey pub;
+};
+
+
+/**
+ * An EdDSA private key that is used to identify a user's account.
+ */
+struct ANASTASIS_CRYPTO_AccountPrivateKeyP
+{
+ struct GNUNET_CRYPTO_EddsaPrivateKey priv;
+};
+
+
+/**
+ * A UUID that is used to identify a truth object
+ */
+struct ANASTASIS_CRYPTO_TruthUUIDP
+{
+ struct GNUNET_ShortHashCode uuid;
+};
+
+
+/**
+ * Specifies a TruthKey which is used to decrypt the Truth stored by the user.
+ */
+struct ANASTASIS_CRYPTO_TruthKeyP
+{
+ struct GNUNET_HashCode key GNUNET_PACKED;
+};
+
+
+/**
+ * Specifies a salt value used to encrypt the master public key.
+ */
+struct ANASTASIS_CRYPTO_MasterSaltP
+{
+ struct GNUNET_HashCode salt GNUNET_PACKED;
+};
+
+
+/**
+ * Specifies a salt value used for salting the answer to a security question.
+ */
+struct ANASTASIS_CRYPTO_QuestionSaltP
+{
+ struct GNUNET_CRYPTO_PowSalt pow_salt;
+};
+
+
+/**
+ * Specifies a salt value provided by an Anastasis provider,
+ * used for deriving the provider-specific user ID.
+ */
+struct ANASTASIS_CRYPTO_ProviderSaltP
+{
+ struct GNUNET_CRYPTO_PowSalt salt;
+};
+
+
+/**
+ * Specifies a policy key which is used to decrypt the master key
+ */
+struct ANASTASIS_CRYPTO_PolicyKeyP
+{
+ struct GNUNET_HashCode key GNUNET_PACKED;
+};
+
+
+/**
+ * Specifies an encrypted master key, the key is used to encrypt the core secret from the user
+ */
+struct ANASTASIS_CRYPTO_EncryptedMasterKeyP
+{
+ struct GNUNET_HashCode key GNUNET_PACKED;
+};
+
+
+/**
+ * Specifies a Nonce used for the AES encryption, here defined as 32Byte large.
+ */
+struct ANASTASIS_CRYPTO_NonceP
+{
+ uint32_t nonce[8];
+};
+
+
+/**
+ * Specifies an IV used for the AES encryption, here defined as 16Byte large.
+ */
+struct ANASTASIS_CRYPTO_IvP
+{
+ uint32_t iv[4];
+};
+
+
+/**
+ * Specifies an symmetric key used for the AES encryption, here defined as 32Byte large.
+ */
+struct ANASTASIS_CRYPTO_SymKeyP
+{
+ uint32_t key[8];
+};
+
+
+/**
+ * Specifies an AES Tag used for the AES authentication, here defined as 16 Byte large.
+ */
+struct ANASTASIS_CRYPTO_AesTagP
+{
+ uint32_t aes_tag[4];
+};
+
+
+/**
+ * Specifies a Key Share from an escrow provider, the combined
+ * keyshares generate the EscrowMasterKey which is used to decrypt the
+ * Secret from the user.
+ */
+struct ANASTASIS_CRYPTO_KeyShareP
+{
+ uint32_t key[8];
+};
+
+
+/**
+ * Specifies an encrypted KeyShare
+ */
+struct ANASTASIS_CRYPTO_EncryptedKeyShareP
+{
+ /**
+ * Nonce used for the symmetric encryption.
+ */
+ struct ANASTASIS_CRYPTO_NonceP nonce;
+
+ /**
+ * GCM tag to check authenticity.
+ */
+ struct ANASTASIS_CRYPTO_AesTagP tag;
+
+ /**
+ * The actual key share.
+ */
+ struct ANASTASIS_CRYPTO_KeyShareP keyshare;
+};
+
+
+/**
+ * The escrow master key is the key used to encrypt the user secret (MasterKey).
+ */
+struct ANASTASIS_CRYPTO_EscrowMasterKeyP
+{
+ uint32_t key[8];
+};
+
+
+/**
+ * The user identifier consists of user information and the server salt. It is used as
+ * entropy source to generate the account public key and the encryption keys.
+ */
+struct ANASTASIS_CRYPTO_UserIdentifierP
+{
+ struct GNUNET_HashCode hash GNUNET_PACKED;
+};
+
+
+/**
+ * Random identifier used to later charge a payment.
+ */
+struct ANASTASIS_PaymentSecretP
+{
+ uint32_t id[8];
+};
+
+
+/**
+ * Data signed by the account public key of a sync client to
+ * authorize the upload of the backup.
+ */
+struct ANASTASIS_UploadSignaturePS
+{
+ /**
+ * Set to #TALER_SIGNATURE_ANASTASIS_POLICY_UPLOAD.
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+ /**
+ * Hash of the new backup.
+ */
+ struct GNUNET_HashCode new_recovery_data_hash;
+
+};
+
+
+/**
+ * Signature made with an account's public key.
+ */
+struct ANASTASIS_AccountSignatureP
+{
+ /**
+ * We use EdDSA.
+ */
+ struct GNUNET_CRYPTO_EddsaSignature eddsa_sig;
+};
+
+
+GNUNET_NETWORK_STRUCT_END
+
+
+/**
+ * Hash a numerical answer to compute the hash value to be submitted
+ * to the server for verification. Useful for PINs and SMS-TANs and
+ * other numbers submitted for challenges.
+ *
+ * @param code the numeric value to hash
+ * @param[out] hashed_code the resulting hash value to submit to the Anastasis server
+ */
+void
+ANASTASIS_hash_answer (uint64_t code,
+ struct GNUNET_HashCode *hashed_code);
+
+
+/**
+ * Creates the UserIdentifier, it is used as entropy source for the
+ * encryption keys and for the public and private key for signing the
+ * data.
+ *
+ * @param id_data JSON encoded data, which contains the raw user secret
+ * @param server_salt salt from the server (escrow provider)
+ * @param[out] id reference to the id which was created
+ */
+void
+ANASTASIS_CRYPTO_user_identifier_derive (
+ const json_t *id_data,
+ const struct ANASTASIS_CRYPTO_ProviderSaltP *server_salt,
+ struct ANASTASIS_CRYPTO_UserIdentifierP *id);
+
+
+/**
+ * Generates the eddsa public Key used as the account identifier on the providers
+ *
+ * @param id holds a hashed user secret which is used as entropy source for the public key generation
+ * @param[out] pub_key handle for the generated public key
+ */
+void
+ANASTASIS_CRYPTO_account_public_key_derive (
+ const struct ANASTASIS_CRYPTO_UserIdentifierP *id,
+ struct ANASTASIS_CRYPTO_AccountPublicKeyP *pub_key);
+
+
+/**
+ * //FIXME combine these two
+ * Generates the eddsa public Key used as the account identifier on the providers
+ *
+ * @param id holds a hashed user secret which is used as entropy source for the public key generation
+ * @param[out] priv_key handle for the generated private key
+ */
+void
+ANASTASIS_CRYPTO_account_private_key_derive (
+ const struct ANASTASIS_CRYPTO_UserIdentifierP *id,
+ struct ANASTASIS_CRYPTO_AccountPrivateKeyP *priv_key);
+
+
+/**
+ * Hash @a answer to security question with @a salt and @a uuid to compute
+ * @a result that would be sent to the service for authorization.
+ *
+ * @param answer human answer to a security question
+ * @param uuid the truth UUID (known to the service)
+ * @param salt random salt value, unknown to the service
+ * @param[out] result where to write the resulting hash
+ */
+void
+ANASTASIS_CRYPTO_secure_answer_hash (
+ const char *answer,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid,
+ const struct ANASTASIS_CRYPTO_QuestionSaltP *salt,
+ struct GNUNET_HashCode *result);
+
+
+/**
+ * Encrypt and signs the recovery document with AES256, the recovery
+ * document is encrypted with a derivation from the user identifier
+ * and the salt "erd".
+ *
+ * @param id Hashed User input, used for the generation of the encryption key
+ * @param rec_doc contains the recovery document as raw data
+ * @param rd_size defines the size of the recovery document inside data
+ * @param[out] enc_rec_doc return from the result, which contains the encrypted recovery document
+ * and the nonce and iv used for the encryption as Additional Data
+ * @param[out] erd_size size of the result
+ */
+void
+ANASTASIS_CRYPTO_recovery_document_encrypt (
+ const struct ANASTASIS_CRYPTO_UserIdentifierP *id,
+ const void *rec_doc,
+ size_t rd_size,
+ void **enc_rec_doc,
+ size_t *erd_size);
+
+
+/**
+ * Decrypts the recovery document with AES256, the decryption key is generated with
+ * the user identifier provided by the user and the salt "erd". The nonce and IV used for the encryption
+ * are the first 48Byte of the data.
+ *
+ * @param id Hashed User input, used for the generation of the encryption key
+ * @param enc_rec_doc, contains the encrypted recovery document and the nonce and iv used for the encryption.
+ * @param erd_size size of the data
+ * @param[out] rec_doc return from the result, which contains the encrypted recovery document
+ * and the nonce and iv used for the encryption as Additional Data
+ * @param[out] rd_size size of the result
+ */
+void
+ANASTASIS_CRYPTO_recovery_document_decrypt (
+ const struct ANASTASIS_CRYPTO_UserIdentifierP *id,
+ const void *enc_rec_doc,
+ size_t erd_size,
+ void **rec_doc,
+ size_t *rd_size);
+
+
+/**
+ * Encrypts a keyshare with a key generated with the user identification as entropy and the salt "eks".
+ *
+ * @param key_share the key share which is afterwards encrypted
+ * @param id the user identification which is the entropy source for the key generation
+ * @param xsalt answer to security question, otherwise NULL; used as extra salt in KDF
+ * @param[out] enc_key_share holds the encrypted share, the first 48 Bytes are the used nonce and tag
+ */
+void
+ANASTASIS_CRYPTO_keyshare_encrypt (
+ const struct ANASTASIS_CRYPTO_KeyShareP *key_share,
+ const struct ANASTASIS_CRYPTO_UserIdentifierP *id,
+ const char *xsalt,
+ struct ANASTASIS_CRYPTO_EncryptedKeyShareP *enc_key_share);
+
+
+/**
+ * Decrypts a keyshare with a key generated with the user identification as entropy and the salt "eks".
+ *
+ * @param enc_key_share holds the encrypted share, the first 48 Bytes are the used nonce and tag
+ * @param id the user identification which is the entropy source for the key generation
+ * @param xsalt answer to security question, otherwise NULL; used as extra salt in KDF
+ * @param[out] key_share the result of decryption
+ */
+void
+ANASTASIS_CRYPTO_keyshare_decrypt (
+ const struct ANASTASIS_CRYPTO_EncryptedKeyShareP *enc_key_share,
+ const struct ANASTASIS_CRYPTO_UserIdentifierP *id,
+ const char *xsalt,
+ struct ANASTASIS_CRYPTO_KeyShareP *key_share);
+
+
+/**
+ * Encrypts the truth data which contains the hashed answer or the
+ * phone number. It is encrypted with AES256, the key is generated
+ * with the user identification as entropy source and the salt "ect".
+ *
+ * @param nonce value to use for the nonce
+ * @param truth_enc_key master key used for encryption of the truth (see interface EscrowMethod)
+ * @param truth truth which will be encrypted
+ * @param truth_size size of the truth
+ * @param[out] enc_truth return from the result, which contains the encrypted truth
+ * and the nonce and iv used for the encryption as Additional Data
+ * @param[out] ect_size size of the result
+ */
+void
+ANASTASIS_CRYPTO_truth_encrypt (
+ const struct ANASTASIS_CRYPTO_NonceP *nonce,
+ const struct ANASTASIS_CRYPTO_TruthKeyP *truth_enc_key,
+ const void *truth,
+ size_t truth_size,
+ void **enc_truth,
+ size_t *ect_size);
+
+
+/**
+ * Decrypts the truth data which contains the hashed answer or the phone number..
+ * It is decrypted with AES256, the key is generated with the user identification as
+ * entropy source and the salt "ect".
+ *
+ * @param truth_enc_key master key used for encryption of the truth (see interface EscrowMethod)
+ * @param enc_truth truth holds the encrypted truth which will be decrypted
+ * @param ect_size size of the truth data
+ * @param truth return from the result, which contains the truth
+ * @param truth_size size of the result
+ */
+void
+ANASTASIS_CRYPTO_truth_decrypt (
+ const struct ANASTASIS_CRYPTO_TruthKeyP *truth_enc_key,
+ const void *enc_truth,
+ size_t ect_size,
+ void **truth,
+ size_t *truth_size);
+
+
+/**
+ * A key share is randomly generated, one key share is generated for every
+ * truth a policy contains.
+ *
+ * @param key_share[out] reference to the created key share.
+ */
+void
+ANASTASIS_CRYPTO_keyshare_create (
+ struct ANASTASIS_CRYPTO_KeyShareP *key_share);
+
+
+/**
+ * Once per policy a policy key is derived. The policy key consists of
+ * multiple key shares which are combined and hashed.
+ *
+ * @param key_shares list of key shares which are combined
+ * @param keyshare_length amount of key shares inside the array
+ * @param salt salt value
+ * @param[out] policy_key reference to the created key
+ */
+void
+ANASTASIS_CRYPTO_policy_key_derive (
+ const struct ANASTASIS_CRYPTO_KeyShareP *key_shares,
+ unsigned int keyshare_length,
+ const struct ANASTASIS_CRYPTO_MasterSaltP *salt,
+ struct ANASTASIS_CRYPTO_PolicyKeyP *policy_key);
+
+
+/**
+ * The core secret is the user provided secret which will be saved with Anastasis.
+ * The secret will be encrypted with the master key, the master key is a random key which will
+ * be generated. The master key afterwards will be encrypted with the different policy keys.
+ * Encryption is performed with AES256
+ *
+ * @param policy_keys an array of policy keys which are used to encrypt the master key
+ * @param policy_keys_length defines the amount of policy keys and also the amount of encrypted master keys
+ * @param core_secret the user provided core secret which is secured by anastasis
+ * @param core_secret_size the size of the core secret
+ * @param[out] enc_core_secret the core secret is encrypted with the generated master key
+ * @param[out] encrypted_master_keys array of encrypted master keys which will be safed inside the policies one encrypted
+ * master key is created for each policy key
+ */
+void
+ANASTASIS_CRYPTO_core_secret_encrypt (
+ const struct ANASTASIS_CRYPTO_PolicyKeyP *policy_keys,
+ unsigned int policy_keys_length,
+ const void *core_secret,
+ size_t core_secret_size,
+ void **enc_core_secret,
+ struct ANASTASIS_CRYPTO_EncryptedMasterKeyP *encrypted_master_keys);
+
+
+/**
+ * Decrypts the core secret with the master key. First the master key is decrypted with the provided policy key.
+ * Afterwards the core secret is encrypted with the master key. The core secret is returned.
+ *
+ * @param encrypted_master_key master key for decrypting the core secret, is itself encrypted by the policy key
+ * @param policy_key built policy key which will decrypt the master key
+ * @param encrypted_core_secret the encrypted core secret from the user, will be encrypted with the policy key
+ * @param encrypted_core_secret_size size of the encrypted core secret
+ * @param[out] core_secret decrypted core secret will be returned
+ * @param[out] core_secret_size size of core secret
+ */
+void
+ANASTASIS_CRYPTO_core_secret_recover (
+ const struct ANASTASIS_CRYPTO_EncryptedMasterKeyP *encrypted_master_key,
+ const struct ANASTASIS_CRYPTO_PolicyKeyP *policy_key,
+ const void *encrypted_core_secret,
+ size_t encrypted_core_secret_size,
+ void **core_secret,
+ size_t *core_secret_size);
diff --git a/src/include/anastasis_database_lib.h b/src/include/anastasis_database_lib.h
new file mode 100644
index 0000000..4b33ea8
--- /dev/null
+++ b/src/include/anastasis_database_lib.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2019 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file include/anastasis_database_lib.h
+ * @brief database plugin loader
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ * @author Christian Grothoff
+ */
+#ifndef ANASTASIS_DB_LIB_H
+#define ANASTASIS_DB_LIB_H
+
+#include "anastasis_database_plugin.h"
+
+/**
+ * Initialize the plugin.
+ *
+ * @param cfg configuration to use
+ * @return NULL on failure
+ */
+struct ANASTASIS_DatabasePlugin *
+ANASTASIS_DB_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg);
+
+
+/**
+ * Shutdown the plugin.
+ *
+ * @param plugin plugin to unload
+ */
+void
+ANASTASIS_DB_plugin_unload (struct ANASTASIS_DatabasePlugin *plugin);
+
+
+#endif /* ANASTASIS_DB_LIB_H */
+
+/* end of anastasis_database_lib.h */
diff --git a/src/include/anastasis_database_plugin.h b/src/include/anastasis_database_plugin.h
new file mode 100644
index 0000000..488a5af
--- /dev/null
+++ b/src/include/anastasis_database_plugin.h
@@ -0,0 +1,655 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2019 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file include/anastasis_database_plugin.h
+ * @brief database access for Anastasis
+ * @author Christian Grothoff
+ */
+#ifndef ANASTASIS_DATABASE_PLUGIN_H
+#define ANASTASIS_DATABASE_PLUGIN_H
+
+#include "anastasis_service.h"
+#include <gnunet/gnunet_db_lib.h>
+
+/**
+ * How long is an offer for a challenge payment valid for payment?
+ */
+#define ANASTASIS_CHALLENGE_OFFER_LIFETIME GNUNET_TIME_UNIT_HOURS
+
+/**
+ * Return values for checking code validity.
+ */
+enum ANASTASIS_DB_CodeStatus
+{
+ /**
+ * Provided authentication code does not match database content.
+ */
+ ANASTASIS_DB_CODE_STATUS_CHALLENGE_CODE_MISMATCH = -3,
+
+ /**
+ * Encountered hard error talking to DB.
+ */
+ ANASTASIS_DB_CODE_STATUS_HARD_ERROR = -2,
+
+ /**
+ * Encountered serialization error talking to DB.
+ */
+ ANASTASIS_DB_CODE_STATUS_SOFT_ERROR = -1,
+
+ /**
+ * We have no challenge in the database.
+ */
+ ANASTASIS_DB_CODE_STATUS_NO_RESULTS = 0,
+
+ /**
+ * The provided challenge matches what we have in the database.
+ */
+ ANASTASIS_DB_CODE_STATUS_VALID_CODE_STORED = 1,
+};
+
+
+/**
+ * Return values for checking account validity.
+ */
+enum ANASTASIS_DB_AccountStatus
+{
+ /**
+ * Account is unknown, user should pay to establish it.
+ */
+ ANASTASIS_DB_ACCOUNT_STATUS_PAYMENT_REQUIRED = -3,
+
+ /**
+ * Encountered hard error talking to DB.
+ */
+ ANASTASIS_DB_ACCOUNT_STATUS_HARD_ERROR = -2,
+
+ /**
+ * Account is valid, but we have no policy stored yet.
+ */
+ ANASTASIS_DB_ACCOUNT_STATUS_NO_RESULTS = 0,
+
+ /**
+ * Account is valid, and we have a policy stored.
+ */
+ ANASTASIS_DB_ACCOUNT_STATUS_VALID_HASH_RETURNED = 1,
+};
+
+
+/**
+ * Return values for storing data in database with payment.
+ */
+enum ANASTASIS_DB_StoreStatus
+{
+ /**
+ * The client has stored too many policies, should pay to store more.
+ */
+ ANASTASIS_DB_STORE_STATUS_STORE_LIMIT_EXCEEDED = -4,
+
+ /**
+ * The client needs to pay to store policies.
+ */
+ ANASTASIS_DB_STORE_STATUS_PAYMENT_REQUIRED = -3,
+
+ /**
+ * Encountered hard error talking to DB.
+ */
+ ANASTASIS_DB_STORE_STATUS_HARD_ERROR = -2,
+
+ /**
+ * Despite retrying, we encountered serialization errors.
+ */
+ ANASTASIS_DB_STORE_STATUS_SOFT_ERROR = -1,
+
+ /**
+ * Database did not need an update (document exists).
+ */
+ ANASTASIS_DB_STORE_STATUS_NO_RESULTS = 0,
+
+ /**
+ * We successfully stored the document.
+ */
+ ANASTASIS_DB_STORE_STATUS_SUCCESS = 1,
+};
+
+
+/**
+ * Function called on all pending payments for an account or challenge.
+ *
+ * @param cls closure
+ * @param timestamp for how long have we been waiting
+ * @param payment_secret payment secret / order id in the backend
+ * @param amount how much is the order for
+ */
+typedef void
+(*ANASTASIS_DB_PaymentPendingIterator)(
+ void *cls,
+ struct GNUNET_TIME_Absolute timestamp,
+ const struct ANASTASIS_PaymentSecretP *payment_secret,
+ const struct TALER_Amount *amount);
+
+
+/**
+ * Handle to interact with the database.
+ *
+ * Functions ending with "_TR" run their OWN transaction scope
+ * and MUST NOT be called from within a transaction setup by the
+ * caller. Functions ending with "_NT" require the caller to
+ * setup a transaction scope. Functions without a suffix are
+ * simple, single SQL queries that MAY be used either way.
+ */
+struct ANASTASIS_DatabasePlugin
+{
+
+ /**
+ * Closure for all callbacks.
+ */
+ void *cls;
+
+ /**
+ * Name of the library which generated this plugin. Set by the
+ * plugin loader.
+ */
+ char *library_name;
+
+ /**
+ * Drop anastasis tables. Used for testcases.
+ *
+ * @param cls closure
+ * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
+ */
+ int
+ (*drop_tables) (void *cls);
+
+ /**
+ * Function called to perform "garbage collection" on the
+ * database, expiring records we no longer require. Deletes
+ * all user records that are not paid up (and by cascade deletes
+ * the associated recovery documents). Also deletes expired
+ * truth and financial records older than @a fin_expire.
+ *
+ * @param cls closure
+ * @param expire_backups backups older than the given time stamp should be garbage collected
+ * @param expire_pending_payments payments still pending from since before
+ * this value should be garbage collected
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*gc)(void *cls,
+ struct GNUNET_TIME_Absolute expire,
+ struct GNUNET_TIME_Absolute expire_pending_payments);
+
+ /**
+ * Do a pre-flight check that we are not in an uncommitted transaction.
+ * If we are, try to commit the previous transaction and output a warning.
+ * Does not return anything, as we will continue regardless of the outcome.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ */
+ void
+ (*preflight) (void *cls);
+
+ /**
+ * Check that the database connection is still up.
+ *
+ * @param pg connection to check
+ */
+ void
+ (*check_connection) (void *cls);
+
+ /**
+ * Roll back the current transaction of a database connection.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @return #GNUNET_OK on success
+ */
+ void
+ (*rollback) (void *cls);
+
+ /**
+ * Start a transaction.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param name unique name identifying the transaction (for debugging),
+ * must point to a constant
+ * @return #GNUNET_OK on success
+ */
+ int
+ (*start) (void *cls,
+ const char *name);
+
+ /**
+ * Commit the current transaction of a database connection.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @return transaction status code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*commit)(void *cls);
+
+
+ /**
+ * Store encrypted recovery document.
+ *
+ * @param cls closure
+ * @param anastasis_pub public key of the user's account
+ * @param account_sig signature affirming storage request
+ * @param data_hash hash of @a data
+ * @param data contains encrypted_recovery_document
+ * @param data_size size of data blob
+ * @param payment_secret identifier for the payment, used to later charge on uploads
+ * @param[out] version set to the version assigned to the document by the database
+ * @return transaction status, 0 if upload could not be finished because @a payment_secret
+ * did not have enough upload left; HARD error if @a payment_secret is unknown, ...
+ */
+ enum ANASTASIS_DB_StoreStatus
+ (*store_recovery_document)(
+ void *cls,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *anastasis_pub,
+ const struct ANASTASIS_AccountSignatureP *account_sig,
+ const struct GNUNET_HashCode *data_hash,
+ const void *data,
+ size_t data_size,
+ const struct ANASTASIS_PaymentSecretP *payment_secret,
+ uint32_t *version);
+
+
+ /**
+ * Fetch recovery document for user according given version.
+ *
+ * @param cls closure
+ * @param anastasis_pub public key of the user's account
+ * @param version the version number of the policy the user requests
+ * @param[out] account_sig signature
+ * @param[out] recovery_data_hash hash of the current recovery data
+ * @param[out] data_size size of data blob
+ * @param[out] data blob which contains the recovery document
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*get_recovery_document)(
+ void *cls,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *anastasis_pub,
+ uint32_t version,
+ struct ANASTASIS_AccountSignatureP *account_sig,
+ struct GNUNET_HashCode *recovery_data_hash,
+ size_t *data_size,
+ void **data);
+
+
+ /**
+ * Fetch latest recovery document for user.
+ *
+ * @param cls closure
+ * @param anastasis_pub public key of the user's account
+ * @param account_sig signature
+ * @param recovery_data_hash hash of the current recovery data
+ * @param[out] data_size set to size of @a data blob
+ * @param[out] data set to blob which contains the recovery document
+ * @param[out] version set to the version number of the policy being returned
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*get_latest_recovery_document)(
+ void *cls,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *anastasis_pub,
+ struct ANASTASIS_AccountSignatureP *account_sig,
+ struct GNUNET_HashCode *recovery_data_hash,
+ size_t *data_size,
+ void **data,
+ uint32_t *version);
+
+
+ /**
+ * Upload Truth, which contains the Truth and the KeyShare.
+ *
+ * @param cls closure
+ * @param truth_uuid the identifier for the Truth
+ * @param key_share_data contains information of an EncryptedKeyShare
+ * @param method name of method
+ * @param nonce nonce used to compute encryption key for encrypted_truth
+ * @param aes_gcm_tag authentication tag of encrypted_truth
+ * @param encrypted_truth contains the encrypted Truth which includes the ground truth i.e. H(challenge answer), phonenumber, SMS
+ * @param encrypted_truth_size the size of the Truth
+ * @param truth_expiration time till the according data will be stored
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*store_truth)(
+ void *cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ const struct ANASTASIS_CRYPTO_EncryptedKeyShareP *key_share_data,
+ const char *mime_type,
+ const void *encrypted_truth,
+ size_t encrypted_truth_size,
+ const char *method,
+ struct GNUNET_TIME_Relative truth_expiration);
+
+
+ /**
+ * Get the encrypted truth to validate the challenge response
+ *
+ * @param cls closure
+ * @param truth_uuid the identifier for the Truth
+ * @param[out] truth contains the encrypted truth
+ * @param[out] truth_size size of the encrypted truth
+ * @param[out] truth_mime mime type of truth
+ * @param[out] method type of the challenge
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*get_escrow_challenge)(
+ void *cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ void **truth,
+ size_t *truth_size,
+ char **truth_mime,
+ char **method);
+
+
+ /**
+ * Lookup (encrypted) key share by @a truth_uuid.
+ *
+ * @param cls closure
+ * @param truth_uuid the identifier for the Truth
+ * @param[out] key_share set to the encrypted Keyshare
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*get_key_share)(
+ void *cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ struct ANASTASIS_CRYPTO_EncryptedKeyShareP *key_share);
+
+
+ /**
+ * Check if an account exists, and if so, return the
+ * current @a recovery_document_hash.
+ *
+ * @param cls closure
+ * @param anastasis_pub account identifier
+ * @param[out] paid_until until when is the account paid up?
+ * @param[out] recovery_data_hash set to hash of @a recovery document
+ * @param[out] version set to the recovery policy version
+ * @return transaction status
+ */
+ enum ANASTASIS_DB_AccountStatus
+ (*lookup_account)(
+ void *cls,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *anastasis_pub,
+ struct GNUNET_TIME_Absolute *paid_until,
+ struct GNUNET_HashCode *recovery_data_hash,
+ uint32_t *version);
+
+
+ /**
+ * Check payment identifier. Used to check if a payment identifier given by
+ * the user is valid (existing and paid).
+ *
+ * @param cls closure
+ * @param payment_secret payment secret which the user must provide with every upload
+ * @param[out] paid bool value to show if payment is paid
+ * @param[out] valid_counter bool value to show if post_counter is > 0
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*check_payment_identifier)(
+ void *cls,
+ const struct ANASTASIS_PaymentSecretP *payment_secret,
+ bool *paid,
+ bool *valid_counter);
+
+
+ /**
+ * Check payment identifier. Used to check if a payment identifier given by
+ * the user is valid (existing and paid).
+ *
+ * @param cls closure
+ * @param payment_secret payment secret which the user must provide with every upload
+ * @param truth_uuid unique identifier of the truth the user must satisfy the challenge
+ * @param[out] paid bool value to show if payment is paid
+ * @param[out] valid_counter bool value to show if post_counter is > 0
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*check_challenge_payment)(
+ void *cls,
+ const struct ANASTASIS_PaymentSecretP *payment_secret,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ bool *paid);
+
+
+ /**
+ * Increment account lifetime by @a lifetime.
+ *
+ * @param cls closure
+ * @param account_pub which account received a payment
+ * @param payment_identifier proof of payment, must be unique and match pending payment
+ * @param lifetime for how long is the account now paid (increment)
+ * @param[out] paid_until set to the end of the lifetime after the operation
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*increment_lifetime)(
+ void *cls,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *anastasis_pub,
+ const struct ANASTASIS_PaymentSecretP *payment_identifier,
+ struct GNUNET_TIME_Relative lifetime,
+ struct GNUNET_TIME_Absolute *paid_until);
+
+
+ /**
+ * Update account lifetime to the maximum of the current
+ * value and @a eol.
+ *
+ * @param cls closure
+ * @param account_pub which account received a payment
+ * @param payment_identifier proof of payment, must be unique and match pending payment
+ * @param eol for how long is the account now paid (absolute)
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*update_lifetime)(
+ void *cls,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *anastasis_pub,
+ const struct ANASTASIS_PaymentSecretP *payment_identifier,
+ struct GNUNET_TIME_Absolute eol);
+
+
+ /**
+ * Store payment. Used to begin a payment, not indicative
+ * that the payment actually was made. (That is done
+ * when we increment the account's lifetime.)
+ *
+ * @param cls closure
+ * @param anastasis_pub anastasis's public key
+ * @param post_counter how many uploads does @a amount pay for
+ * @param payment_secret payment secret which the user must provide with every upload
+ * @param amount how much we asked for
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*record_recdoc_payment)(
+ void *cls,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *anastasis_pub,
+ uint32_t post_counter,
+ const struct ANASTASIS_PaymentSecretP *payment_secret,
+ const struct TALER_Amount *amount);
+
+
+ /**
+ * Record truth upload payment was made.
+ *
+ * @param cls closure
+ * @param uuid the truth's UUID
+ * @param amount the amount that was paid
+ * @param duration how long is the truth paid for
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*record_truth_upload_payment)(
+ void *cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid,
+ const struct TALER_Amount *amount,
+ struct GNUNET_TIME_Relative duration);
+
+
+ /**
+ * Inquire whether truth upload payment was made.
+ *
+ * @param cls closure
+ * @param uuid the truth's UUID
+ * @param[out] paid_until set for how long this truth is paid for
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*check_truth_upload_paid)(
+ void *cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid,
+ struct GNUNET_TIME_Absolute *paid_until);
+
+
+ /**
+ * Verify the provided code with the code on the server.
+ * If the code matches the function will return with success, if the code
+ * does not match, the retry counter will be decreased by one.
+ *
+ * @param cls closure
+ * @param truth_pub identification of the challenge which the code corresponds to
+ * @param hashed_code code which the user provided and wants to verify
+ * @return transaction status
+ */
+ enum ANASTASIS_DB_CodeStatus
+ (*verify_challenge_code)(
+ void *cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_pub,
+ const struct GNUNET_HashCode *hashed_code);
+
+ /**
+ * Insert a new challenge code for a given challenge identified by the challenge
+ * public key. The function will first check if there is already a valid code
+ * for this challenge present and won't insert a new one in this case.
+ *
+ * @param cls closure
+ * @param truth_uuid the identifier for the challenge
+ * @param rotation_period for how long is the code available
+ * @param validity_period for how long is the code available
+ * @param retry_counter amount of retries allowed
+ * @param[out] retransmission_date when to next retransmit
+ * @param[out] code set to the code which will be checked for later
+ * @return transaction status,
+ * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if we are out of valid tries,
+ * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if @a code is now in the DB
+ */
+ enum GNUNET_DB_QueryStatus
+ (*create_challenge_code)(
+ void *cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ struct GNUNET_TIME_Relative rotation_period,
+ struct GNUNET_TIME_Relative validity_period,
+ unsigned int retry_counter,
+ struct GNUNET_TIME_Absolute *retransmission_date,
+ uint64_t *code);
+
+
+ /**
+ * Remember in the database that we successfully sent a challenge.
+ *
+ * @param cls closure
+ * @param truth_uuid the identifier for the challenge
+ * @param code the challenge that was sent
+ */
+ enum GNUNET_DB_QueryStatus
+ (*mark_challenge_sent)(
+ void *cls,
+ const struct ANASTASIS_PaymentSecretP *payment_secret,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ uint64_t code);
+
+
+ /**
+ * Store payment for challenge.
+ *
+ * @param cls closure
+ * @param truth_key identifier of the challenge to pay
+ * @param payment_secret payment secret which the user must provide with every upload
+ * @param amount how much we asked for
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*record_challenge_payment)(
+ void *cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ const struct ANASTASIS_PaymentSecretP *payment_secret,
+ const struct TALER_Amount *amount);
+
+
+ /**
+ * Record refund for challenge.
+ *
+ * @param cls closure
+ * @param truth_key identifier of the challenge to pay
+ * @param payment_secret payment secret which the user must provide with every upload
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*record_challenge_refund)(
+ void *cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ const struct ANASTASIS_PaymentSecretP *payment_secret);
+
+
+ /**
+ * Lookup for a pending payment for a certain challenge
+ *
+ * @param cls closure
+ * @param truth_uuid identification of the challenge
+ * @param[out] payment_secret set to the challenge payment secret
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_challenge_payment)(
+ void *cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ struct ANASTASIS_PaymentSecretP *payment_secret);
+
+
+ /**
+ * Update payment status of challenge
+ *
+ * @param cls closure
+ * @param truth_uuid which challenge received a payment
+ * @param payment_identifier proof of payment, must be unique and match pending payment
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*update_challenge_payment)(
+ void *cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ const struct ANASTASIS_PaymentSecretP *payment_identifier);
+
+
+ /**
+ * Function called to remove all expired codes from the database.
+ * FIXME: maybe implement as part of @e gc() in the future.
+ *
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*challenge_gc)(void *cls);
+
+
+};
+#endif
diff --git a/src/include/anastasis_json.h b/src/include/anastasis_json.h
new file mode 100644
index 0000000..9e8d924
--- /dev/null
+++ b/src/include/anastasis_json.h
@@ -0,0 +1,410 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/anastasis_json.h
+ * @brief anastasis de-/serialization api
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ */
+#ifndef ANASTASIS_JSON_H
+#define ANASTASIS_JSON_H
+
+#include <jansson.h>
+#include <gnunet/gnunet_util_lib.h>
+#include "anastasis_error_codes.h"
+
+/**
+ * Enumeration of possible backup process status.
+ */
+enum ANASTASIS_BackupStatus
+{
+ ANASTASIS_BS_INITIAL,
+ ANASTASIS_BS_SELECT_CONTINENT,
+ ANASTASIS_BS_SELECT_COUNTRY,
+ ANASTASIS_BS_ENTER_USER_ATTRIBUTES,
+ ANASTASIS_BS_ADD_AUTHENTICATION_METHOD,
+ ANASTASIS_BS_ADD_POLICY,
+ ANASTASIS_BS_PAY
+};
+
+/**
+ * Enumeration of possible recovery process status.
+ */
+enum ANASTASIS_RecoveryStatus
+{
+ ANASTASIS_RS_INITIAL,
+ ANASTASIS_RS_SELECT_CONTINENT,
+ ANASTASIS_RS_SELECT_COUNTRY,
+ ANASTASIS_RS_ENTER_USER_ATTRIBUTES,
+ ANASTASIS_RS_SOLVE_CHALLENGE
+};
+
+// A state for the backup process.
+struct ANASTASIS_BackupState
+{
+ enum ANASTASIS_BackupStatus status;
+
+ union
+ {
+
+ struct
+ {
+ // empty!
+ } select_continent;
+
+ struct
+ {
+ const char *continent;
+ } select_country;
+
+ struct
+ {
+ const char *continent;
+ const char *country;
+ const char *currency; // derived or per manual override!
+ json_t *user_attributes;
+ } enter_attributes;
+
+ struct
+ {
+ const char *continent;
+ const char *country;
+ const char *currency;
+ json_t *user_attributes;
+
+ struct AuthenticationDetails
+ {
+ enum AuthenticationMethod
+ {
+ SMS,
+ VIDEO,
+ SECQUEST,
+ EMAIL,
+ SNAILMAIL
+ };
+ char *provider_url;
+ union Truth
+ {
+
+ struct
+ {
+ char *phone_number;
+ } sms;
+
+ struct
+ {
+ char *question;
+ char *answer; // FIXME: Reasonable to store answer in clear text here?
+ } secquest;
+
+ struct
+ {
+ char *mailaddress;
+ } email;
+
+ struct
+ {
+ char *full_name;
+ char *street; // street name + number
+ char *postal_code;
+ char *city;
+ char *country;
+ } snailmail;
+
+ struct
+ {
+ char *path_to_picture;
+ } video;
+ } truth;
+ }*ad; // array
+ size_t ad_length;
+ } add_authentication;
+ struct
+ {
+ const char *continent;
+ const char *country;
+ const char *currency;
+ json_t *user_attributes;
+
+ struct AuthenticationDetails
+ {
+ enum AuthenticationMethod
+ {
+ SMS,
+ VIDEO,
+ SECQUEST,
+ EMAIL,
+ SNAILMAIL
+ };
+ char *provider_url;
+ union Truth
+ {
+
+ struct
+ {
+ char *phone_number;
+ } sms;
+
+ struct
+ {
+ char *question;
+ char *answer; // FIXME: Reasonable to store answer in clear text here?
+ } secquest;
+
+ struct
+ {
+ char *mailaddress;
+ } email;
+
+ struct
+ {
+ char *full_name;
+ char *street; // street name + number
+ char *postal_code;
+ char *city;
+ char *country;
+ } snailmail;
+
+ struct
+ {
+ char *path_to_picture;
+ } video;
+ } truth;
+ }*ad; // array
+ size_t ad_length;
+
+ struct PolicyDetails
+ {
+ struct AuthenticationDetails *ad; // array
+ }*pd; // array
+ size_t pd_length;
+ } add_policy;
+ // FIXME: add_payment
+ } details;
+};
+
+
+// A state for the recovery process.
+struct ANASTASIS_RecoveryState
+{
+ enum ANASTASIS_RecoveryStatus status;
+
+ struct
+ {
+ // empty!
+ } select_continent;
+
+ struct
+ {
+ const char *continent;
+ } select_country;
+
+ struct
+ {
+ const char *continent;
+ const char *country;
+ const char *currency; // derived or per manual override!
+ json_t *user_attributes;
+ } enter_attributes;
+
+ struct
+ {
+ const char *continent;
+ const char *country;
+ const char *currency;
+ json_t *user_attributes;
+
+ struct ChallengeDetails
+ {
+ enum AuthenticationMethod
+ {
+ SMS,
+ VIDEO,
+ SECQUEST,
+ EMAIL,
+ SNAILMAIL
+ };
+ char *provider_url;
+ union Challenge
+ {
+
+ struct
+ {
+ char *phone_number;
+ char *code;
+ } sms;
+
+ struct
+ {
+ char *question;
+ char *answer; // FIXME: Reasonable to store answer in clear text here?
+ } secquest;
+
+ struct
+ {
+ char *mailaddress;
+ char *code;
+ } email;
+
+ struct
+ {
+ char *full_name;
+ char *street; // street name + number
+ char *postal_code;
+ char *city;
+ char *country;
+ char *code;
+ } snailmail;
+
+ struct
+ {
+ char *path_to_picture;
+ char *code;
+ } video;
+ } truth;
+ }*cd; // array
+ size_t cd_length;
+ } solve_challenge;
+};
+
+/**
+ * Definition of actions on ANASTASIS_BackupState.
+ */
+struct ANASTASIS_BackupAction
+{
+ enum action
+ {
+ ANASTASIS_BA_GET_SELECT_CONTINENT,
+ ANASTASIS_BA_GET_SELECT_COUNTRY,
+ ANASTASIS_BA_GET_ENTER_USER_ATTRIBUTES,
+ ANASTASIS_BA_GET_ADD_AUTHENTICATION_METHOD,
+ ANASTASIS_BA_GET_ADD_POLICY,
+ ANASTASIS_BA_GET_PAY,
+ ANASTASIS_BA_SET_SELECT_CONTINENT,
+ ANASTASIS_BA_SET_SELECT_COUNTRY,
+ ANASTASIS_BA_SET_ENTER_USER_ATTRIBUTES,
+ ANASTASIS_BA_SET_ADD_AUTHENTICATION_METHOD,
+ ANASTASIS_BA_SET_ADD_POLICY,
+ ANASTASIS_BA_SET_PAY
+ };
+};
+
+/**
+ * Definition of actions on ANASTASIS_RecoveryState.
+ */
+struct ANASTASIS_RecoveryAction
+{
+ enum action
+ {
+ ANASTASIS_RS_GET_SELECT_CONTINENT,
+ ANASTASIS_RS_GET_SELECT_COUNTRY,
+ ANASTASIS_RS_GET_ENTER_USER_ATTRIBUTES,
+ ANASTASIS_RS_GET_SOLVE_CHALLENGE,
+ ANASTASIS_RS_SET_SELECT_CONTINENT,
+ ANASTASIS_RS_SET_SELECT_COUNTRY,
+ ANASTASIS_RS_SET_ENTER_USER_ATTRIBUTES,
+ ANASTASIS_RS_SET_SOLVE_CHALLENGE
+ };
+};
+
+
+/**
+ * Signature of the callback bassed to #ANASTASIS_apply_anastasis_backup_action
+ * for asynchronous actions on a #ANASTASIS_BackupState.
+ *
+ * @param cls closure
+ * @param new_bs the new #ANASTASIS_BackupState
+ * @param error error code
+ */
+typedef void
+(*ANASTASIS_BackupApplyActionCallback)(
+ void *cls,
+ const struct ANASTASIS_BackupState *new_bs,
+ enum TALER_ErrorCode error);
+
+
+/**
+ * Signature of the callback bassed to #ANASTASIS_apply_anastasis_recovery_action
+ * for asynchronous actions on a #ANASTASIS_RecoveryState.
+ *
+ * @param cls closure
+ * @param new_bs the new #ANASTASIS_RecoveryState
+ * @param error error code
+ */
+typedef void
+(*ANASTASIS_RecoveryApplyActionCallback)(
+ void *cls,
+ const struct ANASTASIS_RecoveryState *new_rs,
+ enum TALER_ErrorCode error);
+
+
+/**
+ * Returns an initial ANASTASIS_BackupState.
+ *
+ * @return initial ANASTASIS_BackupState
+ */
+struct ANASTASIS_BackupState *
+ANASTASIS_get_initial_backup_state ();
+
+
+/**
+ * Returns an initial ANASTASIS_RecoveryState.
+ *
+ * @return initial ANASTASIS_RecoveryState
+ */
+struct ANASTASIS_RecoveryState *
+ANASTASIS_get_initial_recovery_state ();
+
+
+/**
+ * Operates on a backup state depending on given #ANASTASIS_BackupState
+ * and #ANASTASIS_BackupAction. The new #ANASTASIS_BackupState is returned
+ * by a callback function.
+ * This function can do network access to talk to anastasis service providers.
+ *
+ * @param ctx url context for the event loop
+ * @param bs the previous *ANASTASIS_BackupState
+ * @param ba the action to do on #ANASTASIS_BackupState
+ * @param cb callback function to call with the action
+ */
+void
+ANASTASIS_apply_anastasis_backup_action (
+ struct GNUNET_CURL_Context *ctx,
+ struct ANASTASIS_BackupState *bs,
+ struct ANASTASIS_BackupAction *ba,
+ ANASTASIS_BackupApplyActionCallback cb);
+
+
+/**
+ * Operates on a recovery state depending on given #ANASTASIS_RecoveryState
+ * and #ANASTASIS_RecoveryAction. The new #ANASTASIS_RecoveryState is returned
+ * by a callback function.
+ * This function can do network access to talk to anastasis service providers.
+ *
+ * @param ctx url context for the event loop
+ * @param bs the previous *ANASTASIS_RecoveryState
+ * @param ba the action to do on #ANASTASIS_RecoveryState
+ * @param cb callback function to call with the action
+ */
+void
+ANASTASIS_apply_anastasis_recovery_action (
+ struct GNUNET_CURL_Context *ctx,
+ struct ANASTASIS_RecoveryState *rs,
+ struct ANASTASIS_RecoveryAction *ra,
+ ANASTASIS_RecoveryApplyActionCallback cb);
+
+#endif /* _ANASTASIS_JSON_H */ \ No newline at end of file
diff --git a/src/include/anastasis_redux.h b/src/include/anastasis_redux.h
new file mode 100644
index 0000000..7a0ff53
--- /dev/null
+++ b/src/include/anastasis_redux.h
@@ -0,0 +1,127 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 2021 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file include/anastasis_redux.h
+ * @brief anastasis reducer api
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ */
+#ifndef ANASTASIS_REDUX_H
+#define ANASTASIS_REDUX_H
+
+#include <jansson.h>
+#include "anastasis.h"
+#include <taler/taler_mhd_lib.h>
+#include <regex.h>
+
+
+/**
+ * Initialize reducer subsystem.
+ *
+ * @param ctx context to use for CURL requests.
+ */
+void
+ANASTASIS_redux_init (struct GNUNET_CURL_Context *ctx);
+
+
+/**
+ * Terminate reducer subsystem.
+ */
+void
+ANASTASIS_redux_done (void);
+
+
+/**
+ * Returns an initial ANASTASIS backup state.
+ *
+ * @return NULL on failure
+ */
+json_t *
+ANASTASIS_backup_start (const struct GNUNET_CONFIGURATION_Handle *cfg);
+
+
+/**
+ * Returns an initial ANASTASIS recovery state.
+ *
+ * @return NULL on failure
+ */
+json_t *
+ANASTASIS_recovery_start (const struct GNUNET_CONFIGURATION_Handle *cfg);
+
+
+/**
+ * Returns an initial ANASTASIS recovery state.
+ *
+ * @return NULL on failure
+ */
+json_t *
+ANASTASIS_recovery_start (const struct GNUNET_CONFIGURATION_Handle *cfg);
+
+
+/**
+ * Signature of the callback passed to #ANASTASIS_backup_action and
+ * #ANASTASIS_recover_action.
+ *
+ * @param cls closure
+ * @param error error code, #TALER_EC_NONE if @a new_bs is the new successful state
+ * @param new_state the new state of the operation (client should json_incref() to keep an alias)
+ */
+typedef void
+(*ANASTASIS_ActionCallback)(void *cls,
+ enum TALER_ErrorCode error,
+ json_t *new_state);
+
+
+/**
+ * Handle to an ongoing action. Only valid until the #ANASTASIS_ActionCallback is invoked.
+ */
+struct ANASTASIS_ReduxAction;
+
+
+/**
+ * Operates on a state depending on given #ANASTASIS_BackupState
+ * or #ANASTASIS_RecoveryState and #ANASTASIS_BackupAction or
+ * #ANASTASIS_RecoveryAction.
+ * The new #ANASTASIS_BackupState or #ANASTASIS_RecoveryState is returned
+ * by a callback function.
+ * This function can do network access to talk to anastasis service providers.
+ *
+ * @param state input state
+ * @param action what action to perform
+ * @param arguments data for the @a action
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return failure state or new state
+ */
+struct ANASTASIS_ReduxAction *
+ANASTASIS_redux_action (const json_t *state,
+ const char *action,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel ongoing redux action.
+ *
+ * @param ra action to cancel
+ */
+void
+ANASTASIS_redux_action_cancel (struct ANASTASIS_ReduxAction *ra);
+
+
+#endif /* _ANASTASIS_REDUX_H */
diff --git a/src/include/anastasis_service.h b/src/include/anastasis_service.h
new file mode 100644
index 0000000..98ac490
--- /dev/null
+++ b/src/include/anastasis_service.h
@@ -0,0 +1,703 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2019-2021 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License along with
+ Anastasis; see the file COPYING.LIB. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file include/anastasis_service.h
+ * @brief C interface of libanastasisrest, a C library to use merchant's HTTP API
+ * @author Christian Grothoff
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ */
+#ifndef ANASTASIS_SERVICE_H
+#define ANASTASIS_SERVICE_H
+
+#include "anastasis_crypto_lib.h"
+#include "anastasis_util_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include <jansson.h>
+
+
+/**
+ * Anastasis authorization method configuration
+ */
+struct ANASTASIS_AuthorizationMethodConfig
+{
+ /**
+ * Type of the method, i.e. "question".
+ */
+ const char *type;
+
+ /**
+ * Fee charged for accessing key share using this method.
+ */
+ struct TALER_Amount usage_fee;
+};
+
+
+/**
+ * @brief Anastasis configuration data.
+ */
+struct ANASTASIS_Config
+{
+ /**
+ * Protocol version supported by the server.
+ */
+ const char *version;
+
+ /**
+ * Business name of the anastasis provider.
+ */
+ const char *business_name;
+
+ /**
+ * Currency used for payments by the server.
+ */
+ const char *currency;
+
+ /**
+ * Array of authorization methods supported by the server.
+ */
+ const struct ANASTASIS_AuthorizationMethodConfig *methods;
+
+ /**
+ * Length of the @e methods array.
+ */
+ unsigned int methods_length;
+
+ /**
+ * Maximum size of an upload in megabytes.
+ */
+ uint32_t storage_limit_in_megabytes;
+
+ /**
+ * Annual fee for an account / policy upload.
+ */
+ struct TALER_Amount annual_fee;
+
+ /**
+ * Fee for a truth upload.
+ */
+ struct TALER_Amount truth_upload_fee;
+
+ /**
+ * Maximum legal liability for data loss covered by the
+ * provider.
+ */
+ struct TALER_Amount liability_limit;
+
+ /**
+ * Server salt.
+ */
+ struct ANASTASIS_CRYPTO_ProviderSaltP salt;
+
+};
+
+
+/**
+ * Function called with the result of a /config request.
+ * Note that an HTTP status of #MHD_HTTP_OK is no guarantee
+ * that @a acfg is non-NULL. @a acfg is non-NULL only if
+ * the server provided an acceptable response.
+ *
+ * @param cls closure
+ * @param http_status the HTTP status
+ * @param acfg configuration obtained, NULL if we could not parse it
+ */
+typedef void
+(*ANASTASIS_ConfigCallback)(void *cls,
+ unsigned int http_status,
+ const struct ANASTASIS_Config *acfg);
+
+
+/**
+ * @brief A Config Operation Handle
+ */
+struct ANASTASIS_ConfigOperation;
+
+
+/**
+ * Run a GET /config request against the Anastasis backend.
+ *
+ * @param ctx CURL context to use
+ * @param base_url base URL fo the Anastasis backend
+ * @param cb function to call with the results
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel the operation
+ */
+struct ANASTASIS_ConfigOperation *
+ANASTASIS_get_config (struct GNUNET_CURL_Context *ctx,
+ const char *base_url,
+ ANASTASIS_ConfigCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel ongoing #ANASTASIS_get_config() request.
+ *
+ * @param co configuration request to cancel.
+ */
+void
+ANASTASIS_config_cancel (struct ANASTASIS_ConfigOperation *co);
+
+
+/****** POLICY API ******/
+
+
+/**
+ * Detailed results from the successful download.
+ */
+struct ANASTASIS_DownloadDetails
+{
+ /**
+ * Signature (already verified).
+ */
+ struct ANASTASIS_AccountSignatureP sig;
+
+ /**
+ * Hash over @e policy and @e policy_size.
+ */
+ struct GNUNET_HashCode curr_policy_hash;
+
+ /**
+ * The backup we downloaded.
+ */
+ const void *policy;
+
+ /**
+ * Number of bytes in @e backup.
+ */
+ size_t policy_size;
+
+ /**
+ * Policy version returned by the service.
+ */
+ uint32_t version;
+};
+
+
+/**
+ * Handle for a GET /policy operation.
+ */
+struct ANASTASIS_PolicyLookupOperation;
+
+
+/**
+ * Callback to process a GET /policy request
+ *
+ * @param cls closure
+ * @param http_status HTTP status code for this request
+ * @param ec anastasis-specific error code
+ * @param obj the response body
+ */
+typedef void
+(*ANASTASIS_PolicyLookupCallback) (void *cls,
+ unsigned int http_status,
+ const struct ANASTASIS_DownloadDetails *dd);
+
+
+/**
+ * Does a GET /policy.
+ *
+ * @param ctx execution context
+ * @param backend_url base URL of the merchant backend
+ * @param anastasis_pub public key of the user's account
+ * @param cb callback which will work the response gotten from the backend
+ * @param cb_cls closure to pass to the callback
+ * @return handle for this operation, NULL upon errors
+ */
+struct ANASTASIS_PolicyLookupOperation *
+ANASTASIS_policy_lookup (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *anastasis_pub,
+ ANASTASIS_PolicyLookupCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Does a GET /policy for a specific version.
+ *
+ * @param ctx execution context
+ * @param backend_url base URL of the merchant backend
+ * @param anastasis_pub public key of the user's account
+ * @param cb callback which will work the response gotten from the backend
+ * @param cb_cls closure to pass to the callback
+ * @param version version of the policy to be requested
+ * @return handle for this operation, NULL upon errors
+ */
+struct ANASTASIS_PolicyLookupOperation *
+ANASTASIS_policy_lookup_version (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *anastasis_pub,
+ ANASTASIS_PolicyLookupCallback cb,
+ void *cb_cls,
+ unsigned int version);
+
+
+/**
+ * Cancel a GET /policy request.
+ *
+ * @param plo cancel the policy lookup operation
+ */
+void
+ANASTASIS_policy_lookup_cancel (
+ struct ANASTASIS_PolicyLookupOperation *plo);
+
+
+/**
+ * Handle for a POST /policy operation.
+ */
+struct ANASTASIS_PolicyStoreOperation;
+
+
+/**
+ * High-level ways how an upload may conclude.
+ */
+enum ANASTASIS_UploadStatus
+{
+ /**
+ * Backup was successfully made.
+ */
+ ANASTASIS_US_SUCCESS = 0,
+
+ /**
+ * Account expired or payment was explicitly requested
+ * by the client.
+ */
+ ANASTASIS_US_PAYMENT_REQUIRED,
+
+ /**
+ * HTTP interaction failed, see HTTP status.
+ */
+ ANASTASIS_US_HTTP_ERROR,
+
+ /**
+ * We had an internal error (not sure this can happen,
+ * but reserved for HTTP 400 status codes).
+ */
+ ANASTASIS_US_CLIENT_ERROR,
+
+ /**
+ * Server had an internal error.
+ */
+ ANASTASIS_US_SERVER_ERROR,
+
+ /**
+ * Truth already exists. Not applicable for policy uploads.
+ */
+ ANASTASIS_US_CONFLICTING_TRUTH
+};
+
+
+/**
+ * Result of an upload.
+ */
+struct ANASTASIS_UploadDetails
+{
+ /**
+ * High level status of the upload operation. Determines @e details.
+ */
+ enum ANASTASIS_UploadStatus us;
+
+ /**
+ * HTTP status code.
+ */
+ unsigned int http_status;
+
+ /**
+ * Taler error code.
+ */
+ enum TALER_ErrorCode ec;
+
+ union
+ {
+
+ struct
+ {
+ /**
+ * Hash of the stored recovery data, returned if
+ * @e us is #ANASTASIS_US_SUCCESS.
+ */
+ const struct GNUNET_HashCode *curr_backup_hash;
+
+ /**
+ * At what time is the provider set to forget this
+ * policy (because the account expires)?
+ */
+ struct GNUNET_TIME_Absolute policy_expiration;
+
+ /**
+ * Version number of the resulting policy.
+ */
+ unsigned long long policy_version;
+
+ } success;
+
+ /**
+ * Details about required payment.
+ */
+ struct
+ {
+ /**
+ * A taler://pay/-URI with a request to pay the annual fee for
+ * the service. Returned if @e us is #ANASTASIS_US_PAYMENT_REQUIRED.
+ */
+ const char *payment_request;
+
+ /**
+ * The payment secret (aka order ID) extracted from the @e payment_request.
+ */
+ struct ANASTASIS_PaymentSecretP ps;
+ } payment;
+
+ } details;
+};
+
+
+/**
+ * Callback to process a POST /policy request
+ *
+ * @param cls closure
+ * @param http_status HTTP status code for this request
+ * @param obj the decoded response body
+ */
+typedef void
+(*ANASTASIS_PolicyStoreCallback) (void *cls,
+ const struct ANASTASIS_UploadDetails *up);
+
+
+/**
+ * Store policies, does a POST /policy/$ACCOUNT_PUB
+ *
+ * @param ctx the CURL context used to connect to the backend
+ * @param backend_url backend's base URL, including final "/"
+ * @param anastasis_priv private key of the user's account
+ * @param recovery_data policy data to be stored
+ * @param recovery_data_size number of bytes in @a recovery_data
+ * @param payment_years_requested for how many years would the client like the service to store the truth?
+ * @param paid_order_id payment identifier of last payment
+ * @param payment_timeout how long to wait for the payment, use
+ * #GNUNET_TIME_UNIT_ZERO to let the server pick
+ * @param cb callback processing the response from /policy
+ * @param cb_cls closure for cb
+ * @return handle for the operation
+ */
+struct ANASTASIS_PolicyStoreOperation *
+ANASTASIS_policy_store (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const struct ANASTASIS_CRYPTO_AccountPrivateKeyP *anastasis_priv,
+ const void *recovery_data,
+ size_t recovery_data_size,
+ uint32_t payment_years_requested,
+ const struct ANASTASIS_PaymentSecretP *payment_secret,
+ struct GNUNET_TIME_Relative payment_timeout,
+ ANASTASIS_PolicyStoreCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel a POST /policy request.
+ *
+ * @param pso the policy store operation to cancel
+ */
+void
+ANASTASIS_policy_store_cancel (
+ struct ANASTASIS_PolicyStoreOperation *pso);
+
+
+/****** TRUTH API ******/
+
+
+/**
+ * Operational status.
+ */
+enum ANASTASIS_KeyShareDownloadStatus
+{
+ /**
+ * We got the encrypted key share.
+ */
+ ANASTASIS_KSD_SUCCESS = 0,
+
+ /**
+ * Payment is needed to proceed with the recovery.
+ */
+ ANASTASIS_KSD_PAYMENT_REQUIRED,
+
+ /**
+ * The provided answer was wrong or missing. Instructions for
+ * getting a good answer may be provided.
+ */
+ ANASTASIS_KSD_INVALID_ANSWER,
+
+ /**
+ * To answer the challenge, the client should be redirected to
+ * the given URL.
+ */
+ ANASTASIS_KSD_REDIRECT_FOR_AUTHENTICATION,
+
+ /**
+ * The provider had an error.
+ */
+ ANASTASIS_KSD_SERVER_ERROR,
+
+ /**
+ * The provider claims we made an error.
+ */
+ ANASTASIS_KSD_CLIENT_FAILURE,
+
+ /**
+ * The provider does not know this truth.
+ */
+ ANASTASIS_KSD_TRUTH_UNKNOWN,
+
+ /**
+ * Too many attempts to solve the challenge were made in a short
+ * time. Try again laster.
+ */
+ ANASTASIS_KSD_RATE_LIMIT_EXCEEDED
+
+};
+
+
+/**
+ * Detailed results from the successful download.
+ */
+struct ANASTASIS_KeyShareDownloadDetails
+{
+
+ /**
+ * Operational status.
+ */
+ enum ANASTASIS_KeyShareDownloadStatus status;
+
+ /**
+ * Anastasis URL that returned the @e status.
+ */
+ const char *server_url;
+
+ /**
+ * Details depending on @e status.
+ */
+ union
+ {
+
+ /**
+ * The encrypted key share (if @e status is #ANASTASIS_KSD_SUCCESS).
+ */
+ struct ANASTASIS_CRYPTO_EncryptedKeyShareP eks;
+
+ /**
+ * Response if the challenge still needs to be answered, and the
+ * instructions are provided inline (no redirection).
+ */
+ struct
+ {
+
+ /**
+ * HTTP status returned by the server. #MHD_HTTP_ALREADY_REPORTED
+ * if the server did already send the challenge to the user,
+ * #MHD_HTTP_FORBIDDEN if the answer was wrong (or missing).
+ */
+ unsigned int http_status;
+
+ /**
+ * Response with server-side reply containing instructions for the user
+ */
+ const char *body;
+
+ /**
+ * Content-type: mime type of @e body, NULL if server did not provide any.
+ */
+ const char *content_type;
+
+ /**
+ * Number of bytes in @e body.
+ */
+ size_t body_size;
+
+ } open_challenge;
+
+ /**
+ * URL with instructions for the user to satisfy the challenge, if
+ * @e status is #ANASTASIS_KSD_REDIRECT_FOR_AUTHENTICATION.
+ */
+ const char *redirect_url;
+
+ /**
+ * Response with instructions for how to pay, if
+ * @e status is #ANASTASIS_KSD_PAYMENT_REQUIRED.
+ */
+ struct
+ {
+
+ /**
+ * "taler://pay" URL with details how to pay for the challenge.
+ */
+ const char *taler_pay_uri;
+
+ /**
+ * The order ID from @e taler_pay_uri.
+ */
+ struct ANASTASIS_PaymentSecretP payment_secret;
+
+ } payment_required;
+
+
+ /**
+ * Response with details about a server-side failure, if
+ * @e status is #ANASTASIS_KSD_SERVER_FAILURE,
+ * #ANASTASIS_KSD_CLIENT_FAILURE or #ANASTASIS_KSD_TRUTH_UNKNOWN.
+ */
+ struct
+ {
+
+ /**
+ * HTTP status returned by the server.
+ */
+ unsigned int http_status;
+
+ /**
+ * Taler-specific error code.
+ */
+ enum TALER_ErrorCode ec;
+
+ } server_failure;
+
+ } details;
+};
+
+
+/**
+ * Handle for a GET /truth operation.
+ */
+struct ANASTASIS_KeyShareLookupOperation;
+
+
+/**
+ * Callback to process a GET /truth request
+ *
+ * @param cls closure
+ * @param http_status HTTP status code for this request
+ * @param kdd details about the key share
+ */
+typedef void
+(*ANASTASIS_KeyShareLookupCallback) (
+ void *cls,
+ const struct ANASTASIS_KeyShareDownloadDetails *kdd);
+
+
+/**
+ * Does a GET /truth.
+ *
+ * @param ctx execution context
+ * @param backend_url base URL of the merchant backend
+ * @param truth_public_key identification of the Truth
+ * @param truth_key Key used to Decrypt the Truth on the Server
+ * @param payment_secret secret from the previously done payment NULL to trigger payment
+ * @param payment_timeout how long to wait for the payment, use
+ * #GNUNET_TIME_UNIT_ZERO to let the server pick
+ * @param hashed_answer hashed answer to the challenge
+ * @param cb callback which will work the response gotten from the backend
+ * @param cb_cls closure to pass to the callback
+ * @return handle for this operation, NULL upon errors
+ */
+struct ANASTASIS_KeyShareLookupOperation *
+ANASTASIS_keyshare_lookup (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ const struct ANASTASIS_CRYPTO_TruthKeyP *truth_key,
+ const struct ANASTASIS_PaymentSecretP *payment_secret,
+ struct GNUNET_TIME_Relative timeout,
+ const struct GNUNET_HashCode *hashed_answer,
+ ANASTASIS_KeyShareLookupCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel a GET /truth request.
+ *
+ * @param tlo cancel the truth lookup operation
+ */
+void
+ANASTASIS_keyshare_lookup_cancel (
+ struct ANASTASIS_KeyShareLookupOperation *kslo);
+
+
+/**
+ * Handle for a POST /truth operation.
+ */
+struct ANASTASIS_TruthStoreOperation;
+
+
+/**
+ * Callback to process a POST /truth request
+ *
+ * @param cls closure
+ * @param obj the response body
+ */
+typedef void
+(*ANASTASIS_TruthStoreCallback) (void *cls,
+ const struct ANASTASIS_UploadDetails *up);
+
+
+/**
+ * Store Truth, does a POST /truth/$UUID
+ *
+ * @param ctx the CURL context used to connect to the backend
+ * @param backend_url backend's base URL, including final "/"
+ * @param uuid unique identfication of the Truth Upload
+ * @param prev_truth_data_hash hash of the previous truth upload, NULL for the first upload ever
+ * @param type type of the authorization method
+ * @param encrypted_keyshare key material to return to the client upon authorization
+ * @param truth_mime mime type of @e encrypted_truth (after decryption)
+ * @param encrypted_truth_size number of bytes in @e encrypted_truth
+ * @param encrypted_truth contains the @a type-specific authorization data
+ * @param payment_years_requested for how many years would the client like the service to store the truth?
+ * @param payment_timeout how long to wait for the payment, use
+ * #GNUNET_TIME_UNIT_ZERO to let the server pick
+ * @param cb callback processing the response from /truth
+ * @param cb_cls closure for cb
+ * @return handle for the operation
+ */
+struct ANASTASIS_TruthStoreOperation *
+ANASTASIS_truth_store (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid,
+ const char *type,
+ const struct ANASTASIS_CRYPTO_EncryptedKeyShareP *encrypted_keyshare,
+ const char *truth_mime,
+ size_t encrypted_truth_size,
+ const void *encrypted_truth,
+ uint32_t payment_years_requested,
+ struct GNUNET_TIME_Relative payment_timeout,
+ ANASTASIS_TruthStoreCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel a POST /truth request.
+ *
+ * @param tso the truth store operation
+ */
+void
+ANASTASIS_truth_store_cancel (
+ struct ANASTASIS_TruthStoreOperation *tso);
+
+
+#endif /* _ANASTASIS_SERVICE_H */
diff --git a/src/include/anastasis_testing_lib.h b/src/include/anastasis_testing_lib.h
new file mode 100644
index 0000000..a54e3ae
--- /dev/null
+++ b/src/include/anastasis_testing_lib.h
@@ -0,0 +1,884 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file include/anastasis_testing_lib.h
+ * @brief API for writing an interpreter to test Taler components
+ * @author Christian Grothoff <christian@grothoff.org>
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ */
+#ifndef ANASTASIS_TESTING_LIB_H
+#define ANASTASIS_TESTING_LIB_H
+
+#include "anastasis.h"
+#include <taler/taler_testing_lib.h>
+#include <microhttpd.h>
+
+/* ********************* Helper functions ********************* */
+
+#define ANASTASIS_FAIL() \
+ do {GNUNET_break (0); return NULL; } while (0)
+
+/**
+ * Index used in #ANASTASIS_TESTING_get_trait_hash() for the current hash.
+ */
+#define ANASTASIS_TESTING_TRAIT_HASH_CURRENT 0
+
+/**
+ * Index used in #SYNC_TESTING_get_trait_hash() for the previous hash.
+ */
+#define ANASTASIS_TESTING_TRAIT_HASH_PREVIOUS 1
+
+/**
+ * Obtain a hash from @a cmd.
+ *
+ * @param cmd command to extract the number from.
+ * @param index the number's index number, #ANASTASIS_TESTING_TRAIT_HASH_CURRENT or
+ * #SYNC_TESTING_TRAIT_HASH_PREVIOUS
+ * @param[out] h set to the hash coming from @a cmd.
+ * @return #GNUNET_OK on success.
+ */
+int
+ANASTASIS_TESTING_get_trait_hash (const struct TALER_TESTING_Command *cmd,
+ unsigned int index,
+ const struct GNUNET_HashCode **h);
+
+
+/**
+ * Offer a hash.
+ *
+ * @param index the number's index number.
+ * @param h the hash to offer.
+ * @return #GNUNET_OK on success.
+ */
+struct TALER_TESTING_Trait
+ANASTASIS_TESTING_make_trait_hash (unsigned int index,
+ const struct GNUNET_HashCode *h);
+
+
+/**
+ * Obtain a truth decryption key from @a cmd.
+ *
+ * @param cmd command to extract the public key from.
+ * @param index usually 0
+ * @param[out] key set to the account public key used in @a cmd.
+ * @return #GNUNET_OK on success.
+ */
+int
+ANASTASIS_TESTING_get_trait_truth_key (
+ const struct TALER_TESTING_Command *cmd,
+ unsigned int index,
+ const struct ANASTASIS_CRYPTO_TruthKeyP **key);
+
+
+/**
+ * Offer an truth decryption key.
+ *
+ * @param index usually zero
+ * @param h the account_pub to offer.
+ * @return #GNUNET_OK on success.
+ */
+struct TALER_TESTING_Trait
+ANASTASIS_TESTING_make_trait_truth_key (
+ unsigned int index,
+ const struct ANASTASIS_CRYPTO_TruthKeyP *h);
+
+
+/**
+ * Obtain an account public key from @a cmd.
+ *
+ * @param cmd command to extract the public key from.
+ * @param index usually 0
+ * @param[out] pub set to the account public key used in @a cmd.
+ * @return #GNUNET_OK on success.
+ */
+int
+ANASTASIS_TESTING_get_trait_account_pub (
+ const struct
+ TALER_TESTING_Command *cmd,
+ unsigned int index,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP **pub);
+
+
+/**
+ * Offer an account public key.
+ *
+ * @param index usually zero
+ * @param h the account_pub to offer.
+ * @return #GNUNET_OK on success.
+ */
+struct TALER_TESTING_Trait
+ANASTASIS_TESTING_make_trait_account_pub (
+ unsigned int index,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *h);
+
+
+/**
+ * Obtain an account private key from @a cmd.
+ *
+ * @param cmd command to extract the number from.
+ * @param index must be 0
+ * @param[out] priv set to the account private key used in @a cmd.
+ * @return #GNUNET_OK on success.
+ */
+int
+ANASTASIS_TESTING_get_trait_account_priv (
+ const struct
+ TALER_TESTING_Command *cmd,
+ unsigned int index,
+ const struct ANASTASIS_CRYPTO_AccountPrivateKeyP **priv);
+
+
+/**
+ * Offer an account private key.
+ *
+ * @param index usually zero
+ * @param priv the account_priv to offer.
+ * @return #GNUNET_OK on success.
+ */
+struct TALER_TESTING_Trait
+ANASTASIS_TESTING_make_trait_account_priv (
+ unsigned int index,
+ const struct
+ ANASTASIS_CRYPTO_AccountPrivateKeyP *priv);
+
+/**
+ * Obtain an account public key from @a cmd.
+ *
+ * @param cmd command to extract the payment identifier from.
+ * @param index the payment identifier's index number.
+ * @param[out] payment_secret set to the payment secret coming from @a cmd.
+ * @return #GNUNET_OK on success.
+ */
+int
+ANASTASIS_TESTING_get_trait_payment_secret (
+ const struct TALER_TESTING_Command *cmd,
+ unsigned int index,
+ const struct ANASTASIS_PaymentSecretP **payment_secret);
+
+
+/**
+ * Offer a payment secret.
+ *
+ * @param index usually zero
+ * @param h the payment secret to offer.
+ * @return #GNUNET_OK on success.
+ */
+struct TALER_TESTING_Trait
+ANASTASIS_TESTING_make_trait_payment_secret (
+ unsigned int index,
+ const struct ANASTASIS_PaymentSecretP *h);
+
+
+/**
+ * Obtain an truth UUID from @a cmd.
+ *
+ * @param cmd command to extract the number from.
+ * @param index the number's index number.
+ * @param[out] uuid set to the number coming from @a cmd.
+ * @return #GNUNET_OK on success.
+ */
+int
+ANASTASIS_TESTING_get_trait_truth_uuid (
+ const struct TALER_TESTING_Command *cmd,
+ unsigned int index,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP **uuid);
+
+
+/**
+ * Offer a truth UUID.
+ *
+ * @param index the number's index number.
+ * @param uuid the UUID to offer.
+ * @return #GNUNET_OK on success.
+ */
+struct TALER_TESTING_Trait
+ANASTASIS_TESTING_make_trait_truth_uuid (
+ unsigned int index,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid);
+
+
+/**
+ * Obtain an encrypted key share from @a cmd.
+ *
+ * @param cmd command to extract the number from.
+ * @param index the number's index number.
+ * @param[out] uuid set to the number coming from @a cmd.
+ * @return #GNUNET_OK on success.
+ */
+int
+ANASTASIS_TESTING_get_trait_eks (
+ const struct TALER_TESTING_Command *cmd,
+ unsigned int index,
+ const struct ANASTASIS_CRYPTO_EncryptedKeyShareP **eks);
+
+
+/**
+ * Offer an encrypted key share.
+ *
+ * @param index the number's index number.
+ * @param eks the encrypted key share to offer.
+ * @return #GNUNET_OK on success.
+ */
+struct TALER_TESTING_Trait
+ANASTASIS_TESTING_make_trait_eks (
+ unsigned int index,
+ const struct ANASTASIS_CRYPTO_EncryptedKeyShareP *eks);
+
+
+/**
+ * Obtain a code from @a cmd.
+ *
+ * @param cmd command to extract the number from.
+ * @param index the number's index number.
+ * @param[out] code set to the number coming from @a cmd.
+ * @return #GNUNET_OK on success.
+ */
+int
+ANASTASIS_TESTING_get_trait_code (
+ const struct TALER_TESTING_Command *cmd,
+ unsigned int index,
+ const char **code);
+
+
+/**
+ * Offer a filename.
+ *
+ * @param index the number's index number.
+ * @param tpk the public key to offer.
+ * @return #GNUNET_OK on success.
+ */
+struct TALER_TESTING_Trait
+ANASTASIS_TESTING_make_trait_code (unsigned int index,
+ const char *code);
+
+
+/**
+ * Prepare the merchant execution. Create tables and check if
+ * the port is available.
+ *
+ * @param config_filename configuration filename.
+ *
+ * @return the base url, or NULL upon errors. Must be freed
+ * by the caller.
+ */
+char *
+TALER_TESTING_prepare_merchant (const char *config_filename);
+
+
+/**
+ * Start the merchant backend process. Assume the port
+ * is available and the database is clean. Use the "prepare
+ * merchant" function to do such tasks.
+ *
+ * @param config_filename configuration filename.
+ *
+ * @return the process, or NULL if the process could not
+ * be started.
+ */
+struct GNUNET_OS_Process *
+TALER_TESTING_run_merchant (const char *config_filename,
+ const char *merchant_url);
+
+
+/**
+ * Start the anastasis backend process. Assume the port
+ * is available and the database is clean. Use the "prepare
+ * anastasis" function to do such tasks.
+ *
+ * @param config_filename configuration filename.
+ *
+ * @return the process, or NULL if the process could not
+ * be started.
+ */
+struct GNUNET_OS_Process *
+ANASTASIS_TESTING_run_anastasis (const char *config_filename,
+ const char *anastasis_url);
+
+
+/**
+ * Prepare the anastasis execution. Create tables and check if
+ * the port is available.
+ *
+ * @param config_filename configuration filename.
+ *
+ * @return the base url, or NULL upon errors. Must be freed
+ * by the caller.
+ */
+char *
+ANASTASIS_TESTING_prepare_anastasis (const char *config_filename);
+
+
+/* ************** Specific interpreter commands ************ */
+
+
+/**
+ * Types of options for performing the upload. Used as a bitmask.
+ */
+enum ANASTASIS_TESTING_PolicyStoreOption
+{
+ /**
+ * Do everything by the book.
+ */
+ ANASTASIS_TESTING_PSO_NONE = 0,
+
+ /**
+ * Use random hash for previous upload instead of correct
+ * previous hash.
+ */
+ ANASTASIS_TESTING_PSO_PREV_HASH_WRONG = 1,
+
+ /**
+ * Request payment.
+ */
+ ANASTASIS_TESTING_PSO_REQUEST_PAYMENT = 2,
+
+ /**
+ * Reference payment order ID from linked previous upload.
+ */
+ ANASTASIS_TESTING_PSO_REFERENCE_ORDER_ID = 4
+
+};
+
+
+/**
+ * Make a "policy store" command.
+ *
+ * @param label command label
+ * @param anastasis_url base URL of the anastasis serving
+ * the policy store request.
+ * @param prev_upload reference to a previous upload we are
+ * supposed to update, NULL for none
+ * @param http_status expected HTTP status.
+ * @param pso policy store options
+ * @param recovery_data recovery data to post
+ * @param recovery_data_size size of recovery/policy data
+ * @return the command
+ */
+struct TALER_TESTING_Command
+ANASTASIS_TESTING_cmd_policy_store (
+ const char *label,
+ const char *anastasis_url,
+ const char *prev_upload,
+ unsigned int http_status,
+ enum ANASTASIS_TESTING_PolicyStoreOption pso,
+ const void *recovery_data,
+ size_t recovery_data_size);
+
+
+/**
+ * Make the "policy lookup" command.
+ *
+ * @param label command label
+ * @param ANASTASIS_url base URL of the ANASTASIS serving
+ * the policy lookup request.
+ * @param http_status expected HTTP status.
+ * @param upload_ref reference to upload command
+ * @return the command
+ */
+struct TALER_TESTING_Command
+ANASTASIS_TESTING_cmd_policy_lookup (const char *label,
+ const char *ANASTASIS_url,
+ unsigned int http_status,
+ const char *upload_ref);
+
+
+/**
+ * Types of options for performing the upload. Used as a bitmask.
+ */
+enum ANASTASIS_TESTING_TruthStoreOption
+{
+ /**
+ * Do everything by the book.
+ */
+ ANASTASIS_TESTING_TSO_NONE = 0,
+
+ /**
+ * Re-use UUID of previous upload instead of creating a random one.
+ */
+ ANASTASIS_TESTING_TSO_REFERENCE_UUID = 1,
+
+ /**
+ * Explicitly request payment.
+ */
+ ANASTASIS_TESTING_TSO_REQUEST_PAYMENT = 2,
+
+ /**
+ * Reference payment order ID from linked previous upload.
+ */
+ ANASTASIS_TESTING_TSO_REFERENCE_ORDER_ID = 4
+
+};
+
+
+/**
+ * Make the "truth store" command.
+ *
+ * @param label command label
+ * @param anastasis_url base URL of the anastasis serving
+ * the truth store request.
+ * @param prev_upload reference to a previous upload to get a payment ID from
+ * @param method what authentication method is being used
+ * @param mime_type MIME type of @a truth_data
+ * @param truth_data_size number of bytes in @a truth_data
+ * @param truth_data recovery data to post /truth (in plaintext)
+ * @param tso flags
+ * @param http_status expected HTTP status.
+ * @return the command
+ */
+struct TALER_TESTING_Command
+ANASTASIS_TESTING_cmd_truth_store (const char *label,
+ const char *anastasis_url,
+ const char *prev_upload,
+ const char *method,
+ const char *mime_type,
+ size_t truth_data_size,
+ const void *truth_data,
+ enum ANASTASIS_TESTING_TruthStoreOption tso,
+ unsigned int http_status);
+
+
+/**
+ * Make the "truth store" command for a secure question.
+ *
+ * @param label command label
+ * @param anastasis_url base URL of the anastasis serving
+ * the truth store request.
+ * @param prev_upload reference to a previous upload to get a payment ID from
+ * @param answer the answer to the question
+ * @param tso flags
+ * @param http_status expected HTTP status.
+ * @return the command
+ */
+struct TALER_TESTING_Command
+ANASTASIS_TESTING_cmd_truth_question (
+ const char *label,
+ const char *anastasis_url,
+ const char *prev_upload,
+ const char *answer,
+ enum ANASTASIS_TESTING_TruthStoreOption tso,
+ unsigned int http_status);
+
+
+/**
+ * Make the "keyshare lookup" command.
+ *
+ * @param label command label
+ * @param anastasis_url base URL of the ANASTASIS serving
+ * the keyshare lookup request.
+ * @param answer (response to challenge)
+ * @param payment_ref reference to the payment request
+ * @param upload_ref reference to upload command
+ * @param ksdd expected status
+ * @return the command
+ */
+struct TALER_TESTING_Command
+ANASTASIS_TESTING_cmd_keyshare_lookup (
+ const char *label,
+ const char *anastasis_url,
+ const char *answer,
+ const char *payment_ref,
+ const char *upload_ref,
+ int lookup_mode,
+ enum ANASTASIS_KeyShareDownloadStatus ksdd);
+
+
+/**
+ * Obtain a salt from @a cmd.
+ *
+ * @param cmd command to extract the salt from.
+ * @param index the salt's index number.
+ * @param[out] s set to the salt coming from @a cmd.
+ * @return #GNUNET_OK on success.
+ */
+int
+ANASTASIS_TESTING_get_trait_salt (
+ const struct TALER_TESTING_Command *cmd,
+ unsigned int index,
+ const struct ANASTASIS_CRYPTO_ProviderSaltP **s);
+
+
+/**
+ * Offer an salt.
+ *
+ * @param index the salt's index number.
+ * @param u the salt to offer.
+ * @return #GNUNET_OK on success.
+ */
+struct TALER_TESTING_Trait
+ANASTASIS_TESTING_make_trait_salt (
+ unsigned int index,
+ const struct ANASTASIS_CRYPTO_ProviderSaltP *s);
+
+
+/**
+ * Make the "/config" command.
+ *
+ * @param label command label
+ * @param anastasis_url base URL of the ANASTASIS serving
+ * the /config request.
+ * @param http_status expected HTTP status.
+ * @return the command
+ */
+struct TALER_TESTING_Command
+ANASTASIS_TESTING_cmd_config (const char *label,
+ const char *anastasis_url,
+ unsigned int http_status);
+
+/* ********************* test truth upload ********************* */
+
+/**
+ * Obtain a truth from @a cmd.
+ *
+ * @param cmd command to extract the truth from.
+ * @param index the index of the truth
+ * @param[out] t set to the truth coming from @a cmd.
+ * @return #GNUNET_OK on success.
+ */
+int
+ANASTASIS_TESTING_get_trait_truth (const struct TALER_TESTING_Command *cmd,
+ unsigned int index,
+ const struct ANASTASIS_Truth **t);
+
+
+/**
+ * Offer a truth.
+ *
+ * @param index the truth's index number.
+ * @param t the truth to offer.
+ * @return #GNUNET_OK on success.
+ */
+struct TALER_TESTING_Trait
+ANASTASIS_TESTING_make_trait_truth (unsigned int index,
+ const struct ANASTASIS_Truth *t);
+
+/**
+ * Creates a sample of id_data.
+ *
+ * @param id_data some sample data (e.g. AHV, name, surname, ...)
+ * @return truth in json format
+ */
+json_t *
+ANASTASIS_TESTING_make_id_data_example (const char *id_data);
+
+
+/**
+ * Make the "truth upload" command.
+ *
+ * @param label command label
+ * @param anastasis_url base URL of the anastasis serving our requests.
+ * @param id_data ID data to generate user identifier
+ * @param method specifies escrow method
+ * @param instructions specifies what the client/user has to do
+ * @param mime_type mime type of truth_data
+ * @param truth_data some truth data (e.g. hash of answer to a secret question)
+ * @param truth_data_size size of truth_data
+ * @param http_status expected HTTP status
+ * @param tso truth upload options
+ * @param upload_ref reference to the previous upload
+ * @return the command
+ */
+struct TALER_TESTING_Command
+ANASTASIS_TESTING_cmd_truth_upload (
+ const char *label,
+ const char *anastasis_url,
+ const json_t *id_data,
+ const char *method,
+ const char *instructions,
+ const char *mime_type,
+ const void *truth_data,
+ size_t truth_data_size,
+ unsigned int http_status,
+ enum ANASTASIS_TESTING_TruthStoreOption tso,
+ const char *upload_ref);
+
+
+/**
+ * Make the "truth upload" command for a security question.
+ *
+ * @param label command label
+ * @param anastasis_url base URL of the anastasis serving our requests.
+ * @param id_data ID data to generate user identifier
+ * @param instructions specifies what the client/user has to do
+ * @param mime_type mime type of truth_data
+ * @param answer the answer to the security question
+ * @param http_status expected HTTP status
+ * @param tso truth upload options
+ * @param upload_ref reference to the previous upload
+ * @return the command
+ */
+struct TALER_TESTING_Command
+ANASTASIS_TESTING_cmd_truth_upload_question (
+ const char *label,
+ const char *anastasis_url,
+ const json_t *id_data,
+ const char *instructions,
+ const char *mime_type,
+ const void *answer,
+ unsigned int http_status,
+ enum ANASTASIS_TESTING_TruthStoreOption tso,
+ const char *salt_ref);
+
+/* ********************* test policy create ********************* */
+
+/**
+ * Obtain a policy from @a cmd.
+ *
+ * @param cmd command to extract the policy from.
+ * @param index the index of the policy
+ * @param[out] p set to the policy coming from @a cmd.
+ * @return #GNUNET_OK on success.
+ */
+int
+ANASTASIS_TESTING_get_trait_policy (const struct TALER_TESTING_Command *cmd,
+ unsigned int index,
+ const struct ANASTASIS_Policy **p);
+
+
+/**
+ * Offer a policy.
+ *
+ * @param index the policy's index number.
+ * @param t the policy to offer.
+ * @return #GNUNET_OK on success.
+ */
+struct TALER_TESTING_Trait
+ANASTASIS_TESTING_make_trait_policy (unsigned int index,
+ const struct ANASTASIS_Policy *p);
+
+
+/**
+ * Make the "policy create" command.
+ *
+ * @param label command label
+ * @param ... NULL-terminated list of truth upload commands
+ * @return the command
+ */
+struct TALER_TESTING_Command
+ANASTASIS_TESTING_cmd_policy_create (const char *label,
+ ...);
+
+
+/* ********************* test secret share ********************* */
+
+/**
+ * Obtain the core secret from @a cmd.
+ *
+ * @param cmd command to extract the core secret from.
+ * @param index the index of the core secret (usually 0)
+ * @param[out] s set to the core secret coming from @a cmd.
+ * @return #GNUNET_OK on success.
+ */
+int
+ANASTASIS_TESTING_get_trait_core_secret (const struct
+ TALER_TESTING_Command *cmd,
+ unsigned int index,
+ const void **s);
+
+
+/**
+ * Offer the core secret.
+ *
+ * @param index the core secret's index number (usually 0).
+ * @param s the core secret to offer.
+ * @return #GNUNET_OK on success.
+ */
+struct TALER_TESTING_Trait
+ANASTASIS_TESTING_make_trait_core_secret (unsigned int index,
+ const void *s);
+
+/**
+ * Types of options for performing the secret sharing. Used as a bitmask.
+ */
+enum ANASTASIS_TESTING_SecretShareOption
+{
+ /**
+ * Do everything by the book.
+ */
+ ANASTASIS_TESTING_SSO_NONE = 0,
+
+ /**
+ * Request payment.
+ */
+ ANASTASIS_TESTING_SSO_REQUEST_PAYMENT = 2,
+
+ /**
+ * Reference payment order ID from linked previous upload.
+ */
+ ANASTASIS_TESTING_SSO_REFERENCE_ORDER_ID = 4
+
+};
+
+/**
+ * Make the "secret share" command.
+ *
+ * @param label command label
+ * @param anastasis_url base URL of the anastasis serving our requests.
+ * @param config_ref reference to /config operation for @a anastasis_url
+ * @param prev_secret_share reference to a previous secret share command
+ * @param id_data ID data to generate user identifier
+ * @param core_secret core secret to backup/recover
+ * @param core_secret_size size of @a core_secret
+ * @param http_status expected HTTP status.
+ * @param sso secret share options
+ * @param ... NULL-terminated list of policy create commands
+ * @return the command
+ */
+struct TALER_TESTING_Command
+ANASTASIS_TESTING_cmd_secret_share (
+ const char *label,
+ const char *anastasis_url,
+ const char *config_ref,
+ const char *prev_secret_share,
+ const json_t *id_data,
+ const void *core_secret,
+ size_t core_secret_size,
+ unsigned int http_status,
+ enum ANASTASIS_TESTING_SecretShareOption sso,
+ ...);
+
+
+/* ********************* test recover secret ********************* */
+
+/**
+ * Types of options for performing the secret recovery. Used as a bitmask.
+ */
+enum ANASTASIS_TESTING_RecoverSecretOption
+{
+ /**
+ * Do everything by the book.
+ */
+ ANASTASIS_TESTING_RSO_NONE = 0,
+
+ /**
+ * Request payment.
+ */
+ ANASTASIS_TESTING_RSO_REQUEST_PAYMENT = 2,
+
+ /**
+ * Reference payment order ID from linked previous download.
+ */
+ ANASTASIS_TESTING_RSO_REFERENCE_ORDER_ID = 4
+
+};
+
+
+/**
+ * Make the "recover secret" command.
+ *
+ * @param label command label
+ * @param anastasis_url base URL of the anastasis serving our requests.
+ * @param id_data identfication data from the user
+ * @param version of the recovery document to download
+ * @param rso recover secret options
+ * @param download_ref salt download reference
+ * @return the command
+ */
+struct TALER_TESTING_Command
+ANASTASIS_TESTING_cmd_recover_secret (
+ const char *label,
+ const char *anastasis_url,
+ const json_t *id_data,
+ unsigned int version,
+ enum ANASTASIS_TESTING_RecoverSecretOption rso,
+ const char *download_ref,
+ const char *core_secret_ref);
+
+
+/**
+ * Make "recover secret finish" command.
+ *
+ * @param label command label
+ * @param recover_label label of a "recover secret" command to wait for
+ * @param timeout how long to wait at most
+ * @return the command
+ */
+struct TALER_TESTING_Command
+ANASTASIS_TESTING_cmd_recover_secret_finish (
+ const char *label,
+ const char *recover_label,
+ struct GNUNET_TIME_Relative timeout);
+
+
+/* ********************* test challenge answer ********************* */
+/**
+ * Obtain a challenge from @a cmd.
+ *
+ * @param cmd command to extract the challenge from.
+ * @param index the index of the challenge
+ * @param[out] c set to the challenge coming from @a cmd.
+ * @return #GNUNET_OK on success.
+ */
+int
+ANASTASIS_TESTING_get_trait_challenge (const struct TALER_TESTING_Command *cmd,
+ unsigned int index,
+ const struct ANASTASIS_Challenge **c);
+
+/**
+ * Offer a challenge.
+ *
+ * @param index the challenge index number.
+ * @param c the challenge to offer.
+ * @return #GNUNET_OK on success.
+ */
+struct TALER_TESTING_Trait
+ANASTASIS_TESTING_make_trait_challenge (unsigned int index,
+ const struct ANASTASIS_Challenge *r);
+
+
+/**
+ * Create a "challenge start" command. Suitable for the "file"
+ * authorization plugin.
+ *
+ * @param label command label
+ * @param payment_ref reference to payment made for this challenge
+ * @param challenge_ref reference to the recovery process
+ * @param challenge_index defines the index of the trait to solve
+ * @param expected_cs expected reply type
+ * @return the command
+ */
+struct TALER_TESTING_Command
+ANASTASIS_TESTING_cmd_challenge_start (
+ const char *label,
+ const char *payment_ref,
+ const char *challenge_ref,
+ unsigned int challenge_index,
+ enum ANASTASIS_ChallengeStatus expected_cs);
+
+
+/**
+ * Make the "challenge answer" command.
+ *
+ * @param label command label
+ * @param payment_ref reference to payment made for this challenge
+ * @param challenge_ref reference to the recovery process
+ * @param challenge_index defines the index of the trait to solve
+ * @param answer to the challenge
+ * @param expected_cs expected reply type
+ * @return the command
+ */
+struct TALER_TESTING_Command
+ANASTASIS_TESTING_cmd_challenge_answer (
+ const char *label,
+ const char *payment_ref,
+ const char *challenge_ref,
+ unsigned int challenge_index,
+ const char *answer,
+ unsigned int mode,
+ enum ANASTASIS_ChallengeStatus expected_cs);
+
+
+#endif
diff --git a/src/include/anastasis_util_lib.h b/src/include/anastasis_util_lib.h
new file mode 100644
index 0000000..9515c20
--- /dev/null
+++ b/src/include/anastasis_util_lib.h
@@ -0,0 +1,82 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file include/anastasis_util_lib.h
+ * @brief anastasis client api
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ */
+#ifndef ANASTASIS_UTIL_LIB_H
+#define ANASTASIS_UTIL_LIB_H
+
+#include "anastasis_error_codes.h"
+#define GNU_TALER_ERROR_CODES_H 1
+#include <gnunet/gnunet_util_lib.h>
+#include <taler/taler_util.h>
+
+
+/**
+ * Return default project data used by Anastasis.
+ */
+const struct GNUNET_OS_ProjectData *
+ANASTASIS_project_data_default (void);
+
+
+/**
+ * Handle for the child management
+ */
+struct ANASTASIS_ChildWaitHandle;
+
+/**
+ * Defines a ANASTASIS_ChildCompletedCallback which is sent back
+ * upon death or completion of a child process. Used to trigger
+ * authentication commands.
+ *
+ * @param cls handle for the callback
+ * @param type type of the process
+ * @param exit_code status code of the process
+ *
+*/
+typedef void
+(*ANASTASIS_ChildCompletedCallback)(void *cls,
+ enum GNUNET_OS_ProcessStatusType type,
+ long unsigned int exit_code);
+
+
+/**
+ * Starts the handling of the child processes.
+ * Function checks the status of the child process and sends back a
+ * ANASTASIS_ChildCompletedCallback upon completion/death of the child.
+ *
+ * @param proc child process which is monitored
+ * @param cb reference to the callback which is called after completion
+ * @param cb_cls closure for the callback
+ * @return ANASTASIS_ChildWaitHandle is returned
+ */
+struct ANASTASIS_ChildWaitHandle *
+ANASTASIS_wait_child (struct GNUNET_OS_Process *proc,
+ ANASTASIS_ChildCompletedCallback cb,
+ void *cb_cls);
+
+/**
+ * Stop waiting on this child.
+ */
+void
+ANASTASIS_wait_child_cancel (struct ANASTASIS_ChildWaitHandle *cwh);
+
+
+#endif
diff --git a/src/include/gettext.h b/src/include/gettext.h
new file mode 100644
index 0000000..4585126
--- /dev/null
+++ b/src/include/gettext.h
@@ -0,0 +1,71 @@
+/* Convenience header for conditional use of GNU <libintl.h>.
+ Copyright Copyright (C) 1995-1998, 2000-2002 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published
+ by the Free Software Foundation; either version 3, or (at your option)
+ any later version.
+
+ This program 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ USA. */
+
+#ifndef _LIBGETTEXT_H
+#define _LIBGETTEXT_H 1
+
+/* NLS can be disabled through the configure --disable-nls option. */
+#if ENABLE_NLS
+
+/* Get declarations of GNU message catalog functions. */
+#include <libintl.h>
+
+#else
+
+/* Solaris /usr/include/locale.h includes /usr/include/libintl.h, which
+ chokes if dcgettext is defined as a macro. So include it now, to make
+ later inclusions of <locale.h> a NOP. We don't include <libintl.h>
+ as well because people using "gettext.h" will not include <libintl.h>,
+ and also including <libintl.h> would fail on SunOS 4, whereas <locale.h>
+ is GNUNET_OK. */
+#if defined(__sun)
+#include <locale.h>
+#endif
+
+/* Disabled NLS.
+ The casts to 'const char *' serve the purpose of producing warnings
+ for invalid uses of the value returned from these functions.
+ On pre-ANSI systems without 'const', the config.h file is supposed to
+ contain "#define const". */
+#define gettext(Msgid) ((const char *) (Msgid))
+#define dgettext(Domainname, Msgid) ((const char *) (Msgid))
+#define dcgettext(Domainname, Msgid, Category) ((const char *) (Msgid))
+#define ngettext(Msgid1, Msgid2, N) \
+ ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2))
+#define dngettext(Domainname, Msgid1, Msgid2, N) \
+ ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2))
+#define dcngettext(Domainname, Msgid1, Msgid2, N, Category) \
+ ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2))
+/* slight modification here to avoid warnings: generate GNUNET_NO code,
+ not even the cast... */
+#define textdomain(Domainname)
+#define bindtextdomain(Domainname, Dirname)
+#define bind_textdomain_codeset(Domainname, Codeset) ((const char *) (Codeset))
+
+#endif
+
+/* A pseudo function call that serves as a marker for the automated
+ extraction of messages, but does not call gettext(). The run-time
+ translation is done at a different place in the code.
+ The argument, String, should be a literal string. Concatenated strings
+ and other string expressions won't work.
+ The macro's expansion is not parenthesized, so that it is suitable as
+ initializer for static 'char[]' or 'const char[]' variables. */
+#define gettext_noop(String) String
+
+#endif /* _LIBGETTEXT_H */
diff --git a/src/include/platform.h b/src/include/platform.h
new file mode 100644
index 0000000..04a2dce
--- /dev/null
+++ b/src/include/platform.h
@@ -0,0 +1,60 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015, 2016 GNUnet e.V. and INRIA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero 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 include/platform.h
+ * @brief This file contains the includes and definitions which are used by the
+ * rest of the modules
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ */
+
+#ifndef PLATFORM_H_
+#define PLATFORM_H_
+
+/* Include our configuration header */
+#ifndef HAVE_USED_CONFIG_H
+# define HAVE_USED_CONFIG_H
+# ifdef HAVE_CONFIG_H
+# include "anastasis_config.h"
+# endif
+#endif
+
+
+#if (GNUNET_EXTRA_LOGGING >= 1)
+#define VERBOSE(cmd) cmd
+#else
+#define VERBOSE(cmd) do { break; } while (0)
+#endif
+
+/* Include the features available for GNU source */
+#define _GNU_SOURCE
+
+/* Include GNUnet's platform file */
+#include <gnunet/platform.h>
+
+/* Do not use shortcuts for gcrypt mpi */
+#define GCRYPT_NO_MPI_MACROS 1
+
+/* Do not use deprecated functions from gcrypt */
+#define GCRYPT_NO_DEPRECATED 1
+
+/* Ignore MHD deprecations for now as we want to be compatible
+ to "ancient" MHD releases. */
+#define MHD_NO_DEPRECATION 1
+
+#endif /* PLATFORM_H_ */
+
+/* end of platform.h */
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
new file mode 100644
index 0000000..07460d4
--- /dev/null
+++ b/src/lib/Makefile.am
@@ -0,0 +1,27 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include -I$(top_srcdir)/src/backend -I$(top_srcdir)/src/lib
+
+if USE_COVERAGE
+ AM_CFLAGS = --coverage -O0
+ XLIB = -lgcov
+endif
+
+lib_LTLIBRARIES = \
+ libanastasis.la
+
+libanastasis_la_LDFLAGS = \
+ -version-info 0:0:0 \
+ -no-undefined
+libanastasis_la_SOURCES = \
+ anastasis_backup.c \
+ anastasis_recovery.c
+libanastasis_la_LIBADD = \
+ $(top_builddir)/src/util/libanastasisutil.la \
+ $(top_builddir)/src/restclient/libanastasisrest.la \
+ -ltalerutil \
+ -ltalermerchant \
+ -lgnunetjson \
+ -lgnunetutil \
+ -ljansson \
+ -lz \
+ $(XLIB)
diff --git a/src/lib/anastasis_backup.c b/src/lib/anastasis_backup.c
new file mode 100644
index 0000000..ea55e6a
--- /dev/null
+++ b/src/lib/anastasis_backup.c
@@ -0,0 +1,979 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 2021 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @brief anastasis client api
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ */
+#include "platform.h"
+#include "anastasis.h"
+#include <taler/taler_merchant_service.h>
+#include <zlib.h>
+
+
+struct ANASTASIS_Truth
+{
+ /**
+ * Identification of the truth.
+ */
+ struct ANASTASIS_CRYPTO_TruthUUIDP uuid;
+
+ /**
+ * Keyshare of this truth, used to generate policy keys
+ */
+ struct ANASTASIS_CRYPTO_KeyShareP key_share;
+
+ /**
+ * Nonce used for the symmetric encryption.
+ */
+ struct ANASTASIS_CRYPTO_NonceP nonce;
+
+ /**
+ * Key used to encrypt this truth
+ */
+ struct ANASTASIS_CRYPTO_TruthKeyP truth_key;
+
+ /**
+ * Server salt used to derive user identifier
+ */
+ struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt;
+
+ /**
+ * Server salt used to derive hash from security answer
+ */
+ struct ANASTASIS_CRYPTO_QuestionSaltP salt;
+
+ /**
+ * Url of the server
+ */
+ char *url;
+
+ /**
+ * Method used for this truth
+ */
+ char *type;
+
+ /**
+ * Instructions for the user to recover this truth.
+ */
+ char *instructions;
+
+ /**
+ * Mime type of the truth, NULL if not given.
+ */
+ char *mime_type;
+
+};
+
+
+struct ANASTASIS_Truth *
+ANASTASIS_truth_from_json (const json_t *json)
+{
+ struct ANASTASIS_Truth *t = GNUNET_new (struct ANASTASIS_Truth);
+ const char *url;
+ const char *type;
+ const char *instructions;
+ const char *mime_type = NULL;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("url",
+ &url),
+ GNUNET_JSON_spec_string ("type",
+ &type),
+ GNUNET_JSON_spec_string ("instructions",
+ &instructions),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("mime_type",
+ &mime_type)),
+ GNUNET_JSON_spec_fixed_auto ("uuid",
+ &t->uuid),
+ GNUNET_JSON_spec_fixed_auto ("nonce",
+ &t->nonce),
+ GNUNET_JSON_spec_fixed_auto ("key_share",
+ &t->key_share),
+ GNUNET_JSON_spec_fixed_auto ("truth_key",
+ &t->truth_key),
+ GNUNET_JSON_spec_fixed_auto ("salt",
+ &t->salt),
+ GNUNET_JSON_spec_fixed_auto ("provider_salt",
+ &t->provider_salt),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (t);
+ return NULL;
+ }
+ t->url = GNUNET_strdup (url);
+ t->type = GNUNET_strdup (type);
+ t->instructions = GNUNET_strdup (instructions);
+ if (NULL != mime_type)
+ t->mime_type = GNUNET_strdup (mime_type);
+ return t;
+}
+
+
+json_t *
+ANASTASIS_truth_to_json (const struct ANASTASIS_Truth *t)
+{
+ return json_pack (
+ "{s:o,s:o,s:o,s:o,s:o"
+ ",s:o,s:s,s:s,s:s,s:s?}",
+ "uuid",
+ GNUNET_JSON_from_data_auto (&t->uuid),
+ "key_share",
+ GNUNET_JSON_from_data_auto (&t->key_share),
+ "truth_key",
+ GNUNET_JSON_from_data_auto (&t->truth_key),
+ "salt",
+ GNUNET_JSON_from_data_auto (&t->salt),
+ "nonce",
+ GNUNET_JSON_from_data_auto (&t->nonce),
+ "provider_salt",
+ GNUNET_JSON_from_data_auto (&t->provider_salt),
+ "url",
+ t->url,
+ "type",
+ t->type,
+ "instructions",
+ t->instructions,
+ "mime_type",
+ t->mime_type);
+}
+
+
+struct ANASTASIS_TruthUpload
+{
+
+ /**
+ * User identifier used for the keyshare encryption
+ */
+ struct ANASTASIS_CRYPTO_UserIdentifierP id;
+
+ /**
+ * CURL Context for the Post Request
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * Callback which sends back the generated truth object later used to build the policy
+ */
+ ANASTASIS_TruthCallback tc;
+
+ /**
+ * Closure for the Callback
+ */
+ void *tc_cls;
+
+ /**
+ * Reference to the Truthstore Operation
+ */
+ struct ANASTASIS_TruthStoreOperation *tso;
+
+ /**
+ * The truth we are uploading.
+ */
+ struct ANASTASIS_Truth *t;
+
+};
+
+
+/**
+ * Function called with the result of trying to upload truth.
+ *
+ * @param cls our `struct ANASTASIS_TruthUpload`
+ * @param ud details about the upload result
+ */
+static void
+truth_store_callback (void *cls,
+ const struct ANASTASIS_UploadDetails *ud)
+{
+ struct ANASTASIS_TruthUpload *tu = cls;
+
+ tu->tso = NULL;
+ tu->tc (tu->tc_cls,
+ tu->t,
+ ud);
+ tu->t = NULL;
+ ANASTASIS_truth_upload_cancel (tu);
+}
+
+
+struct ANASTASIS_TruthUpload *
+ANASTASIS_truth_upload3 (struct GNUNET_CURL_Context *ctx,
+ const struct ANASTASIS_CRYPTO_UserIdentifierP *user_id,
+ struct ANASTASIS_Truth *t,
+ const void *truth_data,
+ size_t truth_data_size,
+ uint32_t payment_years_requested,
+ struct GNUNET_TIME_Relative pay_timeout,
+ ANASTASIS_TruthCallback tc,
+ void *tc_cls)
+{
+ struct ANASTASIS_TruthUpload *tu;
+ struct ANASTASIS_CRYPTO_EncryptedKeyShareP encrypted_key_share;
+ struct GNUNET_HashCode nt;
+ void *encrypted_truth;
+ size_t encrypted_truth_size;
+
+ tu = GNUNET_new (struct ANASTASIS_TruthUpload);
+ tu->tc = tc;
+ tu->tc_cls = tc_cls;
+ tu->ctx = ctx;
+ tu->id = *user_id;
+ tu->tc = tc;
+ tu->tc_cls = tc_cls;
+ tu->t = t;
+
+ if (0 == strcmp ("question",
+ t->type))
+ {
+ char *answer;
+
+ answer = GNUNET_strndup (truth_data,
+ truth_data_size);
+ ANASTASIS_CRYPTO_secure_answer_hash (answer,
+ &t->uuid,
+ &t->salt,
+ &nt);
+ ANASTASIS_CRYPTO_keyshare_encrypt (&t->key_share,
+ &tu->id,
+ answer,
+ &encrypted_key_share);
+ GNUNET_free (answer);
+ truth_data = &nt;
+ truth_data_size = sizeof (nt);
+ }
+ else
+ {
+ ANASTASIS_CRYPTO_keyshare_encrypt (&t->key_share,
+ &tu->id,
+ NULL,
+ &encrypted_key_share);
+ }
+ ANASTASIS_CRYPTO_truth_encrypt (&t->nonce,
+ &t->truth_key,
+ truth_data,
+ truth_data_size,
+ &encrypted_truth,
+ &encrypted_truth_size);
+ tu->tso = ANASTASIS_truth_store (tu->ctx,
+ t->url,
+ &t->uuid,
+ t->type,
+ &encrypted_key_share,
+ t->mime_type,
+ encrypted_truth_size,
+ encrypted_truth,
+ payment_years_requested,
+ pay_timeout,
+ &truth_store_callback,
+ tu);
+ GNUNET_free (encrypted_truth);
+ if (NULL == tu->tso)
+ {
+ GNUNET_break (0);
+ ANASTASIS_truth_free (t);
+ ANASTASIS_truth_upload_cancel (tu);
+ return NULL;
+ }
+ return tu;
+}
+
+
+struct ANASTASIS_TruthUpload *
+ANASTASIS_truth_upload2 (
+ struct GNUNET_CURL_Context *ctx,
+ const struct ANASTASIS_CRYPTO_UserIdentifierP *user_id,
+ const char *provider_url,
+ const char *type,
+ const char *instructions,
+ const char *mime_type,
+ const struct ANASTASIS_CRYPTO_ProviderSaltP *provider_salt,
+ const void *truth_data,
+ size_t truth_data_size,
+ uint32_t payment_years_requested,
+ struct GNUNET_TIME_Relative pay_timeout,
+ const struct ANASTASIS_CRYPTO_NonceP *nonce,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid,
+ const struct ANASTASIS_CRYPTO_QuestionSaltP *salt,
+ const struct ANASTASIS_CRYPTO_TruthKeyP *truth_key,
+ const struct ANASTASIS_CRYPTO_KeyShareP *key_share,
+ ANASTASIS_TruthCallback tc,
+ void *tc_cls)
+{
+ struct ANASTASIS_Truth *t;
+
+ t = GNUNET_new (struct ANASTASIS_Truth);
+ t->url = GNUNET_strdup (provider_url);
+ t->type = GNUNET_strdup (type);
+ t->instructions = (NULL != instructions)
+ ? GNUNET_strdup (instructions)
+ : NULL;
+ t->mime_type = (NULL != mime_type)
+ ? GNUNET_strdup (mime_type)
+ : NULL;
+ t->provider_salt = *provider_salt;
+ t->salt = *salt;
+ t->nonce = *nonce;
+ t->uuid = *uuid;
+ t->truth_key = *truth_key;
+ t->key_share = *key_share;
+ return ANASTASIS_truth_upload3 (ctx,
+ user_id,
+ t,
+ truth_data,
+ truth_data_size,
+ payment_years_requested,
+ pay_timeout,
+ tc,
+ tc_cls);
+}
+
+
+struct ANASTASIS_TruthUpload *
+ANASTASIS_truth_upload (
+ struct GNUNET_CURL_Context *ctx,
+ const struct ANASTASIS_CRYPTO_UserIdentifierP *user_id,
+ const char *provider_url,
+ const char *type,
+ const char *instructions,
+ const char *mime_type,
+ const struct ANASTASIS_CRYPTO_ProviderSaltP *provider_salt,
+ const void *truth_data,
+ size_t truth_data_size,
+ uint32_t payment_years_requested,
+ struct GNUNET_TIME_Relative pay_timeout,
+ ANASTASIS_TruthCallback tc,
+ void *tc_cls)
+{
+ struct ANASTASIS_CRYPTO_QuestionSaltP question_salt;
+ struct ANASTASIS_CRYPTO_TruthUUIDP uuid;
+ struct ANASTASIS_CRYPTO_TruthKeyP truth_key;
+ struct ANASTASIS_CRYPTO_KeyShareP key_share;
+ struct ANASTASIS_CRYPTO_NonceP nonce;
+
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ &nonce,
+ sizeof (nonce));
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ &question_salt,
+ sizeof (question_salt));
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ &uuid,
+ sizeof (uuid));
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG,
+ &truth_key,
+ sizeof (truth_key));
+ ANASTASIS_CRYPTO_keyshare_create (&key_share);
+ return ANASTASIS_truth_upload2 (ctx,
+ user_id,
+ provider_url,
+ type,
+ instructions,
+ mime_type,
+ provider_salt,
+ truth_data,
+ truth_data_size,
+ payment_years_requested,
+ pay_timeout,
+ &nonce,
+ &uuid,
+ &question_salt,
+ &truth_key,
+ &key_share,
+ tc,
+ tc_cls);
+}
+
+
+void
+ANASTASIS_truth_upload_cancel (struct ANASTASIS_TruthUpload *tu)
+{
+ if (NULL != tu->tso)
+ {
+ ANASTASIS_truth_store_cancel (tu->tso);
+ tu->tso = NULL;
+ }
+ if (NULL != tu->t)
+ {
+ ANASTASIS_truth_free (tu->t);
+ tu->t = NULL;
+ }
+ GNUNET_free (tu);
+}
+
+
+void
+ANASTASIS_truth_free (struct ANASTASIS_Truth *t)
+{
+ GNUNET_free (t->url);
+ GNUNET_free (t->type);
+ GNUNET_free (t->instructions);
+ GNUNET_free (t->mime_type);
+ GNUNET_free (t);
+}
+
+
+struct ANASTASIS_Policy
+{
+ /**
+ * Encrypted policy master key
+ */
+ struct ANASTASIS_CRYPTO_PolicyKeyP policy_key;
+
+ /**
+ * Salt used to encrypt the master key
+ */
+ struct ANASTASIS_CRYPTO_MasterSaltP salt;
+
+ /**
+ * Array of truths
+ */
+ struct ANASTASIS_Truth **truths;
+
+ /**
+ * Length of @ truths array.
+ */
+ uint32_t truths_length;
+
+};
+
+
+/**
+ * Duplicate truth object.
+ *
+ * @param t object to duplicate
+ * @return copy of @a t
+ */
+static struct ANASTASIS_Truth *
+truth_dup (const struct ANASTASIS_Truth *t)
+{
+ struct ANASTASIS_Truth *d = GNUNET_new (struct ANASTASIS_Truth);
+
+ *d = *t;
+ d->url = GNUNET_strdup (t->url);
+ d->type = GNUNET_strdup (t->type);
+ d->instructions = GNUNET_strdup (t->instructions);
+ if (NULL != t->mime_type)
+ d->mime_type = GNUNET_strdup (t->mime_type);
+ return d;
+}
+
+
+struct ANASTASIS_Policy *
+ANASTASIS_policy_create (const struct ANASTASIS_Truth *truths[],
+ unsigned int truths_len)
+{
+ struct ANASTASIS_Policy *p;
+
+ p = GNUNET_new (struct ANASTASIS_Policy);
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ &p->salt,
+ sizeof (p->salt));
+ {
+ struct ANASTASIS_CRYPTO_KeyShareP key_shares[truths_len];
+
+ for (unsigned int i = 0; i < truths_len; i++)
+ key_shares[i] = truths[i]->key_share;
+ ANASTASIS_CRYPTO_policy_key_derive (key_shares,
+ truths_len,
+ &p->salt,
+ &p->policy_key);
+ }
+ p->truths = GNUNET_new_array (truths_len,
+ struct ANASTASIS_Truth *);
+ for (unsigned int i = 0; i<truths_len; i++)
+ p->truths[i] = truth_dup (truths[i]);
+ p->truths_length = truths_len;
+ return p;
+}
+
+
+void
+ANASTASIS_policy_destroy (struct ANASTASIS_Policy *p)
+{
+ for (unsigned int i = 0; i<p->truths_length; i++)
+ ANASTASIS_truth_free (p->truths[i]);
+ GNUNET_free (p->truths);
+ GNUNET_free (p);
+}
+
+
+/**
+ * State for a "policy store" CMD.
+ */
+struct PolicyStoreState
+{
+ /**
+ * User identifier used as entropy source for the account public key
+ */
+ struct ANASTASIS_CRYPTO_UserIdentifierP id;
+
+ /**
+ * Hash of the current upload. Used to check the server's response.
+ */
+ struct GNUNET_HashCode curr_hash;
+
+ /**
+ * Payment identifier.
+ */
+ struct ANASTASIS_PaymentSecretP payment_secret;
+
+ /**
+ * Server salt. Points into a truth object from which we got the
+ * salt.
+ */
+ struct ANASTASIS_CRYPTO_ProviderSaltP server_salt;
+
+ /**
+ * The /policy POST operation handle.
+ */
+ struct ANASTASIS_PolicyStoreOperation *pso;
+
+ /**
+ * URL of the anastasis backend.
+ */
+ char *anastasis_url;
+
+ /**
+ * Payment request returned by this provider, if any.
+ */
+ char *payment_request;
+
+ /**
+ * reference to SecretShare
+ */
+ struct ANASTASIS_SecretShare *ss;
+
+ /**
+ * Version of the policy created at the provider.
+ */
+ unsigned long long policy_version;
+
+ /**
+ * When will the policy expire at the provider.
+ */
+ struct GNUNET_TIME_Absolute policy_expiration;
+
+};
+
+/**
+* Defines a recovery document upload process (recovery document consists of multiple policies)
+*/
+struct ANASTASIS_SecretShare
+{
+ /**
+ * Closure for the Result Callback
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * Callback which gives back the result of the POST Request
+ */
+ ANASTASIS_ShareResultCallback src;
+
+ /**
+ * Closure for the Result Callback
+ */
+ void *src_cls;
+
+ /**
+ * References for the upload states and operations (size of truths passed)
+ */
+ struct PolicyStoreState *pss;
+
+ /**
+ * Closure for the Result Callback
+ */
+ unsigned int pss_length;
+};
+
+
+/**
+ * Callback to process a POST /policy request
+ *
+ * @param cls closure
+ * @param ec anastasis-specific error code
+ * @param obj the decoded response body
+ */
+static void
+policy_store_cb (void *cls,
+ const struct ANASTASIS_UploadDetails *ud)
+{
+ struct PolicyStoreState *pss = cls;
+ struct ANASTASIS_SecretShare *ss = pss->ss;
+ enum ANASTASIS_UploadStatus us;
+
+ pss->pso = NULL;
+ if (NULL == ud)
+ us = ANASTASIS_US_HTTP_ERROR;
+ else
+ us = ud->us;
+ if ( (ANASTASIS_US_SUCCESS == us) &&
+ (0 != GNUNET_memcmp (&pss->curr_hash,
+ ud->details.success.curr_backup_hash)) )
+ {
+ GNUNET_break_op (0);
+ us = ANASTASIS_US_SERVER_ERROR;
+ }
+ switch (us)
+ {
+ case ANASTASIS_US_SUCCESS:
+ pss->policy_version = ud->details.success.policy_version;
+ pss->policy_expiration = ud->details.success.policy_expiration;
+ break;
+ case ANASTASIS_US_PAYMENT_REQUIRED:
+ pss->payment_request = GNUNET_strdup (ud->details.payment.payment_request);
+ pss->payment_secret = ud->details.payment.ps;
+ break;
+ case ANASTASIS_US_HTTP_ERROR:
+ case ANASTASIS_US_CLIENT_ERROR:
+ case ANASTASIS_US_SERVER_ERROR:
+ {
+ struct ANASTASIS_ShareResult sr = {
+ .ss = ANASTASIS_SHARE_STATUS_PROVIDER_FAILED,
+ .details.provider_failure.provider_url = pss->anastasis_url,
+ .details.provider_failure.http_status = ud->http_status,
+ .details.provider_failure.ec = us,
+ };
+
+ ss->src (ss->src_cls,
+ &sr);
+ ANASTASIS_secret_share_cancel (ss);
+ return;
+ }
+ case ANASTASIS_US_CONFLICTING_TRUTH:
+ GNUNET_break (0);
+ break;
+ }
+ for (unsigned int i = 0; i<ss->pss_length; i++)
+ if (NULL != ss->pss[i].pso)
+ /* some upload is still pending, let's wait for it to finish */
+ return;
+
+ {
+ struct ANASTASIS_SharePaymentRequest spr[GNUNET_NZL (ss->pss_length)];
+ struct ANASTASIS_ProviderSuccessStatus apss[GNUNET_NZL (ss->pss_length)];
+ unsigned int off = 0;
+ unsigned int voff = 0;
+ struct ANASTASIS_ShareResult sr;
+
+ for (unsigned int i = 0; i<ss->pss_length; i++)
+ {
+ struct PolicyStoreState *pssi = &ss->pss[i];
+
+ if (NULL == pssi->payment_request)
+ {
+ apss[voff].policy_version = pssi->policy_version;
+ apss[voff].provider_url = pssi->anastasis_url;
+ apss[voff].policy_expiration = pssi->policy_expiration;
+ voff++;
+ }
+ else
+ {
+ spr[off].payment_request_url = pssi->payment_request;
+ spr[off].provider_url = pssi->anastasis_url;
+ spr[off].payment_secret = pssi->payment_secret;
+ off++;
+ }
+ }
+ if (off > 0)
+ {
+ sr.ss = ANASTASIS_SHARE_STATUS_PAYMENT_REQUIRED;
+ sr.details.payment_required.payment_requests = spr;
+ sr.details.payment_required.payment_requests_length = off;
+ }
+ else
+ {
+ sr.ss = ANASTASIS_SHARE_STATUS_SUCCESS;
+ sr.details.success.pss = apss;
+ sr.details.success.num_providers = voff;
+ }
+ ss->src (ss->src_cls,
+ &sr);
+ }
+ ANASTASIS_secret_share_cancel (ss);
+}
+
+
+struct ANASTASIS_SecretShare *
+ANASTASIS_secret_share (struct GNUNET_CURL_Context *ctx,
+ const json_t *id_data,
+ const struct ANASTASIS_ProviderDetails providers[],
+ unsigned int pss_length,
+ const struct ANASTASIS_Policy *policies[],
+ unsigned int policies_len,
+ uint32_t payment_years_requested,
+ struct GNUNET_TIME_Relative pay_timeout,
+ ANASTASIS_ShareResultCallback src,
+ void *src_cls,
+ const char *secret_name,
+ const void *core_secret,
+ size_t core_secret_size)
+{
+ struct ANASTASIS_SecretShare *ss;
+ struct ANASTASIS_CRYPTO_EncryptedMasterKeyP
+ encrypted_master_keys[GNUNET_NZL (policies_len)];
+ void *encrypted_core_secret;
+ json_t *dec_policies;
+ json_t *esc_methods;
+ size_t recovery_document_size;
+ char *recovery_document_str;
+
+ if (0 == pss_length)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ ss = GNUNET_new (struct ANASTASIS_SecretShare);
+ ss->src = src;
+ ss->src_cls = src_cls;
+ ss->pss = GNUNET_new_array (pss_length,
+ struct PolicyStoreState);
+ ss->pss_length = pss_length;
+ ss->ctx = ctx;
+
+ {
+ struct ANASTASIS_CRYPTO_PolicyKeyP policy_keys[GNUNET_NZL (policies_len)];
+
+ for (unsigned int i = 0; i < policies_len; i++)
+ policy_keys[i] = policies[i]->policy_key;
+ ANASTASIS_CRYPTO_core_secret_encrypt (policy_keys,
+ policies_len,
+ core_secret,
+ core_secret_size,
+ &encrypted_core_secret,
+ encrypted_master_keys);
+ }
+ dec_policies = json_array ();
+ GNUNET_assert (NULL != dec_policies);
+ for (unsigned int k = 0; k < policies_len; k++)
+ {
+ const struct ANASTASIS_Policy *policy = policies[k];
+ json_t *uuids = json_array ();
+
+ GNUNET_assert (NULL != uuids);
+ for (unsigned int b = 0; b < policy->truths_length; b++)
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ uuids,
+ GNUNET_JSON_from_data_auto (
+ &policy->truths[b]->uuid)));
+ if (0 !=
+ json_array_append_new (
+ dec_policies,
+ json_pack ("{s:o, s:o, s:o}",
+ "master_key",
+ GNUNET_JSON_from_data_auto (
+ &encrypted_master_keys[k]),
+ "uuids",
+ uuids,
+ "salt",
+ GNUNET_JSON_from_data_auto (&policy->salt))))
+ {
+ GNUNET_break (0);
+ json_decref (dec_policies);
+ ANASTASIS_secret_share_cancel (ss);
+ return NULL;
+ }
+ }
+
+ esc_methods = json_array ();
+ for (unsigned int k = 0; k < policies_len; k++)
+ {
+ const struct ANASTASIS_Policy *policy = policies[k];
+
+ for (unsigned int l = 0; l < policy->truths_length; l++)
+ {
+ const struct ANASTASIS_Truth *pt = policy->truths[l];
+ bool unique = true;
+
+ /* Only append each truth once */
+ for (unsigned int k2 = 0; k2 < k; k2++)
+ {
+ const struct ANASTASIS_Policy *p2 = policies[k2];
+ for (unsigned int l2 = 0; l2 < p2->truths_length; l2++)
+ if (0 ==
+ GNUNET_memcmp (&pt->uuid,
+ &p2->truths[l2]->uuid))
+ {
+ unique = false;
+ break;
+ }
+ if (! unique)
+ break;
+ }
+ if (! unique)
+ continue;
+
+ if (0 !=
+ json_array_append_new (
+ esc_methods,
+ json_pack ("{s:o," /* truth uuid */
+ " s:s," /* provider url */
+ " s:s," /* instructions */
+ " s:o," /* truth key */
+ " s:o," /* truth salt */
+ " s:o," /* provider salt */
+ " s:s}", /* escrow method */
+ "uuid",
+ GNUNET_JSON_from_data_auto (
+ &pt->uuid),
+ "url",
+ pt->url,
+ "instructions",
+ pt->instructions,
+ "truth_key", GNUNET_JSON_from_data_auto (
+ &pt->truth_key),
+ "salt", GNUNET_JSON_from_data_auto (
+ &pt->salt),
+ "provider_salt", GNUNET_JSON_from_data_auto (
+ &pt->provider_salt),
+ "escrow_type",
+ pt->type)))
+ {
+ GNUNET_break (0);
+ json_decref (esc_methods);
+ json_decref (dec_policies);
+ ANASTASIS_secret_share_cancel (ss);
+ return NULL;
+ }
+ }
+ }
+
+ {
+ json_t *recovery_document;
+ size_t rd_size;
+ char *rd_str;
+ Bytef *cbuf;
+ uLongf cbuf_size;
+ int ret;
+ uint32_t be_size;
+
+ recovery_document = json_pack (
+ "{s:s?, s:o, s:o, s:o}",
+ "secret_name", secret_name,
+ "policies", dec_policies,
+ "escrow_methods", esc_methods,
+ "encrypted_core_secret", GNUNET_JSON_from_data (encrypted_core_secret,
+ core_secret_size));
+ GNUNET_assert (NULL != recovery_document);
+ GNUNET_free (encrypted_core_secret);
+
+ rd_str = json_dumps (recovery_document,
+ JSON_COMPACT | JSON_SORT_KEYS);
+ GNUNET_assert (NULL != rd_str);
+ json_decref (recovery_document);
+ rd_size = strlen (rd_str);
+ cbuf_size = compressBound (rd_size);
+ be_size = htonl ((uint32_t) rd_size);
+ cbuf = GNUNET_malloc (cbuf_size + sizeof (uint32_t));
+ memcpy (cbuf,
+ &be_size,
+ sizeof (uint32_t));
+ ret = compress (cbuf + sizeof (uint32_t),
+ &cbuf_size,
+ (const Bytef *) rd_str,
+ rd_size);
+ if (Z_OK != ret)
+ {
+ /* compression failed!? */
+ GNUNET_break (0);
+ free (rd_str);
+ GNUNET_free (cbuf);
+ ANASTASIS_secret_share_cancel (ss);
+ return NULL;
+ }
+ free (rd_str);
+ recovery_document_size = (size_t) (cbuf_size + sizeof (uint32_t));
+ recovery_document_str = (char *) cbuf;
+ }
+
+ for (unsigned int l = 0; l < ss->pss_length; l++)
+ {
+ struct PolicyStoreState *pss = &ss->pss[l];
+ void *recovery_data;
+ size_t recovery_data_size;
+ struct ANASTASIS_CRYPTO_AccountPrivateKeyP anastasis_priv;
+
+ pss->ss = ss;
+ pss->anastasis_url = GNUNET_strdup (providers[l].provider_url);
+ pss->server_salt = providers[l].provider_salt;
+ pss->payment_secret = providers[l].payment_secret;
+ ANASTASIS_CRYPTO_user_identifier_derive (id_data,
+ &pss->server_salt,
+ &pss->id);
+ ANASTASIS_CRYPTO_account_private_key_derive (&pss->id,
+ &anastasis_priv);
+ ANASTASIS_CRYPTO_recovery_document_encrypt (&pss->id,
+ recovery_document_str,
+ recovery_document_size,
+ &recovery_data,
+ &recovery_data_size);
+ GNUNET_CRYPTO_hash (recovery_data,
+ recovery_data_size,
+ &pss->curr_hash);
+ pss->pso = ANASTASIS_policy_store (
+ ss->ctx,
+ pss->anastasis_url,
+ &anastasis_priv,
+ recovery_data,
+ recovery_data_size,
+ payment_years_requested,
+ (! GNUNET_is_zero (&pss->payment_secret))
+ ? &pss->payment_secret
+ : NULL,
+ pay_timeout,
+ &policy_store_cb,
+ pss);
+ GNUNET_free (recovery_data);
+ if (NULL == pss->pso)
+ {
+ GNUNET_break (0);
+ ANASTASIS_secret_share_cancel (ss);
+ GNUNET_free (recovery_document_str);
+ return NULL;
+ }
+ }
+ GNUNET_free (recovery_document_str);
+ return ss;
+}
+
+
+void
+ANASTASIS_secret_share_cancel (struct ANASTASIS_SecretShare *ss)
+{
+ for (unsigned int i = 0; i<ss->pss_length; i++)
+ {
+ struct PolicyStoreState *pssi = &ss->pss[i];
+
+ if (NULL != pssi->pso)
+ {
+ ANASTASIS_policy_store_cancel (pssi->pso);
+ pssi->pso = NULL;
+ }
+ GNUNET_free (pssi->anastasis_url);
+ GNUNET_free (pssi->payment_request);
+ }
+ GNUNET_free (ss->pss);
+ GNUNET_free (ss);
+}
diff --git a/src/lib/anastasis_recovery.c b/src/lib/anastasis_recovery.c
new file mode 100644
index 0000000..5b0726f
--- /dev/null
+++ b/src/lib/anastasis_recovery.c
@@ -0,0 +1,1425 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 2021 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @brief anastasis client api
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ */
+#include "platform.h"
+#include "anastasis.h"
+#include <taler/taler_json_lib.h>
+#include <gnunet/gnunet_util_lib.h>
+#include <taler/taler_merchant_service.h>
+#include <zlib.h>
+
+
+/**
+ * Challenge struct contains the uuid and public key's needed for the
+ * recovery process and a reference to ANASTASIS_Recovery.
+ */
+struct ANASTASIS_Challenge
+{
+
+ /**
+ * Information exported to clients about this challenge.
+ */
+ struct ANASTASIS_ChallengeDetails ci;
+
+ /**
+ * Key used to encrypt the truth passed to the server
+ */
+ struct ANASTASIS_CRYPTO_TruthKeyP truth_key;
+
+ /**
+ * Salt; used to derive hash from security question answers.
+ */
+ struct ANASTASIS_CRYPTO_QuestionSaltP salt;
+
+ /**
+ * Provider salt; used to derive our key material from our identity
+ * key.
+ */
+ struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt;
+
+ /**
+ * Decrypted key share for this challenge. Set once the
+ * challenge was @e ri.solved.
+ */
+ struct ANASTASIS_CRYPTO_KeyShareP key_share;
+
+ /**
+ * Callback which gives back the instructions and a status code of
+ * the request to the user when answering a challenge was initiated.
+ */
+ ANASTASIS_AnswerFeedback af;
+
+ /**
+ * Closure for the challenge callback
+ */
+ void *af_cls;
+
+ /**
+ * Defines the base URL of the Anastasis provider used for the challenge.
+ */
+ char *url;
+
+ /**
+ * What is the type of this challenge (E-Mail, Security Question, SMS...)
+ */
+ char *type;
+
+ /**
+ * Instructions for solving the challenge (generic, set client-side
+ * when challenge was established).
+ */
+ char *instructions;
+
+ /**
+ * Answer to the security question, if @a type is "question". Otherwise NULL.
+ */
+ char *answer;
+
+ /**
+ * Reference to the recovery process which is ongoing
+ */
+ struct ANASTASIS_Recovery *recovery;
+
+ /**
+ * keyshare lookup operation
+ */
+ struct ANASTASIS_KeyShareLookupOperation *kslo;
+
+};
+
+
+/**
+ * Defines a decryption policy with multiple escrow methods
+ */
+struct DecryptionPolicy
+{
+
+ /**
+ * Publicly visible details about a decryption policy.
+ */
+ struct ANASTASIS_DecryptionPolicy pub_details;
+
+ /**
+ * Encrypted masterkey (encrypted with the policy key).
+ */
+ struct ANASTASIS_CRYPTO_EncryptedMasterKeyP emk;
+
+ /**
+ * Salt used to decrypt master key.
+ */
+ struct ANASTASIS_CRYPTO_MasterSaltP salt;
+
+};
+
+
+/**
+ * stores provider URLs, identity key material, decrypted recovery document (internally!)
+ */
+struct ANASTASIS_Recovery
+{
+
+ /**
+ * Identity key material used for the derivation of keys
+ */
+ struct ANASTASIS_CRYPTO_UserIdentifierP id;
+
+ /**
+ * Recovery information which is given to the user
+ */
+ struct ANASTASIS_RecoveryInformation ri;
+
+ /**
+ * Internal of @e ri.dps_len policies that would allow recovery of the core secret.
+ */
+ struct DecryptionPolicy *dps;
+
+ /**
+ * Array of @e ri.cs_len challenges to be solved (for any of the policies).
+ */
+ struct ANASTASIS_Challenge *cs;
+
+ /**
+ * Identity data to user id from.
+ */
+ json_t *id_data;
+
+ /**
+ * Callback to send back a recovery document with the policies and the version
+ */
+ ANASTASIS_PolicyCallback pc;
+
+ /**
+ * closure for the Policy callback
+ */
+ void *pc_cls;
+
+ /**
+ * Callback to send back the core secret which was saved by
+ * anastasis, after all challenges are completed
+ */
+ ANASTASIS_CoreSecretCallback csc;
+
+ /**
+ * Closure for the core secret callback
+ */
+ void *csc_cls;
+
+ /**
+ * Curl context
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * Reference to the policy lookup operation which is executed
+ */
+ struct ANASTASIS_PolicyLookupOperation *plo;
+
+ /**
+ * Array of challenges that have been solved.
+ * Valid entries up to @e solved_challenge_pos.
+ * Length matches the total number of challenges in @e ri.
+ */
+ struct ANASTASIS_Challenge **solved_challenges;
+
+ /**
+ * Our provider URL.
+ */
+ char *provider_url;
+
+ /**
+ * Name of the secret, can be NULL.
+ */
+ char *secret_name;
+
+ /**
+ * Task to run @e pc asynchronously.
+ */
+ struct GNUNET_SCHEDULER_Task *do_async;
+
+ /**
+ * Retrieved encrypted core secret from policy
+ */
+ void *enc_core_secret;
+
+ /**
+ * Size of the @e enc_core_secret
+ */
+ size_t enc_core_secret_size;
+
+ /**
+ * Current offset in the @e solved_challenges array.
+ */
+ unsigned int solved_challenge_pos;
+
+};
+
+
+/**
+ * Function called with the results of a #ANASTASIS_keyshare_lookup().
+ *
+ * @param cls closure
+ * @param http_status HTTP status of the request
+ * @param ud details about the lookup operation
+ */
+static void
+keyshare_lookup_cb (void *cls,
+ const struct ANASTASIS_KeyShareDownloadDetails *dd)
+{
+ struct ANASTASIS_Challenge *c = cls;
+ struct ANASTASIS_Recovery *recovery = c->recovery;
+ struct ANASTASIS_CRYPTO_UserIdentifierP id;
+ struct DecryptionPolicy *rdps;
+
+ c->kslo = NULL;
+ switch (dd->status)
+ {
+ case ANASTASIS_KSD_SUCCESS:
+ break;
+ case ANASTASIS_KSD_PAYMENT_REQUIRED:
+ {
+ struct ANASTASIS_ChallengeStartResponse csr = {
+ .cs = ANASTASIS_CHALLENGE_STATUS_PAYMENT_REQUIRED,
+ .challenge = c,
+ .details.payment_required.taler_pay_uri
+ = dd->details.payment_required.taler_pay_uri,
+ .details.payment_required.payment_secret
+ = dd->details.payment_required.payment_secret
+ };
+
+ c->af (c->af_cls,
+ &csr);
+ return;
+ }
+ case ANASTASIS_KSD_INVALID_ANSWER:
+ {
+ struct ANASTASIS_ChallengeStartResponse csr = {
+ .cs = ANASTASIS_CHALLENGE_STATUS_INSTRUCTIONS,
+ .challenge = c,
+ .details.open_challenge.body
+ = dd->details.open_challenge.body,
+ .details.open_challenge.content_type
+ = dd->details.open_challenge.content_type,
+ .details.open_challenge.body_size
+ = dd->details.open_challenge.body_size,
+ .details.open_challenge.http_status
+ = dd->details.open_challenge.http_status
+ };
+
+ c->af (c->af_cls,
+ &csr);
+ return;
+ }
+ case ANASTASIS_KSD_REDIRECT_FOR_AUTHENTICATION:
+ {
+ struct ANASTASIS_ChallengeStartResponse csr = {
+ .cs = ANASTASIS_CHALLENGE_STATUS_REDIRECT_FOR_AUTHENTICATION,
+ .challenge = c,
+ .details.redirect_url
+ = dd->details.redirect_url
+ };
+
+ c->af (c->af_cls,
+ &csr);
+ return;
+ }
+ case ANASTASIS_KSD_TRUTH_UNKNOWN:
+ {
+ struct ANASTASIS_ChallengeStartResponse csr = {
+ .cs = ANASTASIS_CHALLENGE_STATUS_TRUTH_UNKNOWN,
+ .challenge = c
+ };
+
+ c->af (c->af_cls,
+ &csr);
+ return;
+ }
+ case ANASTASIS_KSD_RATE_LIMIT_EXCEEDED:
+ {
+ struct ANASTASIS_ChallengeStartResponse csr = {
+ .cs = ANASTASIS_CHALLENGE_STATUS_RATE_LIMIT_EXCEEDED,
+ .challenge = c
+ };
+
+ c->af (c->af_cls,
+ &csr);
+ return;
+ }
+ case ANASTASIS_KSD_SERVER_ERROR:
+ case ANASTASIS_KSD_CLIENT_FAILURE:
+ {
+ struct ANASTASIS_ChallengeStartResponse csr = {
+ .cs = ANASTASIS_CHALLENGE_STATUS_SERVER_FAILURE,
+ .challenge = c,
+ .details.server_failure.ec
+ = dd->details.server_failure.ec,
+ .details.server_failure.http_status
+ = dd->details.server_failure.http_status
+ };
+
+ c->af (c->af_cls,
+ &csr);
+ return;
+ }
+ }
+
+ GNUNET_assert (NULL != dd);
+ ANASTASIS_CRYPTO_user_identifier_derive (recovery->id_data,
+ &c->provider_salt,
+ &id);
+ ANASTASIS_CRYPTO_keyshare_decrypt (&dd->details.eks,
+ &id,
+ c->answer,
+ &c->key_share);
+ recovery->solved_challenges[recovery->solved_challenge_pos++] = c;
+
+ {
+ struct ANASTASIS_ChallengeStartResponse csr = {
+ .cs = ANASTASIS_CHALLENGE_STATUS_SOLVED,
+ .challenge = c
+ };
+
+ c->ci.solved = true;
+ c->af (c->af_cls,
+ &csr);
+ }
+
+
+ /* Check if there is a policy for which all challenges have
+ been satisfied, if so, store it in 'rdps'. */
+ rdps = NULL;
+ for (unsigned int i = 0; i < recovery->ri.dps_len; i++)
+ {
+ struct DecryptionPolicy *dps = &recovery->dps[i];
+ bool missing = false;
+
+ for (unsigned int j = 0; j < dps->pub_details.challenges_length; j++)
+ {
+ bool found = false;
+
+ for (unsigned int k = 0; k < recovery->solved_challenge_pos; k++)
+ {
+ if (dps->pub_details.challenges[j] == recovery->solved_challenges[k])
+ {
+ found = true;
+ break;
+ }
+ }
+ if (! found)
+ {
+ missing = true;
+ break;
+ }
+ }
+ if (! missing)
+ {
+ rdps = dps;
+ break;
+ }
+ }
+ if (NULL == rdps)
+ return;
+
+ {
+ void *core_secret;
+ size_t core_secret_size;
+ struct ANASTASIS_CRYPTO_KeyShareP
+ key_shares[rdps->pub_details.challenges_length];
+ struct ANASTASIS_CRYPTO_PolicyKeyP policy_key;
+
+ for (unsigned int l = 0; l < rdps->pub_details.challenges_length; l++)
+ for (unsigned int m = 0; m < recovery->solved_challenge_pos; m++)
+ if (rdps->pub_details.challenges[l] == recovery->solved_challenges[m])
+ key_shares[l] = recovery->solved_challenges[m]->key_share;
+ ANASTASIS_CRYPTO_policy_key_derive (key_shares,
+ rdps->pub_details.challenges_length,
+ &rdps->salt,
+ &policy_key);
+ ANASTASIS_CRYPTO_core_secret_recover (&rdps->emk,
+ &policy_key,
+ recovery->enc_core_secret,
+ recovery->enc_core_secret_size,
+ &core_secret,
+ &core_secret_size);
+ recovery->csc (recovery->csc_cls,
+ ANASTASIS_RS_SUCCESS,
+ core_secret,
+ core_secret_size);
+ GNUNET_free (core_secret);
+ ANASTASIS_recovery_abort (recovery);
+ }
+}
+
+
+const struct ANASTASIS_ChallengeDetails *
+ANASTASIS_challenge_get_details (struct ANASTASIS_Challenge *challenge)
+{
+ return &challenge->ci;
+}
+
+
+int
+ANASTASIS_challenge_start (struct ANASTASIS_Challenge *c,
+ const struct ANASTASIS_PaymentSecretP *psp,
+ struct GNUNET_TIME_Relative timeout,
+ const struct GNUNET_HashCode *hashed_answer,
+ ANASTASIS_AnswerFeedback af,
+ void *af_cls)
+{
+ if (c->ci.solved)
+ {
+ GNUNET_break (0);
+ return GNUNET_NO; /* already solved */
+ }
+ if (NULL != c->kslo)
+ {
+ GNUNET_break (0);
+ return GNUNET_NO; /* already solving */
+ }
+ c->af = af;
+ c->af_cls = af_cls;
+ c->kslo = ANASTASIS_keyshare_lookup (c->recovery->ctx,
+ c->url,
+ &c->ci.uuid,
+ &c->truth_key,
+ psp,
+ timeout,
+ hashed_answer,
+ &keyshare_lookup_cb,
+ c);
+ if (NULL == c->kslo)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+int
+ANASTASIS_challenge_answer (
+ struct ANASTASIS_Challenge *c,
+ const struct ANASTASIS_PaymentSecretP *psp,
+ struct GNUNET_TIME_Relative timeout,
+ const char *answer_str,
+ ANASTASIS_AnswerFeedback af,
+ void *af_cls)
+{
+ struct GNUNET_HashCode hashed_answer;
+
+ GNUNET_free (c->answer);
+ c->answer = GNUNET_strdup (answer_str);
+ ANASTASIS_CRYPTO_secure_answer_hash (answer_str,
+ &c->ci.uuid,
+ &c->salt,
+ &hashed_answer);
+ return ANASTASIS_challenge_start (c,
+ psp,
+ timeout,
+ &hashed_answer,
+ af,
+ af_cls);
+}
+
+
+int
+ANASTASIS_challenge_answer2 (struct ANASTASIS_Challenge *c,
+ const struct ANASTASIS_PaymentSecretP *psp,
+ struct GNUNET_TIME_Relative timeout,
+ uint64_t answer,
+ ANASTASIS_AnswerFeedback af,
+ void *af_cls)
+{
+ struct GNUNET_HashCode answer_s;
+
+ ANASTASIS_hash_answer (answer,
+ &answer_s);
+ return ANASTASIS_challenge_start (c,
+ psp,
+ timeout,
+ &answer_s,
+ af,
+ af_cls);
+}
+
+
+void
+ANASTASIS_challenge_abort (struct ANASTASIS_Challenge *c)
+{
+ if (NULL == c->kslo)
+ {
+ GNUNET_break (0);
+ return;
+ }
+ ANASTASIS_keyshare_lookup_cancel (c->kslo);
+ c->kslo = NULL;
+ c->af = NULL;
+ c->af_cls = NULL;
+}
+
+
+/**
+ * Function called with the results of a ANASTASIS_policy_lookup
+ *
+ * @param cls closure
+ * @param http_status HTTP status of the request
+ * @param ud details about the lookup operation
+ */
+static void
+policy_lookup_cb (void *cls,
+ unsigned int http_status,
+ const struct ANASTASIS_DownloadDetails *dd)
+{
+ struct ANASTASIS_Recovery *r = cls;
+ void *plaintext;
+ size_t size_plaintext;
+ json_error_t json_error;
+ json_t *dec_policies;
+ json_t *esc_methods;
+
+ r->plo = NULL;
+ switch (http_status)
+ {
+ case MHD_HTTP_OK:
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ r->csc (r->csc_cls,
+ ANASTASIS_RS_POLICY_UNKNOWN,
+ NULL,
+ 0);
+ ANASTASIS_recovery_abort (r);
+ return;
+ case MHD_HTTP_NO_CONTENT:
+ /* Account known, policy expired */
+ r->csc (r->csc_cls,
+ ANASTASIS_RS_POLICY_GONE,
+ NULL,
+ 0);
+ ANASTASIS_recovery_abort (r);
+ return;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Bad server... */
+ r->csc (r->csc_cls,
+ ANASTASIS_RS_POLICY_SERVER_ERROR,
+ NULL,
+ 0);
+ ANASTASIS_recovery_abort (r);
+ return;
+ case MHD_HTTP_NOT_MODIFIED:
+ /* Should not be possible, we do not cache, fall-through! */
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u in %s:%u\n",
+ http_status,
+ __FILE__,
+ __LINE__);
+ r->csc (r->csc_cls,
+ ANASTASIS_RS_POLICY_DOWNLOAD_FAILED,
+ NULL,
+ 0);
+ ANASTASIS_recovery_abort (r);
+ return;
+ }
+ if (NULL == dd->policy)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "No recovery data available");
+ r->csc (r->csc_cls,
+ ANASTASIS_RS_POLICY_DOWNLOAD_NO_POLICY,
+ NULL,
+ 0);
+ ANASTASIS_recovery_abort (r);
+ return;
+ }
+ ANASTASIS_CRYPTO_recovery_document_decrypt (&r->id,
+ dd->policy,
+ dd->policy_size,
+ &plaintext,
+ &size_plaintext);
+ if (size_plaintext < sizeof (uint32_t))
+ {
+ GNUNET_break_op (0);
+ r->csc (r->csc_cls,
+ ANASTASIS_RS_POLICY_DOWNLOAD_INVALID_COMPRESSION,
+ NULL,
+ 0);
+ ANASTASIS_recovery_abort (r);
+ GNUNET_free (plaintext);
+ return;
+ }
+ {
+ json_t *recovery_document;
+ uint32_t be_size;
+ uLongf pt_size;
+ char *pt;
+
+ memcpy (&be_size,
+ plaintext,
+ sizeof (uint32_t));
+ pt_size = ntohl (be_size);
+ pt = GNUNET_malloc_large (pt_size);
+ if (NULL == pt)
+ {
+ GNUNET_break_op (0);
+ r->csc (r->csc_cls,
+ ANASTASIS_RS_POLICY_DOWNLOAD_TOO_BIG,
+ NULL,
+ 0);
+ ANASTASIS_recovery_abort (r);
+ GNUNET_free (plaintext);
+ return;
+ }
+ if (Z_OK !=
+ uncompress ((Bytef *) pt,
+ &pt_size,
+ (const Bytef *) plaintext + sizeof (uint32_t),
+ size_plaintext - sizeof (uint32_t)))
+ {
+ GNUNET_break_op (0);
+ r->csc (r->csc_cls,
+ ANASTASIS_RS_POLICY_DOWNLOAD_INVALID_COMPRESSION,
+ NULL,
+ 0);
+ GNUNET_free (plaintext);
+ GNUNET_free (pt);
+ ANASTASIS_recovery_abort (r);
+ return;
+ }
+ GNUNET_free (plaintext);
+ recovery_document = json_loadb ((char *) pt,
+ pt_size,
+ JSON_DECODE_ANY,
+ &json_error);
+ GNUNET_free (pt);
+ if (NULL == recovery_document)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to read JSON input: %s at %d:%s (offset: %d)\n",
+ json_error.text,
+ json_error.line,
+ json_error.source,
+ json_error.position);
+ GNUNET_break_op (0);
+ r->csc (r->csc_cls,
+ ANASTASIS_RS_POLICY_DOWNLOAD_NO_JSON,
+ NULL,
+ 0);
+ ANASTASIS_recovery_abort (r);
+ return;
+ }
+
+ {
+ const char *secret_name = NULL;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_json ("policies",
+ &dec_policies),
+ GNUNET_JSON_spec_json ("escrow_methods",
+ &esc_methods),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("secret_name",
+ &secret_name)),
+ GNUNET_JSON_spec_varsize ("encrypted_core_secret",
+ &r->enc_core_secret,
+ &r->enc_core_secret_size),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (recovery_document,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ json_dumpf (recovery_document,
+ stderr,
+ 0);
+ json_decref (recovery_document);
+ r->csc (r->csc_cls,
+ ANASTASIS_RS_POLICY_MALFORMED_JSON,
+ NULL,
+ 0);
+ ANASTASIS_recovery_abort (r);
+ return;
+ }
+ if (NULL != secret_name)
+ {
+ GNUNET_break (NULL == r->secret_name);
+ r->secret_name = GNUNET_strdup (secret_name);
+ r->ri.secret_name = r->secret_name;
+ }
+ }
+ json_decref (recovery_document);
+ }
+
+ r->ri.version = dd->version;
+ r->ri.cs_len = json_array_size (esc_methods);
+ r->ri.dps_len = json_array_size (dec_policies);
+ r->ri.dps = GNUNET_new_array (r->ri.dps_len,
+ struct ANASTASIS_DecryptionPolicy *);
+ r->dps = GNUNET_new_array (r->ri.dps_len,
+ struct DecryptionPolicy);
+ r->solved_challenges = GNUNET_new_array (r->ri.cs_len,
+ struct ANASTASIS_Challenge *);
+ r->ri.cs = GNUNET_new_array (r->ri.cs_len,
+ struct ANASTASIS_Challenge *);
+ r->cs = GNUNET_new_array (r->ri.cs_len,
+ struct ANASTASIS_Challenge);
+ for (unsigned int i = 0; i < r->ri.cs_len; i++)
+ {
+ struct ANASTASIS_Challenge *cs = &r->cs[i];
+ const char *instructions;
+ const char *url;
+ const char *escrow_type;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("uuid",
+ &cs->ci.uuid),
+ GNUNET_JSON_spec_string ("url",
+ &url),
+ GNUNET_JSON_spec_string ("instructions",
+ &instructions),
+ GNUNET_JSON_spec_fixed_auto ("truth_key",
+ &cs->truth_key),
+ GNUNET_JSON_spec_fixed_auto ("salt",
+ &cs->salt),
+ GNUNET_JSON_spec_fixed_auto ("provider_salt",
+ &cs->provider_salt),
+ GNUNET_JSON_spec_string ("escrow_type",
+ &escrow_type),
+ GNUNET_JSON_spec_end ()
+ };
+
+ r->ri.cs[i] = cs;
+ cs->recovery = r;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json_array_get (esc_methods,
+ i),
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ json_decref (esc_methods);
+ json_decref (dec_policies);
+ r->csc (r->csc_cls,
+ ANASTASIS_RS_POLICY_MALFORMED_JSON,
+ NULL,
+ 0);
+ ANASTASIS_recovery_abort (r);
+ return;
+ }
+ cs->url = GNUNET_strdup (url);
+ cs->type = GNUNET_strdup (escrow_type);
+ cs->ci.type = cs->type;
+ cs->ci.provider_url = cs->url;
+ cs->instructions = GNUNET_strdup (instructions);
+ cs->ci.instructions = cs->instructions;
+ }
+ json_decref (esc_methods);
+
+ for (unsigned int j = 0; j < r->ri.dps_len; j++)
+ {
+ struct DecryptionPolicy *dp = &r->dps[j];
+ json_t *uuids = NULL;
+ json_t *uuid;
+ size_t n_index;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("master_key",
+ &dp->emk),
+ GNUNET_JSON_spec_fixed_auto ("salt",
+ &dp->salt),
+ GNUNET_JSON_spec_json ("uuids",
+ &uuids),
+ GNUNET_JSON_spec_end ()
+ };
+
+ r->ri.dps[j] = &r->dps[j].pub_details;
+ if ( (GNUNET_OK !=
+ GNUNET_JSON_parse (json_array_get (dec_policies,
+ j),
+ spec,
+ NULL, NULL)) ||
+ (! json_is_array (uuids)) )
+ {
+ GNUNET_break_op (0);
+ json_decref (uuids);
+ json_decref (dec_policies);
+ r->csc (r->csc_cls,
+ ANASTASIS_RS_POLICY_MALFORMED_JSON,
+ NULL,
+ 0);
+ ANASTASIS_recovery_abort (r);
+ return;
+ }
+
+ dp->pub_details.challenges_length = json_array_size (uuids);
+ dp->pub_details.challenges
+ = GNUNET_new_array (dp->pub_details.challenges_length,
+ struct ANASTASIS_Challenge *);
+ json_array_foreach (uuids, n_index, uuid)
+ {
+ const char *uuid_str = json_string_value (uuid);
+ struct ANASTASIS_CRYPTO_TruthUUIDP uuid;
+ bool found = false;
+
+ if ( (NULL == uuid_str) ||
+ (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (
+ uuid_str,
+ strlen (uuid_str),
+ &uuid,
+ sizeof (uuid))) )
+ {
+ GNUNET_break_op (0);
+ json_decref (dec_policies);
+ json_decref (uuids);
+ r->csc (r->csc_cls,
+ ANASTASIS_RS_POLICY_MALFORMED_JSON,
+ NULL,
+ 0);
+ ANASTASIS_recovery_abort (r);
+ return;
+ }
+ for (unsigned int i = 0; i<r->ri.cs_len; i++)
+ {
+ if (0 !=
+ GNUNET_memcmp (&uuid,
+ &r->cs[i].ci.uuid))
+ continue;
+ found = true;
+ dp->pub_details.challenges[n_index] = &r->cs[i];
+ break;
+ }
+ if (! found)
+ {
+ GNUNET_break_op (0);
+ json_decref (dec_policies);
+ json_decref (uuids);
+ r->csc (r->csc_cls,
+ ANASTASIS_RS_POLICY_MALFORMED_JSON,
+ NULL,
+ 0);
+ ANASTASIS_recovery_abort (r);
+ return;
+ }
+ }
+ json_decref (uuids);
+ }
+ json_decref (dec_policies);
+ r->pc (r->pc_cls,
+ &r->ri);
+}
+
+
+struct ANASTASIS_Recovery *
+ANASTASIS_recovery_begin (
+ struct GNUNET_CURL_Context *ctx,
+ const json_t *id_data,
+ unsigned int version,
+ const char *anastasis_provider_url,
+ const struct ANASTASIS_CRYPTO_ProviderSaltP *provider_salt,
+ ANASTASIS_PolicyCallback pc,
+ void *pc_cls,
+ ANASTASIS_CoreSecretCallback csc,
+ void *csc_cls)
+{
+ struct ANASTASIS_Recovery *r;
+ struct ANASTASIS_CRYPTO_AccountPublicKeyP pub_key;
+
+ r = GNUNET_new (struct ANASTASIS_Recovery);
+ r->csc = csc;
+ r->csc_cls = csc_cls;
+ r->pc = pc;
+ r->pc_cls = pc_cls;
+ r->ctx = ctx;
+ r->id_data = json_incref ((json_t *) id_data);
+ r->provider_url = GNUNET_strdup (anastasis_provider_url);
+ ANASTASIS_CRYPTO_user_identifier_derive (id_data,
+ provider_salt,
+ &r->id);
+ ANASTASIS_CRYPTO_account_public_key_derive (&r->id,
+ &pub_key);
+ r->ri.version = version;
+ if (0 != version)
+ {
+ r->plo = ANASTASIS_policy_lookup_version (r->ctx,
+ anastasis_provider_url,
+ &pub_key,
+ &policy_lookup_cb,
+ r,
+ version);
+ }
+ else
+ {
+ r->plo = ANASTASIS_policy_lookup (r->ctx,
+ anastasis_provider_url,
+ &pub_key,
+ &policy_lookup_cb,
+ r);
+ }
+ if (NULL == r->plo)
+ {
+ GNUNET_break (0);
+ ANASTASIS_recovery_abort (r);
+ return NULL;
+ }
+ return r;
+}
+
+
+json_t *
+ANASTASIS_recovery_serialize (const struct ANASTASIS_Recovery *r)
+{
+ json_t *dps_arr;
+ json_t *cs_arr;
+
+ dps_arr = json_array ();
+ GNUNET_assert (NULL != dps_arr);
+ for (unsigned int i = 0; i<r->ri.dps_len; i++)
+ {
+ const struct DecryptionPolicy *dp = &r->dps[i];
+ json_t *c_arr;
+ json_t *dps;
+
+ c_arr = json_array ();
+ GNUNET_assert (NULL != c_arr);
+ for (unsigned int j = 0; j < dp->pub_details.challenges_length; j++)
+ {
+ const struct ANASTASIS_Challenge *c = dp->pub_details.challenges[j];
+ json_t *cs;
+
+ cs = json_pack ("{s:o}",
+ "uuid",
+ GNUNET_JSON_from_data_auto (&c->ci.uuid));
+ GNUNET_assert (NULL != cs);
+ GNUNET_assert (0 ==
+ json_array_append_new (c_arr,
+ cs));
+ }
+ dps = json_pack ("{s:o, s:o, s:o}",
+ "emk",
+ GNUNET_JSON_from_data_auto (&dp->emk),
+ "salt",
+ GNUNET_JSON_from_data_auto (&dp->salt),
+ "challenges",
+ c_arr);
+ GNUNET_assert (NULL != dps);
+ GNUNET_assert (0 ==
+ json_array_append_new (dps_arr,
+ dps));
+ }
+ cs_arr = json_array ();
+ GNUNET_assert (NULL != cs_arr);
+ for (unsigned int i = 0; i<r->ri.cs_len; i++)
+ {
+ const struct ANASTASIS_Challenge *c = &r->cs[i];
+ json_t *cs;
+
+ cs = json_pack ("{s:o,s:o,s:o,s:o,s:o?,"
+ " s:s,s:s,s:s,s:b}",
+ "uuid",
+ GNUNET_JSON_from_data_auto (&c->ci.uuid),
+ "truth_key",
+ GNUNET_JSON_from_data_auto (&c->truth_key),
+ "salt",
+ GNUNET_JSON_from_data_auto (&c->salt),
+ "provider_salt",
+ GNUNET_JSON_from_data_auto (&c->provider_salt),
+ "key_share",
+ c->ci.solved
+ ? GNUNET_JSON_from_data_auto (&c->key_share)
+ : NULL,
+ "url",
+ c->url,
+ "type",
+ c->type,
+ "instructions",
+ c->instructions,
+ "solved",
+ c->ci.solved);
+ GNUNET_assert (NULL != cs);
+ GNUNET_assert (0 ==
+ json_array_append_new (cs_arr,
+ cs));
+ }
+
+ return json_pack ("{s:o, s:o, s:o, s:I, s:O, "
+ " s:s, s:s?, s:o}",
+ "id",
+ GNUNET_JSON_from_data_auto (&r->id),
+ "dps",
+ dps_arr,
+ "cs",
+ cs_arr,
+ "version",
+ (json_int_t) r->ri.version,
+ "id_data",
+ r->id_data,
+ "provider_url",
+ r->provider_url,
+ "secret_name",
+ r->secret_name,
+ "core_secret",
+ GNUNET_JSON_from_data (r->enc_core_secret,
+ r->enc_core_secret_size));
+}
+
+
+/**
+ * Parse the @a cs_array and update @a r accordingly
+ *
+ * @param[in,out] r recovery information to update
+ * @param cs_arr serialized data to parse
+ * @return #GNUNET_OK on success
+ */
+static int
+parse_cs_array (struct ANASTASIS_Recovery *r,
+ json_t *cs_arr)
+{
+ json_t *cs;
+ unsigned int n_index;
+
+ if (! json_is_array (cs_arr))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ r->ri.cs_len = json_array_size (cs_arr);
+ r->solved_challenges = GNUNET_new_array (r->ri.cs_len,
+ struct ANASTASIS_Challenge *);
+ r->ri.cs = GNUNET_new_array (r->ri.cs_len,
+ struct ANASTASIS_Challenge *);
+ r->cs = GNUNET_new_array (r->ri.cs_len,
+ struct ANASTASIS_Challenge);
+ json_array_foreach (cs_arr, n_index, cs)
+ {
+ struct ANASTASIS_Challenge *c = &r->cs[n_index];
+ const char *instructions;
+ const char *url;
+ const char *escrow_type;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("uuid",
+ &c->ci.uuid),
+ GNUNET_JSON_spec_string ("url",
+ &url),
+ GNUNET_JSON_spec_string ("instructions",
+ &instructions),
+ GNUNET_JSON_spec_fixed_auto ("truth_key",
+ &c->truth_key),
+ GNUNET_JSON_spec_fixed_auto ("salt",
+ &c->salt),
+ GNUNET_JSON_spec_fixed_auto ("provider_salt",
+ &c->provider_salt),
+ GNUNET_JSON_spec_string ("type",
+ &escrow_type),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("key_share",
+ &c->key_share)),
+ GNUNET_JSON_spec_end ()
+ };
+
+ r->ri.cs[n_index] = c;
+ c->recovery = r;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (cs,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ c->url = GNUNET_strdup (url);
+ c->type = GNUNET_strdup (escrow_type);
+ c->ci.type = c->type;
+ c->instructions = GNUNET_strdup (instructions);
+ c->ci.instructions = c->instructions;
+ c->ci.provider_url = c->url;
+ {
+ json_t *ks;
+
+ ks = json_object_get (cs,
+ "key_share");
+ if ( (NULL != ks) &&
+ (! json_is_null (ks)) )
+ {
+ c->ci.solved = true;
+ r->solved_challenges[r->solved_challenge_pos++] = c;
+ }
+ }
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse the @a dps_array and update @a r accordingly
+ *
+ * @param[in,out] r recovery information to update
+ * @param dps_arr serialized data to parse
+ * @return #GNUNET_OK on success
+ */
+static int
+parse_dps_array (struct ANASTASIS_Recovery *r,
+ json_t *dps_arr)
+{
+ json_t *dps;
+ unsigned int n_index;
+
+ if (! json_is_array (dps_arr))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ r->ri.dps_len = json_array_size (dps_arr);
+ r->dps = GNUNET_new_array (r->ri.dps_len,
+ struct DecryptionPolicy);
+ r->ri.dps = GNUNET_new_array (r->ri.dps_len,
+ struct ANASTASIS_DecryptionPolicy *);
+
+ json_array_foreach (dps_arr, n_index, dps)
+ {
+ struct DecryptionPolicy *dp = &r->dps[n_index];
+ json_t *challenges;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("emk",
+ &dp->emk),
+ GNUNET_JSON_spec_fixed_auto ("salt",
+ &dp->salt),
+ GNUNET_JSON_spec_json ("challenges",
+ &challenges),
+ GNUNET_JSON_spec_end ()
+ };
+ const char *err_json_name;
+ unsigned int err_line;
+
+ r->ri.dps[n_index] = &dp->pub_details;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (dps,
+ spec,
+ &err_json_name,
+ &err_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse decryption policy JSON entry `%s'\n",
+ err_json_name);
+ json_dumpf (dps,
+ stderr,
+ JSON_INDENT (2));
+ return GNUNET_SYSERR;
+ }
+ if (! json_is_array (challenges))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_SYSERR;
+ }
+ dp->pub_details.challenges_length = json_array_size (challenges);
+ dp->pub_details.challenges = GNUNET_new_array (
+ dp->pub_details.challenges_length,
+ struct ANASTASIS_Challenge *);
+
+ {
+ json_t *challenge;
+ unsigned int c_index;
+ json_array_foreach (challenges, c_index, challenge)
+ {
+ struct ANASTASIS_CRYPTO_TruthUUIDP uuid;
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_fixed_auto ("uuid",
+ &uuid),
+ GNUNET_JSON_spec_end ()
+ };
+ bool found = false;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (challenge,
+ ispec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_SYSERR;
+ }
+ for (unsigned int i = 0; i<r->ri.cs_len; i++)
+ {
+ if (0 !=
+ GNUNET_memcmp (&uuid,
+ &r->cs[i].ci.uuid))
+ continue;
+ dp->pub_details.challenges[c_index] = &r->cs[i];
+ found = true;
+ }
+ if (! found)
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_SYSERR;
+ }
+ }
+ }
+ GNUNET_JSON_parse_free (spec);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Asynchronously call "pc" on the recovery information.
+ *
+ * @param cls a `struct ANASTASIS_Recovery *`
+ */
+static void
+run_async_pc (void *cls)
+{
+ struct ANASTASIS_Recovery *r = cls;
+
+ r->do_async = NULL;
+ r->pc (r->pc_cls,
+ &r->ri);
+}
+
+
+struct ANASTASIS_Recovery *
+ANASTASIS_recovery_deserialize (struct GNUNET_CURL_Context *ctx,
+ const json_t *input,
+ ANASTASIS_PolicyCallback pc,
+ void *pc_cls,
+ ANASTASIS_CoreSecretCallback csc,
+ void *csc_cls)
+{
+ struct ANASTASIS_Recovery *r;
+
+ r = GNUNET_new (struct ANASTASIS_Recovery);
+ r->csc = csc;
+ r->csc_cls = csc_cls;
+ r->pc = pc;
+ r->pc_cls = pc_cls;
+ r->ctx = ctx;
+ {
+ const char *err_json_name;
+ unsigned int err_line;
+ uint32_t version;
+ json_t *dps_arr;
+ json_t *cs_arr;
+ json_t *id_data;
+ const char *provider_url;
+ const char *secret_name;
+ void *ecs;
+ size_t ecs_size;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("id",
+ &r->id),
+ GNUNET_JSON_spec_string ("provider_url",
+ &provider_url),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("secret_name",
+ &secret_name)),
+ GNUNET_JSON_spec_uint32 ("version",
+ &version),
+ GNUNET_JSON_spec_json ("dps",
+ &dps_arr),
+ GNUNET_JSON_spec_json ("cs",
+ &cs_arr),
+ GNUNET_JSON_spec_json ("id_data",
+ &id_data),
+ GNUNET_JSON_spec_varsize ("core_secret",
+ &ecs,
+ &ecs_size),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (input,
+ spec,
+ &err_json_name,
+ &err_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse recovery document JSON entry `%s'\n",
+ err_json_name);
+ json_dumpf (input,
+ stderr,
+ JSON_INDENT (2));
+ return NULL;
+ }
+ r->ri.version = version;
+ if ( (GNUNET_OK !=
+ parse_cs_array (r,
+ cs_arr)) ||
+ (GNUNET_OK !=
+ parse_dps_array (r,
+ dps_arr)) )
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_recovery_abort (r);
+ GNUNET_JSON_parse_free (spec);
+ return NULL;
+ }
+ r->id_data = json_incref (id_data);
+ r->provider_url = GNUNET_strdup (provider_url);
+ if (NULL != secret_name)
+ r->secret_name = GNUNET_strdup (secret_name);
+ r->ri.secret_name = r->secret_name;
+ if (0 != ecs_size)
+ {
+ r->enc_core_secret = GNUNET_memdup (ecs,
+ ecs_size);
+ r->enc_core_secret_size = ecs_size;
+ }
+ GNUNET_JSON_parse_free (spec);
+ }
+ if (0 == r->ri.dps_len)
+ {
+ struct ANASTASIS_CRYPTO_AccountPublicKeyP pub_key;
+
+ ANASTASIS_CRYPTO_account_public_key_derive (&r->id,
+ &pub_key);
+ if (0 != r->ri.version)
+ {
+ r->plo = ANASTASIS_policy_lookup_version (r->ctx,
+ r->provider_url,
+ &pub_key,
+ &policy_lookup_cb,
+ r,
+ r->ri.version);
+ }
+ else
+ {
+ r->plo = ANASTASIS_policy_lookup (r->ctx,
+ r->provider_url,
+ &pub_key,
+ &policy_lookup_cb,
+ r);
+ }
+ if (NULL == r->plo)
+ {
+ GNUNET_break (0);
+ ANASTASIS_recovery_abort (r);
+ return NULL;
+ }
+ }
+ else
+ {
+ r->do_async = GNUNET_SCHEDULER_add_now (&run_async_pc,
+ r);
+ }
+ return r;
+}
+
+
+void
+ANASTASIS_recovery_abort (struct ANASTASIS_Recovery *r)
+{
+ if (NULL != r->do_async)
+ {
+ GNUNET_SCHEDULER_cancel (r->do_async);
+ r->do_async = NULL;
+ }
+ if (NULL != r->plo)
+ {
+ ANASTASIS_policy_lookup_cancel (r->plo);
+ r->plo = NULL;
+ }
+ GNUNET_free (r->solved_challenges);
+ for (unsigned int j = 0; j < r->ri.dps_len; j++)
+ GNUNET_free (r->dps[j].pub_details.challenges);
+ GNUNET_free (r->ri.dps);
+ for (unsigned int i = 0; i < r->ri.cs_len; i++)
+ {
+ struct ANASTASIS_Challenge *cs = r->ri.cs[i];
+
+ if (NULL != cs->kslo)
+ {
+ ANASTASIS_keyshare_lookup_cancel (cs->kslo);
+ cs->kslo = NULL;
+ }
+ GNUNET_free (cs->url);
+ GNUNET_free (cs->type);
+ GNUNET_free (cs->instructions);
+ GNUNET_free (cs->answer);
+ }
+ GNUNET_free (r->ri.cs);
+ GNUNET_free (r->cs);
+ GNUNET_free (r->dps);
+ json_decref (r->id_data);
+ GNUNET_free (r->provider_url);
+ GNUNET_free (r->secret_name);
+ GNUNET_free (r->enc_core_secret);
+ GNUNET_free (r);
+}
diff --git a/src/lib/test_merchant.priv b/src/lib/test_merchant.priv
new file mode 100644
index 0000000..9c18c35
--- /dev/null
+++ b/src/lib/test_merchant.priv
@@ -0,0 +1 @@
+`ì&-Èí–ñ./öÀ¿ jxÌGÝ¢O:6l,ζXT4í \ No newline at end of file
diff --git a/src/reducer/Makefile.am b/src/reducer/Makefile.am
new file mode 100644
index 0000000..5cbe6f7
--- /dev/null
+++ b/src/reducer/Makefile.am
@@ -0,0 +1,45 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include -I$(top_srcdir)/src/backend -I$(top_srcdir)/src/lib
+
+if USE_COVERAGE
+ AM_CFLAGS = --coverage -O0
+ XLIB = -lgcov
+endif
+
+.NOTPARALLEL:
+
+lib_LTLIBRARIES = \
+ libanastasisredux.la
+
+libanastasisredux_la_LDFLAGS = \
+ -version-info 0:0:0 \
+ -no-undefined
+libanastasisredux_la_SOURCES = \
+ anastasis_api_redux.c anastasis_api_redux.h \
+ anastasis_api_recovery_redux.c \
+ anastasis_api_backup_redux.c \
+ validation_CH_AHV.c \
+ validation_CZ_BN.c \
+ validation_DE_SVN.c \
+ validation_DE_TIN.c \
+ validation_IN_AADHAR.c \
+ validation_IT_CF.c \
+ validation_XX_SQUARE.c \
+ validation_XY_PRIME.c
+libanastasisredux_la_LIBADD = \
+ $(top_builddir)/src/restclient/libanastasisrest.la \
+ $(top_builddir)/src/lib/libanastasis.la \
+ $(top_builddir)/src/util/libanastasisutil.la \
+ -lgnunetjson \
+ -lgnunetcurl \
+ -lgnunetutil \
+ -ltalermhd \
+ -ltalerutil \
+ -ltalerexchange \
+ -ltalermerchant \
+ -ltalerjson \
+ -ljansson \
+ -lgcrypt \
+ -ldl \
+ -lm \
+ $(XLIB)
diff --git a/src/reducer/anastasis_api_backup_redux.c b/src/reducer/anastasis_api_backup_redux.c
new file mode 100644
index 0000000..cea1360
--- /dev/null
+++ b/src/reducer/anastasis_api_backup_redux.c
@@ -0,0 +1,4893 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 2021 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/anastasis_api_backup_redux.c
+ * @brief anastasis reducer backup api
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ */
+
+#include "platform.h"
+#include "anastasis_redux.h"
+#include "anastasis_api_redux.h"
+#include <taler/taler_merchant_service.h>
+
+/**
+ * How long do Anastasis providers store data if the service
+ * is free? Must match #ANASTASIS_MAX_YEARS_STORAGE from
+ * anastasis-httpd.h.
+ */
+#define ANASTASIS_FREE_STORAGE GNUNET_TIME_relative_multiply ( \
+ GNUNET_TIME_UNIT_YEARS, 5)
+
+/**
+ * CPU limiter: do not evaluate more than 16k
+ * possible policy combinations to find the "best"
+ * policy.
+ */
+#define MAX_EVALUATIONS (1024 * 16)
+
+
+#define GENERATE_STRING(STRING) #STRING,
+static const char *backup_strings[] = {
+ ANASTASIS_BACKUP_STATES (GENERATE_STRING)
+};
+#undef GENERATE_STRING
+
+
+/**
+ * Linked list of costs.
+ */
+struct Costs
+{
+
+ /**
+ * Kept in a LL.
+ */
+ struct Costs *next;
+
+ /**
+ * Cost in one of the currencies.
+ */
+ struct TALER_Amount cost;
+};
+
+
+/**
+ * Add amount from @a cost to @a my_cost list.
+ *
+ * @param[in,out] my_cost pointer to list to modify
+ * @param cost amount to add
+ */
+static void
+add_cost (struct Costs **my_cost,
+ const struct TALER_Amount *cost)
+{
+ for (struct Costs *pos = *my_cost;
+ NULL != pos;
+ pos = pos->next)
+ {
+ if (GNUNET_OK !=
+ TALER_amount_cmp_currency (&pos->cost,
+ cost))
+ continue;
+ GNUNET_assert (0 <=
+ TALER_amount_add (&pos->cost,
+ &pos->cost,
+ cost));
+ return;
+ }
+ {
+ struct Costs *nc;
+
+ nc = GNUNET_new (struct Costs);
+ nc->cost = *cost;
+ nc->next = *my_cost;
+ *my_cost = nc;
+ }
+}
+
+
+enum ANASTASIS_BackupState
+ANASTASIS_backup_state_from_string_ (const char *state_string)
+{
+ for (enum ANASTASIS_BackupState i = 0;
+ i < sizeof (backup_strings) / sizeof(*backup_strings);
+ i++)
+ if (0 == strcmp (state_string,
+ backup_strings[i]))
+ return i;
+ return ANASTASIS_BACKUP_STATE_ERROR;
+}
+
+
+const char *
+ANASTASIS_backup_state_to_string_ (enum ANASTASIS_BackupState bs)
+{
+ if ( (bs < 0) ||
+ (bs >= sizeof (backup_strings) / sizeof(*backup_strings)) )
+ {
+ GNUNET_break_op (0);
+ return NULL;
+ }
+ return backup_strings[bs];
+}
+
+
+/**
+ * Update the 'backup_state' field of @a state to @a new_backup_state.
+ *
+ * @param[in,out] state the state to transition
+ * @param new_backup_state the state to transition to
+ */
+static void
+set_state (json_t *state,
+ enum ANASTASIS_BackupState new_backup_state)
+{
+ GNUNET_assert (
+ 0 ==
+ json_object_set_new (
+ state,
+ "backup_state",
+ json_string (ANASTASIS_backup_state_to_string_ (new_backup_state))));
+}
+
+
+/**
+ * Returns an initial ANASTASIS backup state (CONTINENT_SELECTING).
+ *
+ * @param cfg handle for gnunet configuration
+ * @return NULL on failure
+ */
+json_t *
+ANASTASIS_backup_start (const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ json_t *initial_state;
+
+ (void) cfg;
+ initial_state = ANASTASIS_REDUX_load_continents_ ();
+ if (NULL == initial_state)
+ return NULL;
+ set_state (initial_state,
+ ANASTASIS_BACKUP_STATE_CONTINENT_SELECTING);
+ return initial_state;
+}
+
+
+/**
+ * Test if @a challenge_size is small enough for the provider's
+ * @a size_limit_in_mb.
+ *
+ * We add 1024 to @a challenge_size here as a "safety margin" as
+ * the encrypted challenge has some additional headers around it
+ *
+ * @param size_limit_in_mb provider's upload limit
+ * @param challenge_size actual binary size of the challenge
+ * @return true if this fits
+ */
+static bool
+challenge_size_ok (uint32_t size_limit_in_mb,
+ size_t challenge_size)
+{
+ return (size_limit_in_mb * 1024LLU * 1024LLU >=
+ challenge_size + 1024LLU);
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "add_authentication" action.
+ * Returns an #ANASTASIS_ReduxAction if operation is async.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+static struct ANASTASIS_ReduxAction *
+add_authentication (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ json_t *auth_providers;
+ json_t *method;
+ const char *method_type;
+ void *challenge;
+ size_t challenge_size;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("type",
+ &method_type),
+ GNUNET_JSON_spec_varsize ("challenge",
+ &challenge,
+ &challenge_size),
+ GNUNET_JSON_spec_end ()
+ };
+
+ auth_providers = json_object_get (state,
+ "authentication_providers");
+ if (NULL == auth_providers)
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'authentication_providers' missing");
+ return NULL;
+ }
+
+ method = json_object_get (arguments,
+ "authentication_method");
+ if (NULL == method)
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'authentication_method' required");
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (method,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ json_dumpf (method,
+ stderr,
+ JSON_INDENT (2));
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'authentication_method' content malformed");
+ return NULL;
+ }
+ /* Check we know at least one provider that supports this method */
+ {
+ bool found = false;
+ bool too_big = false;
+ json_t *details;
+ const char *url;
+
+ json_object_foreach (auth_providers, url, details)
+ {
+ json_t *methods;
+ json_t *method;
+ size_t index;
+ uint32_t size_limit_in_mb;
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_uint32 ("storage_limit_in_megabytes",
+ &size_limit_in_mb),
+ GNUNET_JSON_spec_json ("methods",
+ &methods),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (details,
+ ispec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ continue;
+ }
+ json_array_foreach (methods, index, method)
+ {
+ const char *type;
+
+ type = json_string_value (json_object_get (method,
+ "type"));
+ GNUNET_break (NULL != type);
+ if ( (NULL != type) &&
+ (0 == strcmp (type,
+ method_type)) )
+ {
+ found = true;
+ break;
+ }
+ }
+ GNUNET_JSON_parse_free (ispec);
+ if (! challenge_size_ok (size_limit_in_mb,
+ challenge_size))
+ {
+ /* Challenge data too big for this provider. Try to find another one.
+ Note: we add 1024 to challenge-size here as a "safety margin" as
+ the encrypted challenge has some additional headers around it */
+ too_big = true;
+ found = false;
+ }
+ if (found)
+ break;
+ }
+ if (! found)
+ {
+ if (too_big)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_CHALLENGE_DATA_TOO_BIG,
+ method_type);
+ }
+ else
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_AUTHENTICATION_METHOD_NOT_SUPPORTED,
+ method_type);
+ }
+ GNUNET_JSON_parse_free (spec);
+ return NULL;
+ }
+ }
+ GNUNET_JSON_parse_free (spec);
+
+ /* append provided method to our array */
+ {
+ json_t *auth_method_arr;
+
+ auth_method_arr = json_object_get (state,
+ "authentication_methods");
+ if (NULL == auth_method_arr)
+ {
+ auth_method_arr = json_array ();
+ GNUNET_assert (0 == json_object_set_new (state,
+ "authentication_methods",
+ auth_method_arr));
+ }
+ if (! json_is_array (auth_method_arr))
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'authentication_methods' must be an array");
+ return NULL;
+ }
+ GNUNET_assert (0 ==
+ json_array_append (auth_method_arr,
+ method));
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ }
+ return NULL;
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "delete_authentication" action.
+ * Returns an #ANASTASIS_ReduxAction if operation is async.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+static struct ANASTASIS_ReduxAction *
+del_authentication (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ json_t *idx;
+ json_t *auth_method_arr;
+
+ auth_method_arr = json_object_get (state,
+ "authentication_methods");
+ if (! json_is_array (auth_method_arr))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'authentication_methods' must be an array");
+ return NULL;
+ }
+ if (NULL == arguments)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "arguments missing");
+ return NULL;
+ }
+ idx = json_object_get (arguments,
+ "authentication_method");
+ if ( (NULL == idx) ||
+ (! json_is_integer (idx)) )
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'authentication_method' must be a number");
+ return NULL;
+ }
+
+ {
+ size_t index = (size_t) json_integer_value (idx);
+
+ if (0 != json_array_remove (auth_method_arr,
+ index))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID_FOR_STATE,
+ "removal failed");
+ return NULL;
+ }
+ }
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+/* ********************** done_authentication ******************** */
+
+/**
+ * Which provider would be used for the given challenge,
+ * and at what cost?
+ */
+struct PolicyEntry
+{
+ /**
+ * URL of the provider.
+ */
+ const char *provider_name;
+
+ /**
+ * Recovery fee.
+ */
+ struct TALER_Amount usage_fee;
+};
+
+
+/**
+ * Map from challenges to providers.
+ */
+struct PolicyMap
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct PolicyMap *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct PolicyMap *prev;
+
+ /**
+ * Array of proividers selected for each challenge,
+ * with associated costs.
+ * Length of the array will be 'req_methods'.
+ */
+ struct PolicyEntry *providers;
+
+ /**
+ * Diversity score for this policy mapping.
+ */
+ unsigned int diversity;
+
+};
+
+
+/**
+ * Array of challenges for a policy, and DLL with
+ * possible mappings of challenges to providers.
+ */
+struct Policy
+{
+
+ /**
+ * Kept in DLL of all possible policies.
+ */
+ struct Policy *next;
+
+ /**
+ * Kept in DLL of all possible policies.
+ */
+ struct Policy *prev;
+
+ /**
+ * Head of DLL.
+ */
+ struct PolicyMap *pm_head;
+
+ /**
+ * Tail of DLL.
+ */
+ struct PolicyMap *pm_tail;
+
+ /**
+ * Challenges selected for this policy.
+ * Length of the array will be 'req_methods'.
+ */
+ unsigned int *challenges;
+
+};
+
+
+/**
+ * Information for running done_authentication() logic.
+ */
+struct PolicyBuilder
+{
+ /**
+ * Authentication providers available overall, from our state.
+ */
+ json_t *providers;
+
+ /**
+ * Authentication methods available overall, from our state.
+ */
+ const json_t *methods;
+
+ /**
+ * Head of DLL of all possible policies.
+ */
+ struct Policy *p_head;
+
+ /**
+ * Tail of DLL of all possible policies.
+ */
+ struct Policy *p_tail;
+
+ /**
+ * Array of authentication policies to be computed.
+ */
+ json_t *policies;
+
+ /**
+ * Array of length @e req_methods.
+ */
+ unsigned int *m_idx;
+
+ /**
+ * Array of length @e req_methods identifying a set of providers selected
+ * for each authentication method, while we are trying to compute the
+ * 'best' allocation of providers to authentication methods.
+ * Only valid during the go_with() function.
+ */
+ const char **best_sel;
+
+ /**
+ * Error hint to return on failure. Set if @e ec is not #TALER_EC_NONE.
+ */
+ const char *hint;
+
+ /**
+ * Policy we are currently building maps for.
+ */
+ struct Policy *current_policy;
+
+ /**
+ * LL of costs associated with the currently preferred
+ * policy.
+ */
+ struct Costs *best_cost;
+
+ /**
+ * Array of 'best' policy maps found so far,
+ * ordered by policy.
+ */
+ struct PolicyMap *best_map;
+
+ /**
+ * Array of the currency policy maps under evaluation
+ * by find_best_map().
+ */
+ struct PolicyMap *curr_map;
+
+ /**
+ * How many mappings have we evaluated so far?
+ * Used to limit the computation by aborting after
+ * #MAX_EVALUATIONS trials.
+ */
+ unsigned int evaluations;
+
+ /**
+ * Overall number of challenges provided by the user.
+ */
+ unsigned int num_methods;
+
+ /**
+ * Number of challenges that must be satisfied to recover the secret.
+ * Derived from the total number of challenges entered by the user.
+ */
+ unsigned int req_methods;
+
+ /**
+ * Number of different Anastasis providers selected in @e best_sel.
+ * Only valid during the go_with() function.
+ */
+ unsigned int best_diversity;
+
+ /**
+ * Number of identical challenges duplicated at
+ * various providers in the best case. Smaller is
+ * better.
+ */
+ unsigned int best_duplicates;
+
+ /**
+ * Error code to return, #TALER_EC_NONE on success.
+ */
+ enum TALER_ErrorCode ec;
+
+};
+
+
+/**
+ * Free @a costs LL.
+ *
+ * @param[in] costs linked list to free
+ */
+static void
+free_costs (struct Costs *costs)
+{
+ if (NULL == costs)
+ return;
+ free_costs (costs->next);
+ GNUNET_free (costs);
+}
+
+
+/**
+ * Check if providers @a p1 and @a p2 have equivalent
+ * methods and cost structures.
+ *
+ * @return true if the providers are fully equivalent
+ */
+static bool
+equiv_provider (struct PolicyBuilder *pb,
+ const char *p1,
+ const char *p2)
+{
+ json_t *j1;
+ json_t *j2;
+ json_t *m1;
+ json_t *m2;
+ struct TALER_Amount uc1;
+ struct TALER_Amount uc2;
+
+ j1 = json_object_get (pb->providers,
+ p1);
+ j2 = json_object_get (pb->providers,
+ p2);
+ if ( (NULL == j1) ||
+ (NULL == j2) )
+ {
+ GNUNET_break (0);
+ return false;
+ }
+
+ {
+ struct GNUNET_JSON_Specification s1[] = {
+ GNUNET_JSON_spec_json ("methods",
+ &m1),
+ TALER_JSON_spec_amount_any ("truth_upload_fee",
+ &uc1),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j1,
+ s1,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ return false;
+ }
+ }
+
+ {
+ struct GNUNET_JSON_Specification s2[] = {
+ GNUNET_JSON_spec_json ("methods",
+ &m2),
+ TALER_JSON_spec_amount_any ("truth_upload_fee",
+ &uc2),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j2,
+ s2,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ return false;
+ }
+ }
+
+ if ( (GNUNET_OK !=
+ TALER_amount_cmp_currency (&uc1,
+ &uc2)) ||
+ (0 !=
+ TALER_amount_cmp (&uc1,
+ &uc2)) )
+ return false;
+
+ if (json_array_size (m1) != json_array_size (m2))
+ return false;
+ {
+ size_t idx1;
+ json_t *e1;
+
+ json_array_foreach (m1, idx1, e1)
+ {
+ const char *type1;
+ struct TALER_Amount fee1;
+ struct GNUNET_JSON_Specification s1[] = {
+ GNUNET_JSON_spec_string ("type",
+ &type1),
+ TALER_JSON_spec_amount_any ("usage_fee",
+ &fee1),
+ GNUNET_JSON_spec_end ()
+ };
+ bool matched = false;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (e1,
+ s1,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ return false;
+ }
+ {
+ size_t idx2;
+ json_t *e2;
+
+ json_array_foreach (m2, idx2, e2)
+ {
+ const char *type2;
+ struct TALER_Amount fee2;
+ struct GNUNET_JSON_Specification s2[] = {
+ GNUNET_JSON_spec_string ("type",
+ &type2),
+ TALER_JSON_spec_amount_any ("usage_fee",
+ &fee2),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (e2,
+ s2,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ return false;
+ }
+ if ( (0 == strcmp (type1,
+ type2)) &&
+ (GNUNET_OK ==
+ TALER_amount_cmp_currency (&fee1,
+ &fee2)) &&
+ (0 == TALER_amount_cmp (&fee1,
+ &fee2)) )
+ {
+ matched = true;
+ break;
+ }
+ }
+ }
+ if (! matched)
+ return false;
+ }
+ }
+ return true;
+}
+
+
+/**
+ * Evaluate the cost/benefit of the provider selection in @a prov_sel
+ * and if it is better then the best known one in @a pb, update @a pb.
+ *
+ * @param[in,out] pb our operational context
+ * @param[in,out] prov_sel array of req_methods provider indices to complete
+ */
+static void
+eval_provider_selection (struct PolicyBuilder *pb,
+ const char *prov_sel[])
+{
+ unsigned int curr_diversity;
+ struct PolicyEntry policy_ent[pb->req_methods];
+
+ for (unsigned int i = 0; i < pb->req_methods; i++)
+ {
+ const json_t *method_obj = json_array_get (pb->methods,
+ pb->m_idx[i]);
+ const json_t *provider_cfg = json_object_get (pb->providers,
+ prov_sel[i]);
+ json_t *provider_methods;
+ const char *method_type;
+ json_t *md;
+ size_t index;
+ bool found = false;
+ uint32_t size_limit_in_mb;
+ struct TALER_Amount upload_cost;
+ struct GNUNET_JSON_Specification pspec[] = {
+ GNUNET_JSON_spec_uint32 ("storage_limit_in_megabytes",
+ &size_limit_in_mb),
+ GNUNET_JSON_spec_json ("methods",
+ &provider_methods),
+ TALER_JSON_spec_amount_any ("truth_upload_fee",
+ &upload_cost),
+ GNUNET_JSON_spec_end ()
+ };
+ void *challenge;
+ size_t challenge_size;
+ struct GNUNET_JSON_Specification mspec[] = {
+ GNUNET_JSON_spec_string ("type",
+ &method_type),
+ GNUNET_JSON_spec_varsize ("challenge",
+ &challenge,
+ &challenge_size),
+ GNUNET_JSON_spec_end ()
+ };
+
+ policy_ent[i].provider_name = prov_sel[i];
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (method_obj,
+ mspec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ pb->ec = TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID;
+ pb->hint = "'authentication_method' content malformed";
+ return;
+ }
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (provider_cfg,
+ pspec,
+ NULL, NULL))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Skipping provider %s: no suitable configuration\n",
+ prov_sel[i]);
+ GNUNET_JSON_parse_free (mspec);
+ return;
+ }
+ json_array_foreach (provider_methods, index, md)
+ {
+ const char *type;
+ struct TALER_Amount method_cost;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("type",
+ &type),
+ TALER_JSON_spec_amount_any ("usage_fee",
+ &method_cost),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (md,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ pb->ec = TALER_EC_ANASTASIS_REDUCER_STATE_INVALID;
+ pb->hint = "'methods' of provider";
+ GNUNET_JSON_parse_free (pspec);
+ return;
+ }
+ if ( (0 == strcmp (type,
+ method_type)) &&
+ (challenge_size_ok (size_limit_in_mb,
+ challenge_size) ) )
+ {
+ found = true;
+ GNUNET_break (0 <=
+ TALER_amount_add (&policy_ent[i].usage_fee,
+ &method_cost,
+ &upload_cost));
+ }
+ }
+ if (! found)
+ {
+ /* Provider does not OFFER this method, combination not possible.
+ Cost is basically 'infinite', but we simply then skip this. */
+ GNUNET_JSON_parse_free (pspec);
+ GNUNET_JSON_parse_free (mspec);
+ return;
+ }
+ GNUNET_JSON_parse_free (mspec);
+ GNUNET_JSON_parse_free (pspec);
+ }
+
+ /* calculate provider diversity by counting number of different
+ providers selected */
+ curr_diversity = 0;
+ for (unsigned int i = 0; i < pb->req_methods; i++)
+ {
+ bool found = false;
+
+ for (unsigned int j = 0; j < i; j++)
+ {
+ if (prov_sel[i] == prov_sel[j])
+ {
+ found = true;
+ break;
+ }
+ }
+ if (! found)
+ curr_diversity++;
+ }
+#if DEBUG
+ fprintf (stderr,
+ "Diversity: %u (best: %u)\n",
+ curr_diversity,
+ pb->best_diversity);
+#endif
+ if (curr_diversity < pb->best_diversity)
+ return; /* do not allow combinations that are bad
+ for provider diversity */
+ if (curr_diversity > pb->best_diversity)
+ {
+ /* drop existing policies, they are all worse */
+ struct PolicyMap *m;
+
+ while (NULL != (m = pb->current_policy->pm_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (pb->current_policy->pm_head,
+ pb->current_policy->pm_tail,
+ m);
+ GNUNET_free (m->providers);
+ GNUNET_free (m);
+ }
+ pb->best_diversity = curr_diversity;
+ }
+ if (NULL == pb->p_head)
+ {
+ /* For the first policy, check for equivalent
+ policy mapping existing: we
+ do not want to do spend CPU time investigating
+ purely equivalent permutations */
+ for (struct PolicyMap *m = pb->current_policy->pm_head;
+ NULL != m;
+ m = m->next)
+ {
+ bool equiv = true;
+ for (unsigned int i = 0; i<pb->req_methods; i++)
+ {
+ if (! equiv_provider (pb,
+ m->providers[i].provider_name,
+ policy_ent[i].provider_name))
+ {
+ equiv = false;
+ break;
+ }
+ }
+ if (equiv)
+ return; /* equivalent to known allocation */
+ }
+ }
+
+ /* Add possible mapping to result list */
+ {
+ struct PolicyMap *m;
+
+ m = GNUNET_new (struct PolicyMap);
+ m->providers = GNUNET_new_array (pb->req_methods,
+ struct PolicyEntry);
+ memcpy (m->providers,
+ policy_ent,
+ sizeof (struct PolicyEntry) * pb->req_methods);
+ m->diversity = curr_diversity;
+ GNUNET_CONTAINER_DLL_insert (pb->current_policy->pm_head,
+ pb->current_policy->pm_tail,
+ m);
+ }
+}
+
+
+/**
+ * Recursively compute possible combination(s) of provider candidates
+ * in @e prov_sel. The selection is complete up to index @a i. Calls
+ * eval_provider_selection() upon a feasible provider selection for
+ * evaluation, resulting in "better" combinations being persisted in
+ * @a pb.
+ *
+ * @param[in,out] pb our operational context
+ * @param[in,out] prov_sel array of req_methods provider URLs to complete
+ * @param i index up to which @a prov_sel is already initialized
+ */
+static void
+provider_candidate (struct PolicyBuilder *pb,
+ const char *prov_sel[],
+ unsigned int i)
+{
+ const char *url;
+ json_t *pconfig;
+
+ json_object_foreach (pb->providers, url, pconfig)
+ {
+ prov_sel[i] = url;
+ if (i == pb->req_methods - 1)
+ {
+ eval_provider_selection (pb,
+ prov_sel);
+ if (TALER_EC_NONE != pb->ec)
+ break;
+ continue;
+ }
+ provider_candidate (pb,
+ prov_sel,
+ i + 1);
+ }
+}
+
+
+/**
+ * Using the selection of authentication methods from @a pb in
+ * "m_idx", compute the best choice of providers.
+ *
+ * @param[in,out] pb our operational context
+ */
+static void
+go_with (struct PolicyBuilder *pb)
+{
+ const char *prov_sel[pb->req_methods];
+ struct Policy *policy;
+
+ /* compute provider selection */
+ policy = GNUNET_new (struct Policy);
+ policy->challenges = GNUNET_new_array (pb->req_methods,
+ unsigned int);
+ memcpy (policy->challenges,
+ pb->m_idx,
+ pb->req_methods * sizeof (unsigned int));
+ pb->current_policy = policy;
+ pb->best_diversity = 0;
+ provider_candidate (pb,
+ prov_sel,
+ 0);
+ GNUNET_CONTAINER_DLL_insert (pb->p_head,
+ pb->p_tail,
+ policy);
+ pb->current_policy = NULL;
+}
+
+
+/**
+ * Recursively computes all possible subsets of length "req_methods"
+ * from an array of length "num_methods", calling "go_with" on each of
+ * those subsets (in "m_idx").
+ *
+ * @param[in,out] pb our operational context
+ * @param i offset up to which the "m_idx" has been computed
+ */
+static void
+method_candidate (struct PolicyBuilder *pb,
+ unsigned int i)
+{
+ unsigned int start;
+ unsigned int *m_idx = pb->m_idx;
+
+ start = (i > 0) ? m_idx[i - 1] + 1 : 0;
+ for (unsigned int j = start; j < pb->num_methods; j++)
+ {
+ m_idx[i] = j;
+ if (i == pb->req_methods - 1)
+ {
+#if DEBUG
+ fprintf (stderr,
+ "Suggesting: ");
+ for (unsigned int k = 0; k<pb->req_methods; k++)
+ {
+ fprintf (stderr,
+ "%u ",
+ m_idx[k]);
+ }
+ fprintf (stderr, "\n");
+#endif
+ go_with (pb);
+ continue;
+ }
+ method_candidate (pb,
+ i + 1);
+ }
+}
+
+
+/**
+ * Lookup @a salt of @a provider_url in @a state.
+ *
+ * @param state the state to inspect
+ * @param provider_url provider to look into
+ * @param[out] salt value to extract
+ * @return #GNUNET_OK on success
+ */
+static int
+lookup_salt (const json_t *state,
+ const char *provider_url,
+ struct ANASTASIS_CRYPTO_ProviderSaltP *salt)
+{
+ const json_t *aps;
+ const json_t *cfg;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("salt",
+ salt),
+ GNUNET_JSON_spec_end ()
+ };
+
+ aps = json_object_get (state,
+ "authentication_providers");
+ if (NULL == aps)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ cfg = json_object_get (aps,
+ provider_url);
+ if (NULL == cfg)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (cfg,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Compare two cost lists.
+ *
+ * @param my cost to compare
+ * @param be cost to compare
+ * @return 0 if costs are estimated equal,
+ * 1 if @a my < @a be
+ * -1 if @a my > @a be
+ */
+static int
+compare_costs (const struct Costs *my,
+ const struct Costs *be)
+{
+ int ranking = 0;
+
+ for (const struct Costs *cmp = be;
+ NULL != cmp;
+ cmp = cmp->next)
+ {
+ bool found = false;
+
+ for (const struct Costs *pos = my;
+ NULL != pos;
+ pos = pos->next)
+ {
+ if (GNUNET_OK !=
+ TALER_amount_cmp_currency (&cmp->cost,
+ &pos->cost))
+ continue;
+ found = true;
+ }
+ if (! found)
+ ranking--; /* new policy has no cost in this currency */
+ }
+
+ for (const struct Costs *pos = my;
+ NULL != pos;
+ pos = pos->next)
+ {
+ bool found = false;
+
+ for (const struct Costs *cmp = be;
+ NULL != cmp;
+ cmp = cmp->next)
+ {
+ if (GNUNET_OK !=
+ TALER_amount_cmp_currency (&cmp->cost,
+ &pos->cost))
+ continue;
+ found = true;
+ switch (TALER_amount_cmp (&cmp->cost,
+ &pos->cost))
+ {
+ case -1: /* cmp < pos */
+ ranking--;
+ break;
+ case 0:
+ break;
+ case 1: /* cmp > pos */
+ ranking++;
+ break;
+ }
+ break;
+ }
+ if (! found)
+ ranking++; /* old policy has no cost in this currency */
+ }
+ if (0 == ranking)
+ return 0;
+ return (0 > ranking) ? -1 : 1;
+}
+
+
+/**
+ * Evaluate the combined policy map stack in the ``curr_map`` of @a pb
+ * and compare to the current best cost. If we are better, save the
+ * stack in the ``best_map``.
+ *
+ * @param[in,out] pb policy builder we evaluate for
+ * @param num_policies length of the ``curr_map`` array
+ */
+static void
+evaluate_map (struct PolicyBuilder *pb,
+ unsigned int num_policies)
+{
+ struct Costs *my_cost = NULL;
+ unsigned int i = 0;
+ unsigned int duplicates = 0;
+ int ccmp;
+
+#if DEBUG
+ fprintf (stderr,
+ "Checking...\n");
+#endif
+ /* calculate cost */
+ for (const struct Policy *p = pb->p_head;
+ NULL != p;
+ p = p->next)
+ {
+ const struct PolicyMap *pm = &pb->curr_map[i++];
+
+#if DEBUG
+ fprintf (stderr,
+ "Evaluating %p (%u): ",
+ p,
+ pm->diversity);
+ for (unsigned int k = 0; k<pb->req_methods; k++)
+ {
+ const struct PolicyEntry *pe = &pm->providers[k];
+
+ fprintf (stderr,
+ "%u->%s ",
+ p->challenges[k],
+ pe->provider_name);
+ }
+ fprintf (stderr, "\n");
+#endif
+ for (unsigned int j = 0; j<pb->req_methods; j++)
+ {
+ const struct PolicyEntry *pe = &pm->providers[j];
+ unsigned int cv = p->challenges[j];
+ bool found = false;
+ unsigned int i2 = 0;
+
+ /* check for duplicates */
+ for (const struct Policy *p2 = pb->p_head;
+ p2 != p;
+ p2 = p2->next)
+ {
+ const struct PolicyMap *pm2 = &pb->curr_map[i2++];
+
+ for (unsigned int j2 = 0; j2<pb->req_methods; j2++)
+ {
+ const struct PolicyEntry *pe2 = &pm2->providers[j2];
+ unsigned int cv2 = p2->challenges[j2];
+
+ if (cv != cv2)
+ continue; /* different challenge */
+ if (0 == strcmp (pe->provider_name,
+ pe2->provider_name))
+ found = true; /* same challenge&provider! */
+ else
+ duplicates++; /* penalty for same challenge at two providers */
+ }
+ }
+ if (found)
+ continue; /* cost already included, do not add */
+ add_cost (&my_cost,
+ &pe->usage_fee);
+ }
+ }
+
+ ccmp = -1; /* non-zero if 'best_duplicates' is UINT_MAX */
+ if ( (UINT_MAX != pb->best_duplicates) &&
+ (0 > (ccmp = compare_costs (my_cost,
+ pb->best_cost))) )
+ {
+ /* new method not clearly better, do not use it */
+ free_costs (my_cost);
+#if DEBUG
+ fprintf (stderr,
+ "... useless\n");
+#endif
+ return;
+ }
+ if ( (0 == ccmp) &&
+ (duplicates > pb->best_duplicates) )
+ {
+ /* new method is cost-equal, but looses on duplicates,
+ do not use it */
+ free_costs (my_cost);
+#if DEBUG
+ fprintf (stderr,
+ "... useless\n");
+#endif
+ return;
+ }
+ /* new method is better (or first), set as best */
+#if DEBUG
+ fprintf (stderr,
+ "New best: %u duplicates, %s cost\n",
+ duplicates,
+ TALER_amount2s (&my_cost->cost));
+#endif
+ free_costs (pb->best_cost);
+ pb->best_cost = my_cost;
+ pb->best_duplicates = duplicates;
+ memcpy (pb->best_map,
+ pb->curr_map,
+ sizeof (struct PolicyMap) * num_policies);
+}
+
+
+/**
+ * Try all policy maps for @a pos and evaluate the
+ * resulting total cost, saving the best result in
+ * @a pb.
+ *
+ * @param[in,out] pb policy builder context
+ * @param pos policy we are currently looking at maps for
+ * @param off index of @a pos for the policy map
+ */
+static void
+find_best_map (struct PolicyBuilder *pb,
+ struct Policy *pos,
+ unsigned int off)
+{
+ if (NULL == pos)
+ {
+ evaluate_map (pb,
+ off);
+ pb->evaluations++;
+ return;
+ }
+ for (struct PolicyMap *pm = pos->pm_head;
+ NULL != pm;
+ pm = pm->next)
+ {
+ pb->curr_map[off] = *pm;
+ find_best_map (pb,
+ pos->next,
+ off + 1);
+ if (pb->evaluations >= MAX_EVALUATIONS)
+ break;
+ }
+}
+
+
+/**
+ * Select cheapest policy combinations and add them to the JSON ``policies``
+ * array in @a pb
+ *
+ * @param[in,out] policy builder with our state
+ */
+static void
+select_policies (struct PolicyBuilder *pb)
+{
+ unsigned int cnt = 0;
+
+ for (struct Policy *p = pb->p_head;
+ NULL != p;
+ p = p->next)
+ cnt++;
+ {
+ struct PolicyMap best[cnt];
+ struct PolicyMap curr[cnt];
+ unsigned int i;
+
+ pb->best_map = best;
+ pb->curr_map = curr;
+ pb->best_duplicates = UINT_MAX; /* worst */
+ find_best_map (pb,
+ pb->p_head,
+ 0);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Assessed %u/%u policies\n",
+ pb->evaluations,
+ (unsigned int) MAX_EVALUATIONS);
+ i = 0;
+ for (struct Policy *p = pb->p_head;
+ NULL != p;
+ p = p->next)
+ {
+ struct PolicyMap *pm = &best[i++];
+ json_t *method_arr;
+
+#if DEBUG
+ fprintf (stderr,
+ "Best map (%u): ",
+ pm->diversity);
+ for (unsigned int k = 0; k<pb->req_methods; k++)
+ {
+ fprintf (stderr,
+ "%u->%s ",
+ p->challenges[k],
+ pm->providers[k].provider_name);
+ }
+ fprintf (stderr, "\n");
+#endif
+ /* Convert "best" selection into 'policies' array */
+ method_arr = json_array ();
+ GNUNET_assert (NULL != method_arr);
+ for (unsigned int i = 0; i < pb->req_methods; i++)
+ {
+ json_t *policy_method = json_pack ("{s:I, s:s}",
+ "authentication_method",
+ (json_int_t) p->challenges[i],
+ "provider",
+ pm->providers[i].provider_name);
+ GNUNET_assert (0 ==
+ json_array_append_new (method_arr,
+ policy_method));
+ }
+ {
+ json_t *policy = json_pack ("{s:o}",
+ "methods",
+ method_arr);
+ GNUNET_assert (NULL != policy);
+ GNUNET_assert (0 ==
+ json_array_append_new (pb->policies,
+ policy));
+ }
+ }
+ }
+}
+
+
+/**
+ * Clean up @a pb, in particular the policies DLL.
+ *
+ * @param[in] pb builder to clean up
+ */
+static void
+clean_pb (struct PolicyBuilder *pb)
+{
+ struct Policy *p;
+
+ while (NULL != (p = pb->p_head))
+ {
+ struct PolicyMap *pm;
+
+ while (NULL != (pm = p->pm_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (p->pm_head,
+ p->pm_tail,
+ pm);
+ GNUNET_free (pm->providers);
+ GNUNET_free (pm);
+ }
+ GNUNET_CONTAINER_DLL_remove (pb->p_head,
+ pb->p_tail,
+ p);
+ GNUNET_free (p->challenges);
+ GNUNET_free (p);
+ }
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "done_authentication" action. Automaticially computes policies
+ * based on available Anastasis providers and challenges provided by
+ * the user.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+static struct ANASTASIS_ReduxAction *
+done_authentication (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ struct PolicyBuilder pb = {
+ .ec = TALER_EC_NONE
+ };
+ json_t *providers;
+ json_t *policy_providers;
+
+ pb.providers = json_object_get (state,
+ "authentication_providers");
+ if ( (NULL == pb.providers) ||
+ (! json_is_object (pb.providers) ) )
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'authentication_providers' must be provided");
+ return NULL;
+ }
+ pb.methods = json_object_get (state,
+ "authentication_methods");
+ if ( (NULL == pb.methods) ||
+ (! json_is_array (pb.methods)) )
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'authentication_methods' must be provided");
+ return NULL;
+ }
+ pb.num_methods = json_array_size (pb.methods);
+ switch (pb.num_methods)
+ {
+ case 0:
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'authentication_methods' must not be empty");
+ return NULL;
+ case 1:
+ case 2:
+ pb.req_methods = pb.num_methods;
+ break;
+ case 3:
+ case 4:
+ pb.req_methods = pb.num_methods - 1;
+ break;
+ case 5:
+ case 6:
+ pb.req_methods = pb.num_methods - 2;
+ break;
+ case 7:
+ pb.req_methods = pb.num_methods - 3;
+ break;
+ default:
+ /* cap at 4 for auto-generation, algorithm
+ to compute mapping gets too expensive
+ otherwise. */
+ pb.req_methods = 4;
+ break;
+ }
+ {
+ unsigned int m_idx[pb.req_methods];
+
+ /* select req_methods from num_methods. */
+ pb.m_idx = m_idx;
+ method_candidate (&pb,
+ 0);
+ }
+ pb.policies = json_array ();
+ select_policies (&pb);
+ clean_pb (&pb);
+ if (TALER_EC_NONE != pb.ec)
+ {
+ json_decref (pb.policies);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ pb.ec,
+ pb.hint);
+ return NULL;
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (state,
+ "policies",
+ pb.policies));
+ providers = json_object_get (arguments,
+ "providers");
+ if (NULL == providers)
+ {
+ /* Setup a providers array from all working providers */
+ json_t *available = json_object_get (state,
+ "authentication_providers");
+ const char *url;
+ json_t *details;
+
+ policy_providers = json_array ();
+ json_object_foreach (available, url, details)
+ {
+ json_t *provider;
+ struct ANASTASIS_CRYPTO_ProviderSaltP salt;
+
+ if (GNUNET_OK !=
+ lookup_salt (state,
+ url,
+ &salt))
+ continue; /* skip providers that are down */
+ provider = json_pack ("{s:s}",
+ "provider_url", url);
+ GNUNET_assert (NULL != provider);
+ GNUNET_assert (0 ==
+ json_array_append_new (policy_providers,
+ provider));
+ }
+ }
+ else
+ {
+ /* Setup a providers array from all working providers */
+ size_t off;
+ json_t *url;
+
+ policy_providers = json_array ();
+ json_array_foreach (providers, off, url)
+ {
+ json_t *provider;
+ struct ANASTASIS_CRYPTO_ProviderSaltP salt;
+ const char *url_str;
+
+ url_str = json_string_value (url);
+ if ( (NULL == url_str) ||
+ (GNUNET_OK !=
+ lookup_salt (state,
+ url_str,
+ &salt)) )
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "unworkable provider requested");
+ return NULL;
+ }
+ provider = json_pack ("{s:s}",
+ "provider_url", url);
+ GNUNET_assert (NULL != provider);
+ GNUNET_assert (0 ==
+ json_array_append_new (policy_providers,
+ provider));
+ }
+ }
+ if (0 == json_array_size (policy_providers))
+ {
+ json_decref (policy_providers);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "no workable providers in state");
+ return NULL;
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (state,
+ "policy_providers",
+ policy_providers));
+ set_state (state,
+ ANASTASIS_BACKUP_STATE_POLICIES_REVIEWING);
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+/* ******************** add_provider ******************* */
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "add_provider" action. Adds another Anastasis provider
+ * to the list of available providers for storing information.
+ *
+ * @param state state to operate on
+ * @param arguments arguments with a provider URL to add
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ */
+static struct ANASTASIS_ReduxAction *
+add_provider (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ if (ANASTASIS_add_provider_ (state,
+ arguments,
+ cb,
+ cb_cls))
+ return NULL;
+ return ANASTASIS_REDUX_backup_begin_ (state,
+ NULL,
+ cb,
+ cb_cls);
+}
+
+
+/* ******************** add_policy ******************* */
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "add_policy" action.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+static struct ANASTASIS_ReduxAction *
+add_policy (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ const json_t *arg_array;
+ json_t *policies;
+ const json_t *auth_providers;
+ const json_t *auth_methods;
+ json_t *methods;
+
+ if (NULL == arguments)
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "arguments missing");
+ return NULL;
+ }
+ arg_array = json_object_get (arguments,
+ "policy");
+ if (! json_is_array (arg_array))
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'policy' not an array");
+ return NULL;
+ }
+ policies = json_object_get (state,
+ "policies");
+ if (! json_is_array (policies))
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'policies' not an array");
+ return NULL;
+ }
+ auth_providers = json_object_get (state,
+ "authentication_providers");
+ if (! json_is_object (auth_providers))
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'auth_providers' not an object");
+ return NULL;
+ }
+ auth_methods = json_object_get (state,
+ "authentication_methods");
+ if (! json_is_array (auth_methods))
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'auth_methods' not an array");
+ return NULL;
+ }
+
+ methods = json_array ();
+ GNUNET_assert (NULL != methods);
+
+ /* Add all methods from 'arg_array' to 'methods' */
+ {
+ size_t index;
+ json_t *method;
+
+ json_array_foreach (arg_array, index, method)
+ {
+ const char *provider_url;
+ uint32_t method_idx;
+ json_t *prov_methods;
+ const char *method_type;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("provider",
+ &provider_url),
+ GNUNET_JSON_spec_uint32 ("authentication_method",
+ &method_idx),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (method,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ json_decref (methods);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'method' details malformed");
+ return NULL;
+ }
+
+ {
+ const json_t *prov_cfg;
+ uint32_t limit;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_uint32 ("storage_limit_in_megabytes",
+ &limit),
+ GNUNET_JSON_spec_json ("methods",
+ &prov_methods),
+ GNUNET_JSON_spec_end ()
+ };
+
+ prov_cfg = json_object_get (auth_providers,
+ provider_url);
+ if (NULL == prov_cfg)
+ {
+ GNUNET_break (0);
+ json_decref (methods);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "provider URL unknown");
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (prov_cfg,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ json_decref (methods);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "provider lacks authentication methods");
+ return NULL;
+
+ }
+ if (! json_is_array (prov_methods))
+ {
+ GNUNET_break (0);
+ json_decref (methods);
+ json_decref (prov_methods);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "provider lacks authentication methods");
+ return NULL;
+ }
+ }
+
+ {
+ const json_t *auth_method;
+
+ auth_method = json_array_get (auth_methods,
+ method_idx);
+ if (NULL == auth_method)
+ {
+ GNUNET_break (0);
+ json_decref (methods);
+ json_decref (prov_methods);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "authentication method unknown");
+ return NULL;
+ }
+ method_type = json_string_value (json_object_get (auth_method,
+ "type"));
+ if (NULL == method_type)
+ {
+ GNUNET_break (0);
+ json_decref (methods);
+ json_decref (prov_methods);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "authentication method must be a string");
+ return NULL;
+ }
+ }
+
+ {
+ bool found = false;
+ size_t index;
+ json_t *pm;
+ json_array_foreach (prov_methods, index, pm)
+ {
+ struct TALER_Amount method_cost;
+ const char *type;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("type",
+ &type),
+ TALER_JSON_spec_amount_any ("usage_fee",
+ &method_cost),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (pm,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ json_decref (methods);
+ json_decref (prov_methods);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "provider authentication method specification invalid");
+ return NULL;
+ }
+ if (0 != strcmp (type,
+ method_type))
+ continue;
+ found = true;
+ break;
+ }
+ if (! found)
+ {
+ GNUNET_break (0);
+ json_decref (methods);
+ json_decref (prov_methods);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "selected provider does not support authentication method");
+ return NULL;
+ }
+ }
+ GNUNET_assert (0 ==
+ json_array_append (methods,
+ method));
+ json_decref (prov_methods);
+ } /* end of json_array_foreach (arg_array, index, method) */
+ }
+
+ /* add new policy to array of existing policies */
+ {
+ json_t *policy;
+ json_t *idx;
+
+ policy = json_pack ("{s:o}",
+ "methods",
+ methods);
+ GNUNET_assert (NULL != policy);
+ idx = json_object_get (arguments,
+ "policy_index");
+ if ( (NULL == idx) ||
+ (! json_is_integer (idx)) )
+ {
+ GNUNET_assert (0 ==
+ json_array_append_new (policies,
+ policy));
+ }
+ else
+ {
+ GNUNET_assert (0 ==
+ json_array_insert_new (policies,
+ json_integer_value (idx),
+ policy));
+ }
+ }
+
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+/* ******************** update_policy ******************* */
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "update_policy" action.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+static struct ANASTASIS_ReduxAction *
+update_policy (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ const json_t *idx;
+ size_t index;
+ json_t *policy_arr;
+
+ if (NULL == arguments)
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "arguments missing");
+ return NULL;
+ }
+ idx = json_object_get (arguments,
+ "policy_index");
+ if (! json_is_integer (idx))
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'policy_index' must be an integer");
+ return NULL;
+ }
+ index = json_integer_value (idx);
+ policy_arr = json_object_get (state,
+ "policies");
+ if (! json_is_array (policy_arr))
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'policies' must be an array");
+ return NULL;
+ }
+ if (0 != json_array_remove (policy_arr,
+ index))
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID_FOR_STATE,
+ "removal failed");
+ return NULL;
+ }
+ return add_policy (state,
+ arguments,
+ cb,
+ cb_cls);
+}
+
+
+/* ******************** del_policy ******************* */
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "delete_policy" action.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+static struct ANASTASIS_ReduxAction *
+del_policy (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ const json_t *idx;
+ size_t index;
+ json_t *policy_arr;
+
+ if (NULL == arguments)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "arguments missing");
+ return NULL;
+ }
+ idx = json_object_get (arguments,
+ "policy_index");
+ if (! json_is_integer (idx))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'policy_index' must be an integer");
+ return NULL;
+ }
+ index = json_integer_value (idx);
+ policy_arr = json_object_get (state,
+ "policies");
+ if (! json_is_array (policy_arr))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'policies' must be an array");
+ return NULL;
+ }
+ if (0 != json_array_remove (policy_arr,
+ index))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID_FOR_STATE,
+ "removal failed");
+ return NULL;
+ }
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+/* ******************** del_challenge ******************* */
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "delete_challenge" action.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+static struct ANASTASIS_ReduxAction *
+del_challenge (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ const json_t *pidx;
+ const json_t *cidx;
+ size_t index;
+ json_t *policy_arr;
+ json_t *policy;
+ json_t *method_arr;
+
+ if (NULL == arguments)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "arguments missing");
+ return NULL;
+ }
+ pidx = json_object_get (arguments,
+ "policy_index");
+ cidx = json_object_get (arguments,
+ "challenge_index");
+ if (! json_is_integer (pidx))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'policy_index' must be an integer");
+ return NULL;
+ }
+ if (! json_is_integer (cidx))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'challenge_index' must be an integer");
+ return NULL;
+ }
+ index = json_integer_value (pidx);
+ policy_arr = json_object_get (state,
+ "policies");
+ if (! json_is_array (policy_arr))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'policies' must be an array");
+ return NULL;
+ }
+ policy = json_array_get (policy_arr,
+ index);
+ if (NULL == policy)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'policy_index' out of range");
+ return NULL;
+ }
+ method_arr = json_object_get (policy,
+ "methods");
+ if (NULL == method_arr)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID_FOR_STATE,
+ "methods missing in policy");
+ return NULL;
+ }
+ index = json_integer_value (cidx);
+ if (0 != json_array_remove (method_arr,
+ index))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID_FOR_STATE,
+ "removal failed");
+ return NULL;
+ }
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+/* ********************** done_policy_review ***************** */
+
+
+/**
+ * Calculate how many years of service we need
+ * from the desired @a expiration time,
+ * rounding up.
+ *
+ * @param expiration desired expiration time
+ * @return number of years of service to pay for
+*/
+static unsigned int
+expiration_to_years (struct GNUNET_TIME_Absolute expiration)
+{
+ struct GNUNET_TIME_Relative rem;
+ unsigned int years;
+
+ rem = GNUNET_TIME_absolute_get_remaining (expiration);
+ years = rem.rel_value_us / GNUNET_TIME_UNIT_YEARS.rel_value_us;
+ if (0 != rem.rel_value_us % GNUNET_TIME_UNIT_YEARS.rel_value_us)
+ years++;
+ return years;
+}
+
+
+/**
+ * Update @a state such that the earliest expiration for
+ * any truth or policy is @a expiration. Recalculate
+ * the ``upload_fees`` array with the associated costs.
+ *
+ * @param[in,out] state our state to update
+ * @param expiration new expiration to enforce
+ * @return #GNUNET_OK on success,
+ * #GNUNET_SYSERR if the state is invalid
+ */
+static enum GNUNET_GenericReturnValue
+update_expiration_cost (json_t *state,
+ struct GNUNET_TIME_Absolute expiration)
+{
+ struct Costs *costs = NULL;
+ unsigned int years;
+ json_t *providers;
+ bool is_free = true;
+
+ providers = json_object_get (state,
+ "authentication_providers");
+ if (! json_is_object (providers))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ years = expiration_to_years (expiration);
+
+ /* go over all providers and add up cost */
+ {
+ const char *url;
+ json_t *provider;
+
+ json_object_foreach (providers, url, provider)
+ {
+ struct TALER_Amount annual_fee;
+ struct GNUNET_JSON_Specification pspec[] = {
+ TALER_JSON_spec_amount_any ("annual_fee",
+ &annual_fee),
+ GNUNET_JSON_spec_end ()
+ };
+ struct TALER_Amount fee;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (provider,
+ pspec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (0 >
+ TALER_amount_multiply (&fee,
+ &annual_fee,
+ years))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ add_cost (&costs,
+ &fee);
+ }
+ }
+
+ /* go over all truths and add up cost */
+ {
+ unsigned int off = 0;
+ unsigned int len = 0;
+ struct AlreadySeen
+ {
+ uint32_t method;
+ const char *provider_url;
+ } *seen = NULL;
+ json_t *policies;
+ size_t pidx;
+ json_t *policy;
+
+ policies = json_object_get (state,
+ "policies");
+ json_array_foreach (policies, pidx, policy)
+ {
+ json_t *methods;
+ json_t *method;
+ size_t midx;
+
+ methods = json_object_get (policy,
+ "methods");
+ json_array_foreach (methods, midx, method)
+ {
+ const char *provider_url;
+ uint32_t method_idx;
+
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("provider",
+ &provider_url),
+ GNUNET_JSON_spec_uint32 ("authentication_method",
+ &method_idx),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (method,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ /* check if we have seen this one before */
+ {
+ bool found = false;
+
+ for (unsigned int i = 0; i<off; i++)
+ if ( (seen[i].method == method_idx) &&
+ (0 == strcmp (seen[i].provider_url,
+ provider_url)) )
+ found = true;
+ if (found)
+ continue; /* skip */
+ }
+ if (off == len)
+ {
+ GNUNET_array_grow (seen,
+ len,
+ 4 + len * 2);
+ }
+ seen[off].method = method_idx;
+ seen[off].provider_url = provider_url;
+ off++;
+ {
+ struct TALER_Amount upload_cost;
+ struct GNUNET_JSON_Specification pspec[] = {
+ TALER_JSON_spec_amount_any ("truth_upload_fee",
+ &upload_cost),
+ GNUNET_JSON_spec_end ()
+ };
+ struct TALER_Amount fee;
+ const json_t *provider_cfg
+ = json_object_get (providers,
+ provider_url);
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (provider_cfg,
+ pspec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (0 >
+ TALER_amount_multiply (&fee,
+ &upload_cost,
+ years))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ add_cost (&costs,
+ &fee);
+ }
+ }
+ }
+ GNUNET_array_grow (seen,
+ len,
+ 0);
+ }
+
+ /* convert 'costs' into state */
+ {
+ json_t *arr;
+
+ arr = json_array ();
+ GNUNET_assert (NULL != arr);
+ while (NULL != costs)
+ {
+ struct Costs *nxt = costs->next;
+
+ if ( (0 != costs->cost.value) ||
+ (0 != costs->cost.fraction) )
+ {
+ json_t *ao;
+
+ ao = json_pack ("{s:o}",
+ "fee",
+ TALER_JSON_from_amount (&costs->cost));
+ GNUNET_assert (0 ==
+ json_array_append_new (arr,
+ ao));
+ is_free = false;
+ }
+ GNUNET_free (costs);
+ costs = nxt;
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (state,
+ "upload_fees",
+ arr));
+ }
+
+ if (is_free)
+ expiration = GNUNET_TIME_relative_to_absolute (ANASTASIS_FREE_STORAGE);
+ /* update 'expiration' in state */
+ {
+ json_t *eo;
+
+ (void) GNUNET_TIME_round_abs (&expiration);
+ eo = GNUNET_JSON_from_time_abs (expiration);
+ GNUNET_assert (0 ==
+ json_object_set_new (state,
+ "expiration",
+ eo));
+ }
+
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "done_policy_review" action.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+static struct ANASTASIS_ReduxAction *
+done_policy_review (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ const json_t *policy_arr;
+
+ policy_arr = json_object_get (state,
+ "policies");
+ if (0 == json_array_size (policy_arr))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID_FOR_STATE,
+ "no policies specified");
+ return NULL;
+ }
+ {
+ struct GNUNET_TIME_Absolute exp = {0};
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_absolute_time ("expiration",
+ &exp)),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (state,
+ spec,
+ NULL, NULL))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID_FOR_STATE,
+ "invalid expiration specified");
+ return NULL;
+ }
+ if (0 == exp.abs_value_us)
+ exp = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_YEARS);
+ if (GNUNET_OK !=
+ update_expiration_cost (state,
+ exp))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID_FOR_STATE,
+ "could not calculate expiration cost");
+ return NULL;
+ }
+ }
+ set_state (state,
+ ANASTASIS_BACKUP_STATE_SECRET_EDITING);
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+/**
+ * Information we keep for an upload() operation.
+ */
+struct UploadContext;
+
+
+/**
+ * Maps a TruthUpload to a policy and recovery method where this
+ * truth is used.
+ */
+struct PolicyMethodReference
+{
+ /**
+ * Offset into the "policies" array.
+ */
+ unsigned int policy_index;
+
+ /**
+ * Offset into the "methods" array (of the policy selected
+ * by @e policy_index).
+ */
+ unsigned int method_index;
+
+};
+
+
+/**
+ * Entry we keep per truth upload.
+ */
+struct TruthUpload
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct TruthUpload *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct TruthUpload *prev;
+
+ /**
+ * Handle to the actual upload operation.
+ */
+ struct ANASTASIS_TruthUpload *tu;
+
+ /**
+ * Upload context this operation is part of.
+ */
+ struct UploadContext *uc;
+
+ /**
+ * Truth resulting from the upload, if any.
+ */
+ struct ANASTASIS_Truth *t;
+
+ /**
+ * A taler://pay/-URI with a request to pay the annual fee for
+ * the service. Set if payment is required.
+ */
+ char *payment_request;
+
+ /**
+ * Which policies and methods does this truth affect?
+ */
+ struct PolicyMethodReference *policies;
+
+ /**
+ * Where are we uploading to?
+ */
+ char *provider_url;
+
+ /**
+ * Which challenge object are we uploading?
+ */
+ uint32_t am_idx;
+
+ /**
+ * Length of the @e policies array.
+ */
+ unsigned int policies_length;
+
+ /**
+ * Status of the upload.
+ */
+ enum ANASTASIS_UploadStatus us;
+
+ /**
+ * Taler error code of the upload.
+ */
+ enum TALER_ErrorCode ec;
+
+};
+
+
+/**
+ * Information we keep for an upload() operation.
+ */
+struct UploadContext
+{
+ /**
+ * Recovery action returned to caller for aborting the operation.
+ */
+ struct ANASTASIS_ReduxAction ra;
+
+ /**
+ * Function to call upon completion.
+ */
+ ANASTASIS_ActionCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Our state.
+ */
+ json_t *state;
+
+ /**
+ * Master secret sharing operation, NULL if not yet running.
+ */
+ struct ANASTASIS_SecretShare *ss;
+
+ /**
+ * Head of DLL of truth uploads.
+ */
+ struct TruthUpload *tues_head;
+
+ /**
+ * Tail of DLL of truth uploads.
+ */
+ struct TruthUpload *tues_tail;
+
+ /**
+ * Timeout to use for the operation, from the arguments.
+ */
+ struct GNUNET_TIME_Relative timeout;
+
+ /**
+ * For how many years should we pay?
+ */
+ unsigned int years;
+
+};
+
+
+/**
+ * Function called when the #upload transition is being aborted.
+ *
+ * @param cls a `struct UploadContext`
+ */
+static void
+upload_cancel_cb (void *cls)
+{
+ struct UploadContext *uc = cls;
+ struct TruthUpload *tue;
+
+ while (NULL != (tue = uc->tues_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (uc->tues_head,
+ uc->tues_tail,
+ tue);
+ if (NULL != tue->tu)
+ {
+ ANASTASIS_truth_upload_cancel (tue->tu);
+ tue->tu = NULL;
+ }
+ if (NULL != tue->t)
+ {
+ ANASTASIS_truth_free (tue->t);
+ tue->t = NULL;
+ }
+ GNUNET_free (tue->provider_url);
+ GNUNET_free (tue->payment_request);
+ GNUNET_free (tue->policies);
+ GNUNET_free (tue);
+ }
+ if (NULL != uc->ss)
+ {
+ ANASTASIS_secret_share_cancel (uc->ss);
+ uc->ss = NULL;
+ }
+ json_decref (uc->state);
+ GNUNET_free (uc);
+}
+
+
+/**
+ * Take all of the ongoing truth uploads and serialize them into the @a uc
+ * state.
+ *
+ * @param[in,out] uc context to take truth uploads from and to update state of
+ */
+static void
+serialize_truth (struct UploadContext *uc)
+{
+ json_t *policies;
+
+ policies = json_object_get (uc->state,
+ "policies");
+ GNUNET_assert (json_is_array (policies));
+ for (struct TruthUpload *tue = uc->tues_head;
+ NULL != tue;
+ tue = tue->next)
+ {
+ if (NULL == tue->t)
+ continue;
+ for (unsigned int i = 0; i<tue->policies_length; i++)
+ {
+ const struct PolicyMethodReference *pmr = &tue->policies[i];
+ json_t *policy = json_array_get (policies,
+ pmr->policy_index);
+ json_t *methods = json_object_get (policy,
+ "methods");
+ json_t *auth_method = json_array_get (methods,
+ pmr->method_index);
+ json_t *truth = ANASTASIS_truth_to_json (tue->t);
+
+ GNUNET_assert (0 ==
+ json_object_set_new (truth,
+ "upload_status",
+ json_integer (tue->us)));
+ GNUNET_assert (NULL != policy);
+ GNUNET_assert (NULL != methods);
+ GNUNET_assert (NULL != auth_method);
+ GNUNET_assert (NULL != truth);
+ GNUNET_assert (0 ==
+ json_object_set_new (auth_method,
+ "truth",
+ truth));
+ }
+ }
+}
+
+
+/**
+ * Function called with the results of a #ANASTASIS_secret_share().
+ *
+ * @param cls closure with a `struct UploadContext *`
+ * @param sr share result
+ */
+static void
+secret_share_result_cb (void *cls,
+ const struct ANASTASIS_ShareResult *sr)
+{
+ struct UploadContext *uc = cls;
+
+ uc->ss = NULL;
+ switch (sr->ss)
+ {
+ case ANASTASIS_SHARE_STATUS_SUCCESS:
+ /* Just to be safe, delete the "core_secret" so that it is not
+ accidentally preserved anywhere */
+ (void) json_object_del (uc->state,
+ "core_secret");
+ {
+ json_t *sa = json_object ();
+
+ GNUNET_assert (NULL != sa);
+ for (unsigned int i = 0; i<sr->details.success.num_providers; i++)
+ {
+ const struct ANASTASIS_ProviderSuccessStatus *pssi
+ = &sr->details.success.pss[i];
+ json_t *d;
+
+ d = json_pack ("{s:I, s:o}",
+ "policy_version",
+ pssi->policy_version,
+ "policy_expiration",
+ GNUNET_JSON_from_time_abs (pssi->policy_expiration));
+ GNUNET_assert (NULL != d);
+ GNUNET_assert (0 ==
+ json_object_set_new (sa,
+ pssi->provider_url,
+ d));
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (uc->state,
+ "success_details",
+ sa));
+ }
+ set_state (uc->state,
+ ANASTASIS_BACKUP_STATE_BACKUP_FINISHED);
+ uc->cb (uc->cb_cls,
+ TALER_EC_NONE,
+ uc->state);
+ break;
+ case ANASTASIS_SHARE_STATUS_PAYMENT_REQUIRED:
+ {
+ json_t *ra;
+ json_t *providers;
+
+ providers = json_object_get (uc->state,
+ "policy_providers");
+ set_state (uc->state,
+ ANASTASIS_BACKUP_STATE_POLICIES_PAYING);
+ serialize_truth (uc);
+ ra = json_array ();
+ GNUNET_assert (NULL != ra);
+ for (unsigned int i = 0; i<
+ sr->details.payment_required.payment_requests_length; i++)
+ {
+ const struct ANASTASIS_SharePaymentRequest *spr;
+ json_t *pr;
+ size_t off;
+ json_t *provider;
+
+ spr = &sr->details.payment_required.payment_requests[i];
+ pr = json_pack ("{s:s, s:s}",
+ "payto",
+ spr->payment_request_url,
+ "provider",
+ spr->provider_url);
+ GNUNET_assert (0 ==
+ json_array_append_new (ra,
+ pr));
+ json_array_foreach (providers, off, provider)
+ {
+ const char *purl = json_string_value (json_object_get (provider,
+ "provider_url"));
+
+ if (NULL == purl)
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (uc->cb,
+ uc->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "policy_providers array contents are invalid");
+ json_decref (ra);
+ return;
+ }
+ if (0 == strcmp (purl,
+ spr->provider_url))
+ {
+ json_t *psj;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Remembering payment secret for provider `%s'\n",
+ spr->provider_url);
+ psj = GNUNET_JSON_from_data_auto (&spr->payment_secret);
+ GNUNET_assert (0 ==
+ json_object_set_new (provider,
+ "payment_secret",
+ psj));
+ }
+ }
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (uc->state,
+ "policy_payment_requests",
+ ra));
+ }
+ uc->cb (uc->cb_cls,
+ TALER_EC_NONE,
+ uc->state);
+ break;
+ case ANASTASIS_SHARE_STATUS_PROVIDER_FAILED:
+ {
+ json_t *details;
+
+ details = json_pack ("{s:s, s:I, s:I, s:s}",
+ "backup_state",
+ "ERROR",
+ "http_status",
+ (json_int_t) sr->details.provider_failure.http_status,
+ "upload_status",
+ (json_int_t) sr->details.provider_failure.ec,
+ "provider_url",
+ sr->details.provider_failure.provider_url);
+ uc->cb (uc->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_BACKUP_PROVIDER_FAILED,
+ details);
+ json_decref (details);
+ }
+ break;
+ default:
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (uc->cb,
+ uc->cb_cls,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "unexpected share result");
+ break;
+ }
+ upload_cancel_cb (uc);
+}
+
+
+/**
+ * All truth uploads are done, begin with uploading the policy.
+ *
+ * @param[in,out] uc context for the operation
+ */
+static void
+share_secret (struct UploadContext *uc)
+{
+ json_t *user_id;
+ json_t *core_secret;
+ json_t *jpolicies;
+ json_t *providers = NULL;
+ size_t policies_len;
+ const char *secret_name = NULL;
+ unsigned int pds_len;
+ struct GNUNET_TIME_Relative timeout = GNUNET_TIME_UNIT_ZERO;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_json ("identity_attributes",
+ &user_id),
+ GNUNET_JSON_spec_json ("policies",
+ &jpolicies),
+ GNUNET_JSON_spec_json ("policy_providers",
+ &providers),
+ GNUNET_JSON_spec_json ("core_secret",
+ &core_secret),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("secret_name",
+ &secret_name)),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (uc->state,
+ spec,
+ NULL, NULL))
+ {
+ ANASTASIS_redux_fail_ (uc->cb,
+ uc->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "State parsing failed when preparing to share secret");
+ upload_cancel_cb (uc);
+ return;
+ }
+
+ {
+ json_t *args;
+ struct GNUNET_JSON_Specification pspec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_relative_time ("timeout",
+ &timeout)),
+ GNUNET_JSON_spec_end ()
+ };
+
+ args = json_object_get (uc->state,
+ "pay-arguments");
+ if ( (NULL != args) &&
+ (GNUNET_OK !=
+ GNUNET_JSON_parse (args,
+ pspec,
+ NULL, NULL)) )
+ {
+ json_dumpf (args,
+ stderr,
+ JSON_INDENT (2));
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (uc->cb,
+ uc->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ NULL);
+ upload_cancel_cb (uc);
+ return;
+ }
+ }
+
+ if ( (! json_is_object (user_id)) ||
+ (! json_is_array (jpolicies)) ||
+ (0 == json_array_size (jpolicies)) ||
+ ( (NULL != providers) &&
+ (! json_is_array (providers)) ) )
+ {
+ ANASTASIS_redux_fail_ (uc->cb,
+ uc->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "State parsing failed checks when preparing to share secret");
+ GNUNET_JSON_parse_free (spec);
+ upload_cancel_cb (uc);
+ return;
+ }
+
+ policies_len = json_array_size (jpolicies);
+ pds_len = json_array_size (providers);
+
+ if (0 == pds_len)
+ {
+ ANASTASIS_redux_fail_ (uc->cb,
+ uc->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "no workable providers in state");
+ GNUNET_JSON_parse_free (spec);
+ upload_cancel_cb (uc);
+ return;
+ }
+
+
+ {
+ struct ANASTASIS_Policy *vpolicies[policies_len];
+ const struct ANASTASIS_Policy *policies[policies_len];
+ struct ANASTASIS_ProviderDetails pds[GNUNET_NZL (pds_len)];
+
+ /* initialize policies/vpolicies arrays */
+ memset (pds,
+ 0,
+ sizeof (pds));
+ for (size_t i = 0; i<policies_len; i++)
+ {
+ const json_t *policy = json_array_get (jpolicies,
+ i);
+ const json_t *jmethods = json_object_get (policy,
+ "methods");
+ unsigned int methods_len;
+
+ if ( (! json_is_array (jmethods)) ||
+ (0 == json_array_size (jmethods)) )
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (uc->cb,
+ uc->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'methods' must be an array");
+ GNUNET_JSON_parse_free (spec);
+ upload_cancel_cb (uc);
+ return;
+ }
+ methods_len = json_array_size (jmethods);
+ {
+ struct ANASTASIS_Policy *p;
+ struct ANASTASIS_Truth *truths[methods_len];
+ const struct ANASTASIS_Truth *ctruths[methods_len];
+
+ for (unsigned int j = 0; j<methods_len; j++)
+ {
+ const json_t *jmethod = json_array_get (jmethods,
+ j);
+ json_t *jtruth = NULL;
+ uint32_t truth_index;
+ const char *provider_url;
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_json ("truth",
+ &jtruth)),
+ GNUNET_JSON_spec_string ("provider",
+ &provider_url),
+ GNUNET_JSON_spec_uint32 ("authentication_method",
+ &truth_index),
+ GNUNET_JSON_spec_end ()
+ };
+
+ GNUNET_break (NULL != jmethod);
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (jmethod,
+ ispec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ for (unsigned int k = 0; k<j; k++)
+ ANASTASIS_truth_free (truths[k]);
+ ANASTASIS_redux_fail_ (uc->cb,
+ uc->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'truth' failed to decode");
+ GNUNET_JSON_parse_free (spec);
+ upload_cancel_cb (uc);
+ return;
+ }
+ if (NULL != jtruth)
+ {
+ /* Get truth by deserializing from state */
+ truths[j] = ANASTASIS_truth_from_json (jtruth);
+ if (NULL == truths[j])
+ {
+ GNUNET_break (0);
+ for (unsigned int k = 0; k<j; k++)
+ ANASTASIS_truth_free (truths[k]);
+ ANASTASIS_redux_fail_ (uc->cb,
+ uc->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'truth' failed to decode");
+ GNUNET_JSON_parse_free (ispec);
+ GNUNET_JSON_parse_free (spec);
+ upload_cancel_cb (uc);
+ return;
+ }
+ }
+ else
+ {
+ bool found = false;
+ /* Maybe we never serialized the truth; find it in our DLL */
+ for (struct TruthUpload *tue = uc->tues_head;
+ NULL != tue;
+ tue = tue->next)
+ {
+ GNUNET_break (NULL != tue->t);
+ if ( (tue->am_idx == truth_index) &&
+ (0 == strcmp (provider_url,
+ tue->provider_url)) )
+ {
+ /* Duplicate truth object */
+ json_t *jt = ANASTASIS_truth_to_json (tue->t);
+
+ GNUNET_assert (NULL != jt);
+ truths[j] = ANASTASIS_truth_from_json (jt);
+ GNUNET_assert (NULL != truths[j]);
+ json_decref (jt);
+ found = true;
+ break;
+ }
+ }
+ if (! found)
+ {
+ GNUNET_break (0);
+ for (unsigned int k = 0; k<j; k++)
+ ANASTASIS_truth_free (truths[k]);
+ ANASTASIS_redux_fail_ (uc->cb,
+ uc->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'truth' failed to decode");
+ GNUNET_JSON_parse_free (ispec);
+ GNUNET_JSON_parse_free (spec);
+ upload_cancel_cb (uc);
+ return;
+ }
+ }
+ GNUNET_JSON_parse_free (ispec);
+ ctruths[j] = truths[j];
+ }
+ p = ANASTASIS_policy_create (ctruths,
+ methods_len);
+ vpolicies[i] = p;
+ policies[i] = p;
+ for (unsigned int k = 0; k<methods_len; k++)
+ ANASTASIS_truth_free (truths[k]);
+ }
+ }
+
+ /* initialize 'pds' array */
+ for (unsigned int i = 0; i<pds_len; i++)
+ {
+ json_t *pdj = json_array_get (providers,
+ i);
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("payment_secret",
+ &pds[i].payment_secret)),
+ GNUNET_JSON_spec_string ("provider_url",
+ &pds[i].provider_url),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if ( (GNUNET_OK !=
+ GNUNET_JSON_parse (pdj,
+ ispec,
+ NULL, NULL)) ||
+ (GNUNET_OK !=
+ lookup_salt (uc->state,
+ pds[i].provider_url,
+ &pds[i].provider_salt)) )
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (uc->cb,
+ uc->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'providers' entry malformed");
+ for (unsigned int i = 0; i<policies_len; i++)
+ ANASTASIS_policy_destroy (vpolicies[i]);
+ upload_cancel_cb (uc);
+ GNUNET_JSON_parse_free (spec);
+ return;
+ }
+ }
+
+ {
+ char *secret;
+ size_t secret_size;
+
+ secret = json_dumps (core_secret,
+ JSON_COMPACT | JSON_SORT_KEYS);
+ GNUNET_assert (NULL != secret);
+ secret_size = strlen (secret);
+ uc->ss = ANASTASIS_secret_share (ANASTASIS_REDUX_ctx_,
+ user_id,
+ pds,
+ pds_len,
+ policies,
+ policies_len,
+ uc->years,
+ timeout,
+ &secret_share_result_cb,
+ uc,
+ secret_name,
+ secret,
+ secret_size);
+ GNUNET_free (secret);
+ }
+ for (unsigned int i = 0; i<policies_len; i++)
+ ANASTASIS_policy_destroy (vpolicies[i]);
+ }
+ GNUNET_JSON_parse_free (spec);
+ if (NULL == uc->ss)
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (uc->cb,
+ uc->cb_cls,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "Failed to begin secret sharing");
+ upload_cancel_cb (uc);
+ return;
+ }
+}
+
+
+/**
+ * Some truth uploads require payment, serialize state and
+ * request payment to be executed by the application.
+ *
+ * @param[in,out] uc context for the operation
+ */
+static void
+request_truth_payment (struct UploadContext *uc)
+{
+ json_t *payments;
+
+ payments = json_array ();
+ GNUNET_assert (NULL != payments);
+ serialize_truth (uc);
+ for (struct TruthUpload *tue = uc->tues_head;
+ NULL != tue;
+ tue = tue->next)
+ {
+ if (NULL == tue->payment_request)
+ continue;
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (payments,
+ json_string (
+ tue->payment_request)));
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (uc->state,
+ "payments",
+ payments));
+ set_state (uc->state,
+ ANASTASIS_BACKUP_STATE_TRUTHS_PAYING);
+ uc->cb (uc->cb_cls,
+ TALER_EC_NONE,
+ uc->state);
+ upload_cancel_cb (uc);
+}
+
+
+/**
+ * We may be finished with all (active) asynchronous operations.
+ * Check if any are pending and continue accordingly.
+ *
+ * @param[in,out] uc context for the operation
+ */
+static void
+check_upload_finished (struct UploadContext *uc)
+{
+ bool pay = false;
+ bool active = false;
+
+ for (struct TruthUpload *tue = uc->tues_head;
+ NULL != tue;
+ tue = tue->next)
+ {
+ if (TALER_EC_NONE != tue->ec)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Truth upload failed with error %d\n",
+ (int) tue->ec);
+ uc->cb (uc->cb_cls,
+ tue->ec,
+ NULL);
+ upload_cancel_cb (uc);
+ return;
+ }
+ if (NULL != tue->tu)
+ active = true;
+ if (NULL != tue->payment_request)
+ pay = true;
+ }
+ if (active)
+ return;
+ if (pay)
+ {
+ request_truth_payment (uc);
+ return;
+ }
+ share_secret (uc);
+}
+
+
+/**
+ * Upload result information. The resulting truth object can be used
+ * to create policies. If payment is required, the @a taler_pay_url
+ * is returned and the operation must be retried after payment.
+ * Callee MUST free @a t using ANASTASIS_truth_free().
+ *
+ * @param cls closure with a `struct TruthUpload`
+ * @param t truth object to create policies, NULL on failure
+ * @param ud upload details
+ */
+static void
+truth_upload_cb (void *cls,
+ struct ANASTASIS_Truth *t,
+ const struct ANASTASIS_UploadDetails *ud)
+{
+ struct TruthUpload *tue = cls;
+
+ tue->tu = NULL;
+ tue->t = t;
+ tue->ec = ud->ec;
+ tue->us = ud->us;
+ if (ANASTASIS_US_PAYMENT_REQUIRED == ud->us)
+ {
+ tue->payment_request = GNUNET_strdup (
+ ud->details.payment.payment_request);
+ }
+ check_upload_finished (tue->uc);
+}
+
+
+/**
+ * Check if we still need to create a new truth object for the truth
+ * identified by @a provider_url and @a am_idx. If so, create it from
+ * @a truth for policy reference @a pmr. If such a truth object
+ * already exists, append @a pmr to its list of reasons.
+ *
+ * @param[in,out] our upload context
+ * @param pmr policy method combination that requires the truth
+ * @param provider_url the URL of the Anastasis provider to upload
+ * the truth to, used to check for existing entries
+ * @param am_idx index of the authentication method, used to check for existing entries
+ * @param[in] truth object representing already uploaded truth, reference captured!
+ * @param[in,out] async_truth pointer to counter with the number of ongoing uploads,
+ * updated
+ * @param auth_method object with the challenge details, to generate the truth
+ * @return #GNUNET_SYSERR error requiring abort,
+ * #GNUNET_OK on success
+ */
+static int
+add_truth_object (struct UploadContext *uc,
+ const struct PolicyMethodReference *pmr,
+ const char *provider_url,
+ uint32_t am_idx,
+ json_t *truth,
+ unsigned int *async_truth,
+ json_t *auth_method)
+{
+ /* check if we are already uploading this truth */
+ struct TruthUpload *tue;
+ bool must_upload = true;
+
+ for (tue = uc->tues_head;
+ NULL != tue;
+ tue = tue->next)
+ {
+ if ( (0 == strcmp (tue->provider_url,
+ provider_url)) &&
+ (am_idx == tue->am_idx) )
+ {
+ GNUNET_array_append (tue->policies,
+ tue->policies_length,
+ *pmr);
+ break;
+ }
+ }
+
+ if (NULL == tue)
+ {
+ /* Create new entry */
+ tue = GNUNET_new (struct TruthUpload);
+
+ GNUNET_CONTAINER_DLL_insert (uc->tues_head,
+ uc->tues_tail,
+ tue);
+ tue->uc = uc;
+ tue->policies = GNUNET_new (struct PolicyMethodReference);
+ *tue->policies = *pmr;
+ tue->provider_url = GNUNET_strdup (provider_url);
+ tue->am_idx = am_idx;
+ tue->policies_length = 1;
+ }
+
+ {
+ uint32_t status = UINT32_MAX;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint32 ("upload_status",
+ &status)),
+ GNUNET_JSON_spec_end ()
+ };
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (truth,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ must_upload = (ANASTASIS_US_SUCCESS != status);
+ }
+
+ if (NULL == tue->t)
+ {
+ tue->t = ANASTASIS_truth_from_json (truth);
+ if (NULL == tue->t)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ }
+
+ if ( (NULL != tue->tu) &&
+ (! must_upload) )
+ {
+ ANASTASIS_truth_upload_cancel (tue->tu);
+ (*async_truth)--;
+ tue->tu = NULL;
+ return GNUNET_OK;
+ }
+
+ if ( (NULL == tue->tu) &&
+ (must_upload) )
+ {
+ struct ANASTASIS_CRYPTO_ProviderSaltP salt;
+ struct ANASTASIS_CRYPTO_UserIdentifierP id;
+ void *truth_data;
+ size_t truth_data_size;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_varsize ("challenge",
+ &truth_data,
+ &truth_data_size),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ lookup_salt (uc->state,
+ provider_url,
+ &salt))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (auth_method,
+ spec,
+ NULL, NULL))
+ {
+ json_dumpf (auth_method,
+ stderr,
+ JSON_INDENT (2));
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ {
+ json_t *user_id;
+
+ user_id = json_object_get (uc->state,
+ "identity_attributes");
+ if (! json_is_object (user_id))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ ANASTASIS_CRYPTO_user_identifier_derive (user_id,
+ &salt,
+ &id);
+ }
+ tue->tu = ANASTASIS_truth_upload3 (ANASTASIS_REDUX_ctx_,
+ &id,
+ tue->t,
+ truth_data,
+ truth_data_size,
+ uc->years,
+ uc->timeout,
+ &truth_upload_cb,
+ tue);
+ GNUNET_JSON_parse_free (spec);
+ tue->t = NULL;
+ (*async_truth)++;
+ }
+
+ if ( (NULL != tue->tu) &&
+ (NULL != tue->t) )
+ {
+ /* no point in having both */
+ ANASTASIS_truth_free (tue->t);
+ tue->t = NULL;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Check if we still need to upload the truth identified by
+ * @a provider_url and @a am_idx. If so, upload it for
+ * policy reference @a pmr. If the upload is already queued,
+ * append @a pmr to its list of reasons.
+ *
+ * @param[in,out] our upload context
+ * @param pmr policy method combination that requires the truth
+ * @param provider_url the URL of the Anastasis provider to upload
+ * the truth to, used to check for existing entries
+ * @param am_idx index of the authentication method, used to check for existing entries
+ * @param auth_method object with the challenge details, to generate the truth
+ * @return #GNUNET_SYSERR on error requiring abort,
+ * #GNUNET_NO if no new truth upload was generated (@a pmr was appended)
+ * #GNUNET_OK if a new truth upload was initiated
+ */
+static int
+check_truth_upload (struct UploadContext *uc,
+ const struct PolicyMethodReference *pmr,
+ const char *provider_url,
+ uint32_t am_idx,
+ json_t *auth_method)
+{
+ json_t *user_id;
+ json_t *jtruth;
+ struct TruthUpload *tue;
+
+ user_id = json_object_get (uc->state,
+ "identity_attributes");
+ if (! json_is_object (user_id))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ /* check if we are already uploading this truth */
+ for (tue = uc->tues_head;
+ NULL != tue;
+ tue = tue->next)
+ {
+ if ( (0 == strcmp (tue->provider_url,
+ provider_url)) &&
+ (am_idx == tue->am_idx) )
+ {
+ GNUNET_array_append (tue->policies,
+ tue->policies_length,
+ *pmr);
+ return GNUNET_NO;
+ }
+ }
+
+ /* need new upload */
+ tue = GNUNET_new (struct TruthUpload);
+ {
+ json_t *policies = json_object_get (uc->state,
+ "policies");
+ json_t *policy = json_array_get (policies,
+ pmr->policy_index);
+ json_t *methods = json_object_get (policy,
+ "methods");
+ json_t *method = json_array_get (methods,
+ pmr->method_index);
+
+ jtruth = json_object_get (method,
+ "truth");
+ }
+
+ {
+ const char *type;
+ const char *mime_type = NULL;
+ const char *instructions = NULL;
+ void *truth_data;
+ size_t truth_data_size;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("type",
+ &type),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("mime_type",
+ &mime_type)),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("instructions",
+ &instructions)),
+ GNUNET_JSON_spec_varsize ("challenge",
+ &truth_data,
+ &truth_data_size),
+ GNUNET_JSON_spec_end ()
+ };
+ struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt;
+ struct ANASTASIS_CRYPTO_UserIdentifierP id;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (auth_method,
+ spec,
+ NULL, NULL))
+ {
+ json_dumpf (auth_method,
+ stderr,
+ JSON_INDENT (2));
+ GNUNET_break (0);
+ GNUNET_free (tue);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_CONTAINER_DLL_insert (uc->tues_head,
+ uc->tues_tail,
+ tue);
+ tue->uc = uc;
+ tue->policies = GNUNET_new (struct PolicyMethodReference);
+ *tue->policies = *pmr;
+ tue->provider_url = GNUNET_strdup (provider_url);
+ tue->am_idx = am_idx;
+ tue->policies_length = 1;
+ if (GNUNET_OK !=
+ lookup_salt (uc->state,
+ provider_url,
+ &provider_salt))
+ {
+ GNUNET_break (0);
+ GNUNET_JSON_parse_free (spec);
+ upload_cancel_cb (uc);
+ return GNUNET_SYSERR;
+ }
+ ANASTASIS_CRYPTO_user_identifier_derive (user_id,
+ &provider_salt,
+ &id);
+ {
+ struct ANASTASIS_CRYPTO_TruthUUIDP uuid;
+ struct ANASTASIS_CRYPTO_QuestionSaltP question_salt;
+ struct ANASTASIS_CRYPTO_TruthKeyP truth_key;
+ struct ANASTASIS_CRYPTO_KeyShareP key_share;
+ struct ANASTASIS_CRYPTO_NonceP nonce;
+
+ struct GNUNET_JSON_Specification jspec[] = {
+ GNUNET_JSON_spec_fixed_auto ("salt",
+ &question_salt),
+ GNUNET_JSON_spec_fixed_auto ("truth_key",
+ &truth_key),
+ GNUNET_JSON_spec_fixed_auto ("nonce",
+ &nonce),
+ GNUNET_JSON_spec_fixed_auto ("uuid",
+ &uuid),
+ GNUNET_JSON_spec_fixed_auto ("key_share",
+ &key_share),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (jtruth,
+ jspec,
+ NULL, NULL))
+ {
+ tue->tu = ANASTASIS_truth_upload (ANASTASIS_REDUX_ctx_,
+ &id,
+ provider_url,
+ type,
+ instructions,
+ mime_type,
+ &provider_salt,
+ truth_data,
+ truth_data_size,
+ uc->years,
+ uc->timeout,
+ &truth_upload_cb,
+ tue);
+ }
+ else
+ {
+ tue->tu = ANASTASIS_truth_upload2 (ANASTASIS_REDUX_ctx_,
+ &id,
+ provider_url,
+ type,
+ instructions,
+ mime_type,
+ &provider_salt,
+ truth_data,
+ truth_data_size,
+ uc->years,
+ uc->timeout,
+ &nonce,
+ &uuid,
+ &question_salt,
+ &truth_key,
+ &key_share,
+ &truth_upload_cb,
+ tue);
+ }
+ }
+ if (NULL == tue->tu)
+ {
+ GNUNET_break (0);
+ GNUNET_JSON_parse_free (spec);
+ upload_cancel_cb (uc);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_OK;
+ }
+}
+
+
+/**
+ * Function to upload truths and recovery document policies.
+ * Ultimately transitions to failed state (allowing user to go back
+ * and change providers/policies), or payment, or finished.
+ *
+ * @param state state to operate on
+ * @param truth_indices indices of truths to upload explicitly
+ * @param cb callback (#ANASTASIS_ActionCallback) to call after upload
+ * @param cb_cls callback closure
+ */
+static struct ANASTASIS_ReduxAction *
+upload (json_t *state,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ struct UploadContext *uc;
+ json_t *auth_methods;
+ json_t *policies;
+ struct GNUNET_TIME_Absolute expiration;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_absolute_time ("expiration",
+ &expiration),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (state,
+ spec,
+ NULL, NULL))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'expiration' missing");
+ return NULL;
+ }
+ auth_methods = json_object_get (state,
+ "authentication_methods");
+ if ( (! json_is_array (auth_methods)) ||
+ (0 == json_array_size (auth_methods)) )
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'authentication_methods' must be non-empty array");
+ return NULL;
+ }
+ policies = json_object_get (state,
+ "policies");
+ if ( (! json_is_array (policies)) ||
+ (0 == json_array_size (policies)) )
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'policies' must be non-empty array");
+ return NULL;
+ }
+
+ uc = GNUNET_new (struct UploadContext);
+ uc->ra.cleanup = &upload_cancel_cb;
+ uc->ra.cleanup_cls = uc;
+ uc->cb = cb;
+ uc->cb_cls = cb_cls;
+ uc->state = json_incref (state);
+ uc->years = expiration_to_years (expiration);
+
+ {
+ json_t *args;
+ struct GNUNET_JSON_Specification pspec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_relative_time ("timeout",
+ &uc->timeout)),
+ GNUNET_JSON_spec_end ()
+ };
+
+ args = json_object_get (uc->state,
+ "pay-arguments");
+ if ( (NULL != args) &&
+ (GNUNET_OK !=
+ GNUNET_JSON_parse (args,
+ pspec,
+ NULL, NULL)) )
+ {
+ json_dumpf (args,
+ stderr,
+ JSON_INDENT (2));
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'timeout' must be valid delay");
+
+ return NULL;
+ }
+ }
+
+ {
+ json_t *policy;
+ size_t pindex;
+ unsigned int async_truth = 0;
+
+ json_array_foreach (policies, pindex, policy)
+ {
+ json_t *methods = json_object_get (policy,
+ "methods");
+ json_t *auth_method;
+ size_t mindex;
+
+ if ( (! json_is_array (methods)) ||
+ (0 == json_array_size (policies)) )
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'policies' must be non-empty array");
+ upload_cancel_cb (uc);
+ return NULL;
+ }
+ json_array_foreach (methods, mindex, auth_method)
+ {
+ uint32_t am_idx;
+ const char *provider_url;
+ json_t *truth = NULL;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("provider",
+ &provider_url),
+ GNUNET_JSON_spec_uint32 ("authentication_method",
+ &am_idx),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_json ("truth",
+ &truth)),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (auth_method,
+ spec,
+ NULL, NULL))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'method' data malformed");
+ upload_cancel_cb (uc);
+ return NULL;
+ }
+ {
+ struct PolicyMethodReference pmr = {
+ .policy_index = pindex,
+ .method_index = mindex
+ };
+ json_t *amj;
+
+ amj = json_array_get (auth_methods,
+ am_idx);
+ if (NULL == amj)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'authentication_method' refers to invalid authorization index malformed");
+ upload_cancel_cb (uc);
+ GNUNET_JSON_parse_free (spec);
+ return NULL;
+ }
+ if (NULL == truth)
+ {
+ int ret;
+
+ ret = check_truth_upload (uc,
+ &pmr,
+ provider_url,
+ am_idx,
+ amj);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_JSON_parse_free (spec);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ NULL);
+ return NULL;
+ }
+ if (GNUNET_OK == ret)
+ async_truth++;
+ }
+ else
+ {
+ int ret;
+
+ ret = add_truth_object (uc,
+ &pmr,
+ provider_url,
+ am_idx,
+ truth,
+ &async_truth,
+ amj);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_JSON_parse_free (spec);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ NULL);
+ return NULL;
+ }
+ }
+ }
+ GNUNET_JSON_parse_free (spec);
+ } /* end for all methods of policy */
+ } /* end for all policies */
+ if (async_truth > 0)
+ return &uc->ra;
+ }
+ share_secret (uc);
+ if (NULL == uc->ss)
+ return NULL;
+ return &uc->ra;
+}
+
+
+/**
+ * Test if the core secret @a secret_size is small enough to be stored
+ * at all providers, which have a minimum upload limit of @a min_limit_in_mb.
+ *
+ * For now, we do not precisely calculate the size of the recovery document,
+ * and simply assume that the instructions (i.e. security questions) are all
+ * relatively small (aka sane), and that the number of authentication methods
+ * and recovery policies is similarly small so that all of this meta data
+ * fits in 512 kb (which is VERY big).
+ *
+ * Even with the minimum permitted upload limit of 1 MB (which is likely,
+ * given that there is hardly a reason for providers to offer more), this
+ * leaves 512 kb for the @a secret_size, which should be plenty (given
+ * that this is supposed to be for a master key, and not the actual data).
+ *
+ * @param state our state, could be used in the future to calculate the
+ * size of the recovery document without the core secret
+ * @param secret_size size of the core secret
+ * @param min_limit_in_mb minimum upload size of all providers
+ */
+static bool
+core_secret_fits (const json_t *state,
+ size_t secret_size,
+ uint32_t min_limit_in_mb)
+{
+ return (min_limit_in_mb * 1024LL * 1024LL >
+ 512LLU * 1024LLU + secret_size);
+}
+
+
+/**
+ * Check if the upload size limit is satisfied.
+ *
+ * @param state our state
+ * @param jsecret the uploaded secret
+ * @return #GNUNET_OK if @a secret_size works for all providers,
+ * #GNUNET_NO if the @a secret_size is too big,
+ * #GNUNET_SYSERR if a provider has a limit of 0
+ */
+static enum GNUNET_GenericReturnValue
+check_upload_size_limit (json_t *state,
+ const json_t *jsecret)
+{
+ uint32_t min_limit = UINT32_MAX;
+ json_t *aps = json_object_get (state,
+ "authentication_providers");
+ const char *url;
+ json_t *ap;
+ size_t secret_size;
+
+ {
+ char *secret;
+
+ secret = json_dumps (jsecret,
+ JSON_COMPACT | JSON_SORT_KEYS);
+ GNUNET_assert (NULL != secret);
+ secret_size = strlen (secret);
+ GNUNET_free (secret);
+ }
+
+ /* We calculate the minimum upload limit of all possible providers;
+ this is under the (simplified) assumption that we store the
+ recovery document at all providers; this may be changed later,
+ see #6760. */
+ json_object_foreach (aps, url, ap)
+ {
+ uint32_t limit;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_uint32 ("storage_limit_in_megabytes",
+ &limit),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (ap,
+ spec,
+ NULL, NULL))
+ {
+ /* skip malformed provider, likely /config failed */
+ continue;
+ }
+ if (0 == limit)
+ return GNUNET_SYSERR;
+ min_limit = GNUNET_MIN (min_limit,
+ limit);
+ }
+ if (! core_secret_fits (state,
+ secret_size,
+ min_limit))
+ return GNUNET_NO;
+ return GNUNET_OK;
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "enter_secret" action.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+static struct ANASTASIS_ReduxAction *
+enter_secret (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ json_t *jsecret;
+ struct GNUNET_TIME_Absolute expiration = {0};
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_json ("secret",
+ &jsecret),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_absolute_time ("expiration",
+ &expiration)),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (NULL == arguments)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "arguments missing");
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (arguments,
+ spec,
+ NULL, NULL))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'secret' argument required");
+ return NULL;
+ }
+
+ /* check upload size limit */
+ {
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = check_upload_size_limit (state,
+ jsecret);
+ switch (ret)
+ {
+ case GNUNET_SYSERR:
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "provider has an upload limit of 0");
+ return NULL;
+ case GNUNET_NO:
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_SECRET_TOO_BIG,
+ NULL);
+ return NULL;
+ default:
+ break;
+ }
+ }
+ if (0 != expiration.abs_value_us)
+ {
+ if (GNUNET_OK !=
+ update_expiration_cost (state,
+ expiration))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID_FOR_STATE,
+ "could not calculate expiration cost");
+ return NULL;
+ }
+ }
+ GNUNET_assert (0 ==
+ json_object_set (state,
+ "core_secret",
+ jsecret));
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ GNUNET_JSON_parse_free (spec);
+ return NULL;
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "clear_secret" action.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+static struct ANASTASIS_ReduxAction *
+clear_secret (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ if (0 !=
+ json_object_del (state,
+ "core_secret"))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'core_secret' not set");
+ return NULL;
+ }
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for an
+ * "enter_secret_name" action.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+static struct ANASTASIS_ReduxAction *
+enter_secret_name (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ const char *secret_name = NULL;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("name",
+ &secret_name),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (NULL == arguments)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "arguments missing");
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (arguments,
+ spec,
+ NULL, NULL))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'name' argument required");
+ return NULL;
+ }
+
+ GNUNET_assert (0 ==
+ json_object_set_new (state,
+ "secret_name",
+ json_string (secret_name)));
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ GNUNET_JSON_parse_free (spec);
+ return NULL;
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for the
+ * "update_expiration" action in the "secret editing" state.
+ * Updates how long we are to store the truth and policies
+ * and computes the new cost.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL (synchronous operation)
+ */
+static struct ANASTASIS_ReduxAction *
+update_expiration (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ struct GNUNET_TIME_Absolute expiration;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_absolute_time ("expiration",
+ &expiration),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (NULL == arguments)
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "arguments missing");
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (arguments,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'expiration' argument required");
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ update_expiration_cost (state,
+ expiration))
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID_FOR_STATE,
+ "could not calculate expiration cost");
+ return NULL;
+ }
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for the
+ * "next" action in the "secret editing" state.
+ * Returns an #ANASTASIS_ReduxAction as operation is async.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ */
+static struct ANASTASIS_ReduxAction *
+finish_secret (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ json_t *core_secret;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_json ("core_secret",
+ &core_secret),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (state,
+ spec,
+ NULL, NULL))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "State parsing failed: 'core_secret' is missing");
+ return NULL;
+ }
+
+ /* check upload size limit */
+ {
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = check_upload_size_limit (state,
+ core_secret);
+ switch (ret)
+ {
+ case GNUNET_SYSERR:
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "provider has an upload limit of 0");
+ GNUNET_JSON_parse_free (spec);
+ return NULL;
+ case GNUNET_NO:
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_SECRET_TOO_BIG,
+ NULL);
+ GNUNET_JSON_parse_free (spec);
+ return NULL;
+ default:
+ break;
+ }
+ }
+
+ GNUNET_JSON_parse_free (spec);
+ return upload (state,
+ cb,
+ cb_cls);
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "pay" action.
+ * Returns an #ANASTASIS_ReduxAction as operation is async.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ */
+static struct ANASTASIS_ReduxAction *
+pay_truths_backup (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ /* Clear 'payments' if it exists */
+ (void) json_object_del (state,
+ "payments");
+ if (NULL != arguments)
+ GNUNET_assert (0 ==
+ json_object_set (state,
+ "pay-arguments",
+ (json_t *) arguments));
+ return upload (state,
+ cb,
+ cb_cls);
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "pay" action.
+ * Returns an #ANASTASIS_ReduxAction as operation is async.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ */
+static struct ANASTASIS_ReduxAction *
+pay_policies_backup (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ /* Clear 'policy_payment_requests' if it exists */
+ (void) json_object_del (state,
+ "policy_payment_requests");
+ if (NULL != arguments)
+ GNUNET_assert (0 ==
+ json_object_set (state,
+ "pay-arguments",
+ (json_t *) arguments));
+ return upload (state,
+ cb,
+ cb_cls);
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "back" action if state is "FINISHED".
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+static struct ANASTASIS_ReduxAction *
+back_finished (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ set_state (state,
+ ANASTASIS_BACKUP_STATE_SECRET_EDITING);
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+/**
+ * Signature of callback function that implements a state transition.
+ *
+ * @param state current state
+ * @param arguments arguments for the state transition
+ * @param cb function to call when done
+ * @param cb_cls closure for @a cb
+ */
+typedef struct ANASTASIS_ReduxAction *
+(*DispatchHandler)(json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Operates on a backup state depending on given #ANASTASIS_BackupState
+ * and #ANASTASIS_BackupAction. The new #ANASTASIS_BackupState is returned
+ * by a callback function.
+ * This function can do network access to talk to anastasis service providers.
+ *
+ * @param[in,out] state input/output state (to be modified)
+ * @param action what action to perform
+ * @param arguments data for the @a action
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ */
+struct ANASTASIS_ReduxAction *
+ANASTASIS_backup_action_ (json_t *state,
+ const char *action,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ struct Dispatcher
+ {
+ enum ANASTASIS_BackupState backup_state;
+ const char *backup_action;
+ DispatchHandler fun;
+ } dispatchers[] = {
+ {
+ ANASTASIS_BACKUP_STATE_AUTHENTICATIONS_EDITING,
+ "add_authentication",
+ &add_authentication
+ },
+ {
+ ANASTASIS_BACKUP_STATE_AUTHENTICATIONS_EDITING,
+ "delete_authentication",
+ &del_authentication
+ },
+ {
+ ANASTASIS_BACKUP_STATE_AUTHENTICATIONS_EDITING,
+ "next",
+ &done_authentication
+ },
+ {
+ ANASTASIS_BACKUP_STATE_AUTHENTICATIONS_EDITING,
+ "add_provider",
+ &add_provider
+ },
+ {
+ ANASTASIS_BACKUP_STATE_AUTHENTICATIONS_EDITING,
+ "back",
+ &ANASTASIS_back_generic_decrement_
+ },
+ {
+ ANASTASIS_BACKUP_STATE_POLICIES_REVIEWING,
+ "add_policy",
+ &add_policy
+ },
+ {
+ ANASTASIS_BACKUP_STATE_POLICIES_REVIEWING,
+ "update_policy",
+ &update_policy
+ },
+ {
+ ANASTASIS_BACKUP_STATE_POLICIES_REVIEWING,
+ "delete_policy",
+ &del_policy
+ },
+ {
+ ANASTASIS_BACKUP_STATE_POLICIES_REVIEWING,
+ "delete_challenge",
+ &del_challenge
+ },
+ {
+ ANASTASIS_BACKUP_STATE_POLICIES_REVIEWING,
+ "next",
+ &done_policy_review
+ },
+ {
+ ANASTASIS_BACKUP_STATE_POLICIES_REVIEWING,
+ "back",
+ &ANASTASIS_back_generic_decrement_
+ },
+ {
+ ANASTASIS_BACKUP_STATE_SECRET_EDITING,
+ "enter_secret",
+ &enter_secret
+ },
+ {
+ ANASTASIS_BACKUP_STATE_SECRET_EDITING,
+ "clear_secret",
+ &clear_secret
+ },
+ {
+ ANASTASIS_BACKUP_STATE_SECRET_EDITING,
+ "enter_secret_name",
+ &enter_secret_name
+ },
+ {
+ ANASTASIS_BACKUP_STATE_SECRET_EDITING,
+ "back",
+ &ANASTASIS_back_generic_decrement_
+ },
+ {
+ ANASTASIS_BACKUP_STATE_SECRET_EDITING,
+ "update_expiration",
+ &update_expiration
+ },
+ {
+ ANASTASIS_BACKUP_STATE_SECRET_EDITING,
+ "next",
+ &finish_secret
+ },
+ {
+ ANASTASIS_BACKUP_STATE_TRUTHS_PAYING,
+ "pay",
+ &pay_truths_backup
+ },
+ {
+ ANASTASIS_BACKUP_STATE_POLICIES_PAYING,
+ "pay",
+ &pay_policies_backup
+ },
+ {
+ ANASTASIS_BACKUP_STATE_BACKUP_FINISHED,
+ "back",
+ &back_finished
+ },
+ { ANASTASIS_BACKUP_STATE_ERROR, NULL, NULL }
+ };
+ const char *s = json_string_value (json_object_get (state,
+ "backup_state"));
+ enum ANASTASIS_BackupState bs;
+
+ GNUNET_assert (NULL != s); /* holds as per invariant of caller */
+ bs = ANASTASIS_backup_state_from_string_ (s);
+ if (ANASTASIS_BACKUP_STATE_ERROR == bs)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "unknown 'backup_state'");
+ return NULL;
+ }
+ for (unsigned int i = 0; NULL != dispatchers[i].fun; i++)
+ {
+ if ( (bs == dispatchers[i].backup_state) &&
+ (0 == strcmp (action,
+ dispatchers[i].backup_action)) )
+ {
+ return dispatchers[i].fun (state,
+ arguments,
+ cb,
+ cb_cls);
+ }
+ }
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_ACTION_INVALID,
+ action);
+ return NULL;
+}
+
+
+/**
+ * State for a #ANASTASIS_REDUX_backup_begin_() operation.
+ */
+struct BackupStartState;
+
+
+/**
+ * Entry in the list of all known applicable Anastasis providers.
+ * Used to wait for it to complete downloading /config.
+ */
+struct BackupStartStateProviderEntry
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct BackupStartStateProviderEntry *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct BackupStartStateProviderEntry *prev;
+
+ /**
+ * Main operation this entry is part of.
+ */
+ struct BackupStartState *bss;
+
+ /**
+ * Resulting provider information, NULL if not (yet) available.
+ */
+ json_t *istate;
+
+ /**
+ * Ongoing reducer action to obtain /config, NULL if completed.
+ */
+ struct ANASTASIS_ReduxAction *ra;
+
+ /**
+ * Final result of the operation (once completed).
+ */
+ enum TALER_ErrorCode ec;
+};
+
+
+struct BackupStartState
+{
+ /**
+ * Head of list of provider /config operations we are doing.
+ */
+ struct BackupStartStateProviderEntry *pe_head;
+
+ /**
+ * Tail of list of provider /config operations we are doing.
+ */
+ struct BackupStartStateProviderEntry *pe_tail;
+
+ /**
+ * State we are updating.
+ */
+ json_t *state;
+
+ /**
+ * Function to call when we are done.
+ */
+ ANASTASIS_ActionCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Redux action we returned to our controller.
+ */
+ struct ANASTASIS_ReduxAction ra;
+
+ /**
+ * Number of provider /config operations in @e ba_head that
+ * are still awaiting completion.
+ */
+ unsigned int pending;
+};
+
+
+/**
+ * The backup start operation is being aborted, terminate.
+ *
+ * @param cls a `struct BackupStartState *`
+ */
+static void
+abort_backup_begin_cb (void *cls)
+{
+ struct BackupStartState *bss = cls;
+ struct BackupStartStateProviderEntry *pe;
+
+ while (NULL != (pe = bss->pe_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (bss->pe_head,
+ bss->pe_tail,
+ pe);
+ if (NULL != pe->ra)
+ pe->ra->cleanup (pe->ra->cleanup_cls);
+ json_decref (pe->istate);
+ GNUNET_free (pe);
+ }
+ json_decref (bss->state);
+ GNUNET_free (bss);
+}
+
+
+/**
+ * We finished downloading /config from all providers, merge
+ * into the main state, trigger the continuation and free our
+ * state.
+ *
+ * @param[in] bss main state to merge into
+ */
+static void
+providers_complete (struct BackupStartState *bss)
+{
+ struct BackupStartStateProviderEntry *pe;
+ json_t *tlist;
+
+ tlist = json_object_get (bss->state,
+ "authentication_providers");
+ if (NULL == tlist)
+ {
+ tlist = json_object ();
+ GNUNET_assert (NULL != tlist);
+ GNUNET_assert (0 ==
+ json_object_set_new (bss->state,
+ "authentication_providers",
+ tlist));
+ }
+ while (NULL != (pe = bss->pe_head))
+ {
+ json_t *provider_list;
+
+ GNUNET_CONTAINER_DLL_remove (bss->pe_head,
+ bss->pe_tail,
+ pe);
+ provider_list = json_object_get (pe->istate,
+ "authentication_providers");
+ /* merge provider_list into tlist (overriding existing entries) */
+ if (NULL != provider_list)
+ {
+ const char *url;
+ json_t *value;
+
+ json_object_foreach (provider_list, url, value) {
+ GNUNET_assert (0 ==
+ json_object_set (tlist,
+ url,
+ value));
+ }
+ }
+ json_decref (pe->istate);
+ GNUNET_free (pe);
+ }
+ bss->cb (bss->cb_cls,
+ TALER_EC_NONE,
+ bss->state);
+ json_decref (bss->state);
+ GNUNET_free (bss);
+}
+
+
+/**
+ * Function called when the complete information about a provider
+ * was added to @a new_state.
+ *
+ * @param cls a `struct BackupStartStateProviderEntry`
+ * @param error error code
+ * @param new_state resulting new state
+ */
+static void
+provider_added_cb (void *cls,
+ enum TALER_ErrorCode error,
+ json_t *new_state)
+{
+ struct BackupStartStateProviderEntry *pe = cls;
+
+ pe->ra = NULL;
+ pe->istate = json_incref (new_state);
+ pe->ec = error;
+ pe->bss->pending--;
+ if (0 == pe->bss->pending)
+ providers_complete (pe->bss);
+}
+
+
+struct ANASTASIS_ReduxAction *
+ANASTASIS_REDUX_backup_begin_ (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ json_t *provider_list;
+ struct BackupStartState *bss;
+
+ provider_list = json_object_get (state,
+ "authentication_providers");
+ if (NULL == provider_list)
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'authentication_providers' missing");
+ return NULL;
+ }
+ bss = GNUNET_new (struct BackupStartState);
+ bss->state = json_incref (state);
+ bss->cb = cb;
+ bss->cb_cls = cb_cls;
+ bss->ra.cleanup_cls = bss;
+ bss->ra.cleanup = &abort_backup_begin_cb;
+ bss->pending = 1; /* decremented after initialization loop */
+
+ {
+ json_t *prov;
+ const char *url;
+ json_object_foreach (provider_list, url, prov) {
+ struct BackupStartStateProviderEntry *pe;
+ json_t *istate;
+
+ pe = GNUNET_new (struct BackupStartStateProviderEntry);
+ pe->bss = bss;
+ istate = json_object ();
+ GNUNET_assert (NULL != istate);
+ GNUNET_CONTAINER_DLL_insert (bss->pe_head,
+ bss->pe_tail,
+ pe);
+ pe->ra = ANASTASIS_REDUX_add_provider_to_state_ (url,
+ istate,
+ &provider_added_cb,
+ pe);
+ json_decref (istate);
+ if (NULL != pe->ra)
+ bss->pending++;
+ }
+ }
+ bss->pending--;
+ if (0 == bss->pending)
+ {
+ providers_complete (bss);
+ return NULL;
+ }
+ return &bss->ra;
+}
diff --git a/src/reducer/anastasis_api_recovery_redux.c b/src/reducer/anastasis_api_recovery_redux.c
new file mode 100644
index 0000000..8a900ec
--- /dev/null
+++ b/src/reducer/anastasis_api_recovery_redux.c
@@ -0,0 +1,2558 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 2021 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/anastasis_api_recovery_redux.c
+ * @brief anastasis reducer recovery api
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ */
+
+#include <platform.h>
+#include <jansson.h>
+#include "anastasis_redux.h"
+#include "anastasis_error_codes.h"
+#include "anastasis_api_redux.h"
+
+
+#define GENERATE_STRING(STRING) #STRING,
+static const char *recovery_strings[] = {
+ ANASTASIS_RECOVERY_STATES (GENERATE_STRING)
+};
+#undef GENERATE_STRING
+
+
+enum ANASTASIS_RecoveryState
+ANASTASIS_recovery_state_from_string_ (const char *state_string)
+{
+ for (enum ANASTASIS_RecoveryState i = 0;
+ i < sizeof (recovery_strings) / sizeof(*recovery_strings);
+ i++)
+ if (0 == strcmp (state_string,
+ recovery_strings[i]))
+ return i;
+ return ANASTASIS_RECOVERY_STATE_ERROR;
+}
+
+
+const char *
+ANASTASIS_recovery_state_to_string_ (enum ANASTASIS_RecoveryState rs)
+{
+ if ( (rs < 0) ||
+ (rs >= sizeof (recovery_strings) / sizeof(*recovery_strings)) )
+ {
+ GNUNET_break_op (0);
+ return NULL;
+ }
+ return recovery_strings[rs];
+}
+
+
+static void
+set_state (json_t *state,
+ enum ANASTASIS_RecoveryState new_recovery_state)
+{
+ GNUNET_assert (
+ 0 ==
+ json_object_set_new (
+ state,
+ "recovery_state",
+ json_string (ANASTASIS_recovery_state_to_string_ (new_recovery_state))));
+}
+
+
+/**
+ * Returns an initial ANASTASIS recovery state.
+ *
+ * @return NULL on failure
+ */
+json_t *
+ANASTASIS_recovery_start (const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ json_t *initial_state;
+
+ (void) cfg;
+ initial_state = ANASTASIS_REDUX_load_continents_ ();
+ if (NULL == initial_state)
+ return NULL;
+ set_state (initial_state,
+ ANASTASIS_RECOVERY_STATE_CONTINENT_SELECTING);
+ return initial_state;
+}
+
+
+/**
+ * Context for a "select_challenge" operation.
+ */
+struct SelectChallengeContext
+{
+ /**
+ * Handle we returned for cancellation of the operation.
+ */
+ struct ANASTASIS_ReduxAction ra;
+
+ /**
+ * UUID of the challenge selected by the user for solving.
+ */
+ struct ANASTASIS_CRYPTO_TruthUUIDP uuid;
+
+ /**
+ * Which timeout was set for the operation?
+ */
+ struct GNUNET_TIME_Relative timeout;
+
+ /**
+ * Overall recovery action.
+ */
+ struct ANASTASIS_Recovery *r;
+
+ /**
+ * Function to call with the next state.
+ */
+ ANASTASIS_ActionCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Our state.
+ */
+ json_t *state;
+
+ /**
+ * Our arguments (like answers to the challenge, if already provided).
+ */
+ json_t *args;
+
+ /**
+ * Task scheduled for delayed success reporting. Needed to make
+ * sure that the solved challenge was really the final result,
+ * cancelled if the solved challenge resulted in the secret being
+ * recovered.
+ */
+ struct GNUNET_SCHEDULER_Task *delayed_report;
+
+ /**
+ * Payment secret, if we are in the "pay" state.
+ */
+ struct ANASTASIS_PaymentSecretP ps;
+};
+
+
+/**
+ * Cleanup a select challenge context.
+ *
+ * @param cls a `struct SelectChallengeContext *`
+ */
+static void
+sctx_free (void *cls)
+{
+ struct SelectChallengeContext *sctx = cls;
+
+ if (NULL != sctx->r)
+ {
+ ANASTASIS_recovery_abort (sctx->r);
+ sctx->r = NULL;
+ }
+ json_decref (sctx->state);
+ json_decref (sctx->args);
+ if (NULL != sctx->delayed_report)
+ {
+ GNUNET_SCHEDULER_cancel (sctx->delayed_report);
+ sctx->delayed_report = NULL;
+ }
+ GNUNET_free (sctx);
+}
+
+
+/**
+ * Update @a state to reflect the error provided in @a rc.
+ *
+ * @param[in,out] state state to update
+ * @param rc error code to translate to JSON
+ * @return error code to use
+ */
+static enum TALER_ErrorCode
+update_state_by_error (json_t *state,
+ enum ANASTASIS_RecoveryStatus rc)
+{
+ const char *msg = NULL;
+ enum TALER_ErrorCode ec = TALER_EC_INVALID;
+
+ switch (rc)
+ {
+ case ANASTASIS_RS_SUCCESS:
+ GNUNET_assert (0);
+ break;
+ case ANASTASIS_RS_POLICY_DOWNLOAD_FAILED:
+ msg = gettext_noop ("download failed due to unexpected network issue");
+ ec = TALER_EC_ANASTASIS_REDUCER_NETWORK_FAILED;
+ break;
+ case ANASTASIS_RS_POLICY_DOWNLOAD_NO_POLICY:
+ GNUNET_break (0);
+ msg = gettext_noop ("policy document returned was malformed");
+ ec = TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED;
+ break;
+ case ANASTASIS_RS_POLICY_DOWNLOAD_TOO_BIG:
+ GNUNET_break (0);
+ msg = gettext_noop ("policy document too large for client memory");
+ ec = TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED;
+ break;
+ case ANASTASIS_RS_POLICY_DOWNLOAD_INVALID_COMPRESSION:
+ GNUNET_break (0);
+ msg = gettext_noop ("failed to decompress policy document");
+ ec = TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED;
+ break;
+ case ANASTASIS_RS_POLICY_DOWNLOAD_NO_JSON:
+ GNUNET_break (0);
+ msg = gettext_noop ("policy document returned was not in JSON format");
+ ec = TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED;
+ break;
+ case ANASTASIS_RS_POLICY_MALFORMED_JSON:
+ GNUNET_break (0);
+ msg = gettext_noop (
+ "policy document returned was not in required JSON format");
+ ec = TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED;
+ break;
+ case ANASTASIS_RS_POLICY_SERVER_ERROR:
+ msg = gettext_noop ("Anastasis server reported transient internal error");
+ ec = TALER_EC_ANASTASIS_REDUCER_BACKUP_PROVIDER_FAILED;
+ break;
+ case ANASTASIS_RS_POLICY_GONE:
+ msg = gettext_noop ("policy document no longer exists");
+ ec = TALER_EC_ANASTASIS_REDUCER_POLICY_LOOKUP_FAILED;
+ break;
+ case ANASTASIS_RS_POLICY_UNKNOWN:
+ msg = gettext_noop ("account unknown to Anastasis server");
+ ec = TALER_EC_ANASTASIS_REDUCER_POLICY_LOOKUP_FAILED;
+ break;
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (state,
+ "error_message",
+ json_string (msg)));
+ GNUNET_assert (0 ==
+ json_object_set_new (state,
+ "error_code",
+ json_integer (rc)));
+ set_state (state,
+ ANASTASIS_GENERIC_STATE_ERROR);
+ return ec;
+}
+
+
+/**
+ * This function is called whenever the recovery process ends.
+ * On success, the secret is returned in @a secret.
+ *
+ * @param cls handle for the callback
+ * @param ec error code
+ * @param secret contains the core secret which is passed to the user
+ * @param secret_size defines the size of the core secret
+ */
+static void
+core_secret_cb (void *cls,
+ enum ANASTASIS_RecoveryStatus rc,
+ const void *secret,
+ size_t secret_size)
+{
+ struct SelectChallengeContext *sctx = cls;
+ enum TALER_ErrorCode ec;
+
+ sctx->r = NULL;
+ if (ANASTASIS_RS_SUCCESS == rc)
+ {
+ json_t *jsecret;
+
+ jsecret = json_loadb (secret,
+ secret_size,
+ JSON_REJECT_DUPLICATES,
+ NULL);
+ if (NULL == jsecret)
+ {
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_SECRET_MALFORMED,
+ NULL);
+ sctx_free (sctx);
+ return;
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (sctx->state,
+ "core_secret",
+ jsecret));
+ set_state (sctx->state,
+ ANASTASIS_RECOVERY_STATE_RECOVERY_FINISHED);
+ sctx->cb (sctx->cb_cls,
+ TALER_EC_NONE,
+ sctx->state);
+ sctx_free (sctx);
+ return;
+ }
+ ec = update_state_by_error (sctx->state,
+ rc);
+ sctx->cb (sctx->cb_cls,
+ ec,
+ sctx->state);
+ sctx_free (sctx);
+}
+
+
+/**
+ * A challenge was solved, but we are not yet finished.
+ * Report to caller that the challenge was completed.
+ *
+ * @param cls a `struct SelectChallengeContext`
+ */
+static void
+report_solved (void *cls)
+{
+ struct SelectChallengeContext *sctx = cls;
+
+ sctx->delayed_report = NULL;
+ set_state (sctx->state,
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
+ sctx->cb (sctx->cb_cls,
+ TALER_EC_NONE,
+ sctx->state);
+ sctx_free (sctx);
+}
+
+
+/**
+ * Find challenge of @a uuid in @a state under "recovery_information".
+ *
+ * @param state the state to search
+ * @param uuid the UUID to search for
+ * @return NULL on error, otherwise challenge entry; RC is NOT incremented
+ */
+static json_t *
+find_challenge_in_ri (json_t *state,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid)
+{
+ struct ANASTASIS_CRYPTO_TruthUUIDP u;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("uuid",
+ &u),
+ GNUNET_JSON_spec_end ()
+ };
+ json_t *ri;
+ json_t *challenges;
+ json_t *challenge;
+ size_t index;
+
+ ri = json_object_get (state,
+ "recovery_information");
+ if (NULL == ri)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ challenges = json_object_get (ri,
+ "challenges");
+ if (NULL == challenges)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ json_array_foreach (challenges, index, challenge)
+ {
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (challenge,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ if (0 ==
+ GNUNET_memcmp (&u,
+ uuid))
+ {
+ return challenge;
+ }
+ }
+ return NULL;
+}
+
+
+/**
+ * Defines a callback for the response status for a challenge start
+ * operation.
+ *
+ * @param cls a `struct SelectChallengeContext *`
+ * @param csr response details
+ */
+static void
+answer_feedback_cb (
+ void *cls,
+ const struct ANASTASIS_ChallengeStartResponse *csr)
+{
+ struct SelectChallengeContext *sctx = cls;
+ const struct ANASTASIS_ChallengeDetails *cd;
+ char uuid[sizeof (cd->uuid) * 2];
+ char *end;
+ json_t *feedback;
+
+ cd = ANASTASIS_challenge_get_details (csr->challenge);
+ end = GNUNET_STRINGS_data_to_string (&cd->uuid,
+ sizeof (cd->uuid),
+ uuid,
+ sizeof (uuid));
+ GNUNET_assert (NULL != end);
+ *end = '\0';
+ feedback = json_object_get (sctx->state,
+ "challenge_feedback");
+ if (NULL == feedback)
+ {
+ feedback = json_object ();
+ GNUNET_assert (0 ==
+ json_object_set_new (sctx->state,
+ "challenge_feedback",
+ feedback));
+ }
+ switch (csr->cs)
+ {
+ case ANASTASIS_CHALLENGE_STATUS_SOLVED:
+ {
+ json_t *rd;
+
+ rd = ANASTASIS_recovery_serialize (sctx->r);
+ if (NULL == rd)
+ {
+ GNUNET_break (0);
+ set_state (sctx->state,
+ ANASTASIS_GENERIC_STATE_ERROR);
+ sctx->cb (sctx->cb_cls,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ sctx->state);
+ sctx_free (sctx);
+ return;
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (sctx->state,
+ "recovery_document",
+ rd));
+ }
+ {
+ json_t *solved;
+
+ solved = json_pack ("{s:s}",
+ "state",
+ "solved");
+ GNUNET_assert (NULL != solved);
+ GNUNET_assert (0 ==
+ json_object_set_new (feedback,
+ uuid,
+ solved));
+ }
+ /* Delay reporting challenge success, as we MAY still
+ also see a secret recovery success (and we can only
+ call the callback once) */
+ sctx->delayed_report = GNUNET_SCHEDULER_add_now (&report_solved,
+ sctx);
+ return;
+ case ANASTASIS_CHALLENGE_STATUS_INSTRUCTIONS:
+ {
+ json_t *instructions;
+ json_t *val;
+ const char *mime;
+
+ mime = csr->details.open_challenge.content_type;
+ if (NULL != mime)
+ {
+ if ( (0 == strcasecmp (mime,
+ "text/plain")) ||
+ (0 == strcasecmp (mime,
+ "text/utf8")) )
+ {
+ char *s = GNUNET_strndup (csr->details.open_challenge.body,
+ csr->details.open_challenge.body_size);
+
+ instructions = json_pack (
+ "{s:s, s:s, s:I}",
+ "state",
+ "hint",
+ "hint",
+ s,
+ "http_status",
+ (json_int_t) csr->details.open_challenge.http_status);
+ GNUNET_free (s);
+ }
+ else if (0 == strcasecmp (mime,
+ "application/json"))
+ {
+ json_t *body;
+
+ body = json_loadb (csr->details.open_challenge.body,
+ csr->details.open_challenge.body_size,
+ JSON_REJECT_DUPLICATES,
+ NULL);
+ if (NULL == body)
+ {
+ GNUNET_break_op (0);
+ mime = NULL;
+ }
+ else
+ {
+ instructions = json_pack (
+ "{s:s, s:o, s:I}",
+ "state",
+ "details",
+ "details",
+ body,
+ "http_status",
+ (json_int_t) csr->details.open_challenge.http_status);
+ }
+ }
+ else
+ {
+ /* unexpected / unsupported mime type */
+ mime = NULL;
+ }
+ }
+ if (NULL == mime)
+ {
+ val = GNUNET_JSON_from_data (csr->details.open_challenge.body,
+ csr->details.open_challenge.body_size);
+ GNUNET_assert (NULL != val);
+ instructions = json_pack (
+ "{s:s, s:o, s:I, s:s?}",
+ "state",
+ "body",
+ "body",
+ val,
+ "http_status",
+ (json_int_t) csr->details.open_challenge.http_status,
+ "mime_type",
+ mime);
+ }
+ GNUNET_assert (NULL != instructions);
+ GNUNET_assert (0 ==
+ json_object_set_new (feedback,
+ uuid,
+ instructions));
+ }
+ set_state (sctx->state,
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING);
+ sctx->cb (sctx->cb_cls,
+ TALER_EC_NONE,
+ sctx->state);
+ sctx_free (sctx);
+ return;
+ case ANASTASIS_CHALLENGE_STATUS_REDIRECT_FOR_AUTHENTICATION:
+ {
+ json_t *redir;
+
+ redir = json_pack ("{s:s, s:s}",
+ "state",
+ "redirect",
+ "redirect_url",
+ csr->details.redirect_url);
+ GNUNET_assert (NULL != redir);
+ GNUNET_assert (0 ==
+ json_object_set_new (feedback,
+ uuid,
+ redir));
+ }
+ set_state (sctx->state,
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING);
+ sctx->cb (sctx->cb_cls,
+ TALER_EC_NONE,
+ sctx->state);
+ sctx_free (sctx);
+ return;
+ case ANASTASIS_CHALLENGE_STATUS_PAYMENT_REQUIRED:
+ {
+ json_t *pay;
+
+ pay = json_pack ("{s:s, s:s, s:s, s:o}",
+ "state",
+ "payment",
+ "taler_pay_uri",
+ csr->details.payment_required.taler_pay_uri,
+ "provider",
+ cd->provider_url,
+ "payment_secret",
+ GNUNET_JSON_from_data_auto (
+ &csr->details.payment_required.payment_secret));
+ GNUNET_assert (NULL != pay);
+ GNUNET_assert (0 ==
+ json_object_set_new (feedback,
+ uuid,
+ pay));
+ }
+ /* Remember payment secret for later (once application claims it paid) */
+ {
+ json_t *challenge = find_challenge_in_ri (sctx->state,
+ &cd->uuid);
+
+ GNUNET_assert (NULL != challenge);
+ GNUNET_assert (0 ==
+ json_object_set_new (challenge,
+ "payment_secret",
+ GNUNET_JSON_from_data_auto (
+ &csr->details.payment_required.
+ payment_secret)));
+ }
+ set_state (sctx->state,
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_PAYING);
+ sctx->cb (sctx->cb_cls,
+ TALER_EC_NONE,
+ sctx->state);
+ sctx_free (sctx);
+ return;
+ case ANASTASIS_CHALLENGE_STATUS_SERVER_FAILURE:
+ {
+ json_t *err;
+
+ err = json_pack ("{s:s, s:I, s:I}",
+ "state",
+ "server-failure",
+ "http_status",
+ (json_int_t) csr->details.server_failure.http_status,
+ "error_code",
+ (json_int_t) csr->details.server_failure.ec);
+ GNUNET_assert (NULL != err);
+ GNUNET_assert (0 ==
+ json_object_set_new (feedback,
+ uuid,
+ err));
+ }
+ GNUNET_break_op (0);
+ set_state (sctx->state,
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
+ sctx->cb (sctx->cb_cls,
+ TALER_EC_NONE,
+ sctx->state);
+ sctx_free (sctx);
+ return;
+ case ANASTASIS_CHALLENGE_STATUS_TRUTH_UNKNOWN:
+ {
+ json_t *err;
+
+ err = json_pack ("{s:s, s:I}",
+ "state",
+ "truth-unknown",
+ "error_code",
+ (json_int_t) TALER_EC_ANASTASIS_TRUTH_UNKNOWN);
+ GNUNET_assert (NULL != err);
+ GNUNET_assert (0 ==
+ json_object_set_new (feedback,
+ uuid,
+ err));
+ }
+ set_state (sctx->state,
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
+ sctx->cb (sctx->cb_cls,
+ TALER_EC_NONE,
+ sctx->state);
+ sctx_free (sctx);
+ return;
+ case ANASTASIS_CHALLENGE_STATUS_RATE_LIMIT_EXCEEDED:
+ {
+ json_t *err;
+
+ err = json_pack ("{s:s, s:I}",
+ "state",
+ "rate-limit-exceeded",
+ "error_code",
+ (json_int_t) TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED);
+
+ GNUNET_assert (NULL != err);
+ GNUNET_assert (0 ==
+ json_object_set_new (feedback,
+ uuid,
+ err));
+ }
+ GNUNET_break_op (0);
+ set_state (sctx->state,
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
+ sctx->cb (sctx->cb_cls,
+ TALER_EC_NONE,
+ sctx->state);
+ sctx_free (sctx);
+ return;
+ }
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ NULL);
+ sctx_free (sctx);
+}
+
+
+/**
+ * Callback which passes back the recovery document and its possible
+ * policies. Also passes back the version of the document for the user
+ * to check.
+ *
+ * We find the selected challenge and try to answer it (or begin
+ * the process).
+ *
+ * @param cls a `struct SelectChallengeContext *`
+ * @param ri recovery information struct which contains the policies
+ */
+static void
+solve_challenge_cb (void *cls,
+ const struct ANASTASIS_RecoveryInformation *ri)
+{
+ struct SelectChallengeContext *sctx = cls;
+ const struct ANASTASIS_PaymentSecretP *psp = NULL;
+ struct ANASTASIS_PaymentSecretP ps;
+ struct GNUNET_TIME_Relative timeout = GNUNET_TIME_UNIT_ZERO;
+ struct GNUNET_JSON_Specification tspec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_relative_time ("timeout",
+ &timeout)),
+ GNUNET_JSON_spec_end ()
+ };
+ struct GNUNET_JSON_Specification pspec[] = {
+ GNUNET_JSON_spec_fixed_auto ("payment_secret",
+ &ps),
+ GNUNET_JSON_spec_end ()
+ };
+
+ json_t *challenge;
+
+ if (NULL == ri)
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "recovery information could not be deserialized");
+ sctx_free (sctx);
+ return;
+ }
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (sctx->args,
+ tspec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'timeout' malformed");
+ sctx_free (sctx);
+ return;
+ }
+
+ /* Check if we got a payment_secret */
+ challenge = find_challenge_in_ri (sctx->state,
+ &sctx->uuid);
+ if (NULL == challenge)
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "challenge not found");
+ sctx_free (sctx);
+ return;
+ }
+
+ if (NULL !=
+ json_object_get (sctx->args,
+ "payment_secret"))
+ {
+ /* check if we got payment secret in args */
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (sctx->args,
+ pspec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'payment_secret' malformed");
+ sctx_free (sctx);
+ return;
+ }
+ psp = &ps;
+ }
+ else if (NULL !=
+ json_object_get (challenge,
+ "payment_secret"))
+ {
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (challenge,
+ pspec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'payment_secret' malformed");
+ sctx_free (sctx);
+ return;
+ }
+ psp = &ps;
+ }
+
+ for (unsigned int i = 0; i<ri->cs_len; i++)
+ {
+ struct ANASTASIS_Challenge *ci = ri->cs[i];
+ const struct ANASTASIS_ChallengeDetails *cd;
+ int ret;
+
+ cd = ANASTASIS_challenge_get_details (ci);
+ if (0 !=
+ GNUNET_memcmp (&sctx->uuid,
+ &cd->uuid))
+ continue;
+ if (cd->solved)
+ {
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "Selected challenge already solved");
+ sctx_free (sctx);
+ return;
+ }
+ if (0 == strcmp ("question",
+ cd->type))
+ {
+ /* security question, answer must be a string */
+ json_t *janswer = json_object_get (sctx->args,
+ "answer");
+ const char *answer = json_string_value (janswer);
+
+ if (NULL == answer)
+ {
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'answer' missing");
+ sctx_free (sctx);
+ return;
+ }
+ /* persist answer, in case payment is required */
+ GNUNET_assert (0 ==
+ json_object_set (challenge,
+ "answer",
+ janswer));
+ ret = ANASTASIS_challenge_answer (ci,
+ psp,
+ timeout,
+ answer,
+ &answer_feedback_cb,
+ sctx);
+ }
+ else
+ {
+ /* Check if we got a PIN or a HASH */
+ json_t *pin = json_object_get (sctx->args,
+ "pin");
+ json_t *hash = json_object_get (sctx->args,
+ "hash");
+ if (json_is_integer (pin))
+ {
+ uint64_t ianswer = json_integer_value (pin);
+
+ ret = ANASTASIS_challenge_answer2 (ci,
+ psp,
+ timeout,
+ ianswer,
+ &answer_feedback_cb,
+ sctx);
+ }
+ else if (NULL != hash)
+ {
+ struct GNUNET_HashCode hashed_answer;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("hash",
+ &hashed_answer),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (sctx->args,
+ spec,
+ NULL, NULL))
+ {
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'answer' malformed");
+ sctx_free (sctx);
+ return;
+ }
+ ret = ANASTASIS_challenge_start (ci,
+ psp,
+ timeout,
+ &hashed_answer,
+ &answer_feedback_cb,
+ sctx);
+ }
+ else
+ {
+ /* no answer provided */
+ ret = ANASTASIS_challenge_start (ci,
+ psp,
+ timeout,
+ NULL, /* no answer */
+ &answer_feedback_cb,
+ sctx);
+ }
+ }
+ if (GNUNET_OK != ret)
+ {
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "Failed to begin answering challenge");
+ sctx_free (sctx);
+ return;
+ }
+ return; /* await answer feedback */
+ }
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'uuid' not in list of challenges");
+ sctx_free (sctx);
+}
+
+
+/**
+ * Callback which passes back the recovery document and its possible
+ * policies. Also passes back the version of the document for the user
+ * to check.
+ *
+ * We find the selected challenge and try to answer it (or begin
+ * the process).
+ *
+ * @param cls a `struct SelectChallengeContext *`
+ * @param ri recovery information struct which contains the policies
+ */
+static void
+pay_challenge_cb (void *cls,
+ const struct ANASTASIS_RecoveryInformation *ri)
+{
+ struct SelectChallengeContext *sctx = cls;
+ json_t *challenge;
+
+ if (NULL == ri)
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "recovery information could not be deserialized");
+ sctx_free (sctx);
+ return;
+ }
+
+ challenge = find_challenge_in_ri (sctx->state,
+ &sctx->uuid);
+ if (NULL == challenge)
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "challenge not found");
+ sctx_free (sctx);
+ return;
+ }
+ /* persist payment, in case we need to run the request again */
+ GNUNET_assert (
+ 0 ==
+ json_object_set_new (challenge,
+ "payment_secret",
+ GNUNET_JSON_from_data_auto (&sctx->ps)));
+
+ for (unsigned int i = 0; i<ri->cs_len; i++)
+ {
+ struct ANASTASIS_Challenge *ci = ri->cs[i];
+ const struct ANASTASIS_ChallengeDetails *cd;
+ int ret;
+
+ cd = ANASTASIS_challenge_get_details (ci);
+ if (0 !=
+ GNUNET_memcmp (&sctx->uuid,
+ &cd->uuid))
+ continue;
+ if (cd->solved)
+ {
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "Selected challenge already solved");
+ sctx_free (sctx);
+ return;
+ }
+
+ if (0 == strcmp ("question",
+ cd->type))
+ {
+ /* security question, answer must be a string and already ready */
+ json_t *janswer = json_object_get (challenge,
+ "answer");
+ const char *answer = json_string_value (janswer);
+
+ if (NULL == answer)
+ {
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'answer' missing");
+ sctx_free (sctx);
+ return;
+ }
+ ret = ANASTASIS_challenge_answer (ci,
+ &sctx->ps,
+ sctx->timeout,
+ answer,
+ &answer_feedback_cb,
+ sctx);
+ }
+ else
+ {
+ ret = ANASTASIS_challenge_start (ci,
+ &sctx->ps,
+ sctx->timeout,
+ NULL, /* no answer yet */
+ &answer_feedback_cb,
+ sctx);
+ }
+ if (GNUNET_OK != ret)
+ {
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "Failed to begin answering challenge");
+ sctx_free (sctx);
+ return;
+ }
+ return; /* await answer feedback */
+ }
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'uuid' not in list of challenges");
+ sctx_free (sctx);
+}
+
+
+/**
+ * The user selected a challenge to be solved. Begin the solving
+ * process.
+ *
+ * @param[in] state we are in
+ * @param arguments our arguments with the solution
+ * @param cb functiont o call with the new state
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel challenge selection step
+ */
+static struct ANASTASIS_ReduxAction *
+solve_challenge (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ struct SelectChallengeContext *sctx
+ = GNUNET_new (struct SelectChallengeContext);
+ json_t *rd;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("selected_challenge_uuid",
+ &sctx->uuid),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (NULL == arguments)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "arguments missing");
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (state,
+ spec,
+ NULL, NULL))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'selected_challenge_uuid' missing");
+ return NULL;
+ }
+ rd = json_object_get (state,
+ "recovery_document");
+ if (NULL == rd)
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "solve_challenge");
+ return NULL;
+ }
+ sctx->cb = cb;
+ sctx->cb_cls = cb_cls;
+ sctx->state = json_incref (state);
+ sctx->args = json_incref ((json_t*) arguments);
+ sctx->r = ANASTASIS_recovery_deserialize (ANASTASIS_REDUX_ctx_,
+ rd,
+ &solve_challenge_cb,
+ sctx,
+ &core_secret_cb,
+ sctx);
+ if (NULL == sctx->r)
+ {
+ json_decref (sctx->state);
+ json_decref (sctx->args);
+ GNUNET_free (sctx);
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'recovery_document' invalid");
+ return NULL;
+ }
+ sctx->ra.cleanup = &sctx_free;
+ sctx->ra.cleanup_cls = sctx;
+ return &sctx->ra;
+}
+
+
+/**
+ * The user selected a challenge to be solved. Handle the payment
+ * process.
+ *
+ * @param[in] state we are in
+ * @param arguments our arguments with the solution
+ * @param cb functiont o call with the new state
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel challenge selection step
+ */
+static struct ANASTASIS_ReduxAction *
+pay_challenge (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ struct SelectChallengeContext *sctx
+ = GNUNET_new (struct SelectChallengeContext);
+ json_t *rd;
+ struct GNUNET_TIME_Relative timeout = GNUNET_TIME_UNIT_ZERO;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("selected_challenge_uuid",
+ &sctx->uuid),
+ GNUNET_JSON_spec_end ()
+ };
+ struct GNUNET_JSON_Specification aspec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_relative_time ("timeout",
+ &timeout)),
+ GNUNET_JSON_spec_fixed_auto ("payment_secret",
+ &sctx->ps),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (NULL == arguments)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "arguments missing");
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (arguments,
+ aspec,
+ NULL, NULL))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'payment_secret' missing");
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (state,
+ spec,
+ NULL, NULL))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'selected_challenge_uuid' missing");
+ return NULL;
+ }
+ rd = json_object_get (state,
+ "recovery_document");
+ if (NULL == rd)
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "pay_challenge");
+ return NULL;
+ }
+ sctx->timeout = timeout;
+ sctx->cb = cb;
+ sctx->cb_cls = cb_cls;
+ sctx->state = json_incref (state);
+ sctx->args = json_incref ((json_t*) arguments);
+ sctx->r = ANASTASIS_recovery_deserialize (ANASTASIS_REDUX_ctx_,
+ rd,
+ &pay_challenge_cb,
+ sctx,
+ &core_secret_cb,
+ sctx);
+ if (NULL == sctx->r)
+ {
+ json_decref (sctx->state);
+ json_decref (sctx->args);
+ GNUNET_free (sctx);
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'recovery_document' invalid");
+ return NULL;
+ }
+ sctx->ra.cleanup = &sctx_free;
+ sctx->ra.cleanup_cls = sctx;
+ return &sctx->ra;
+}
+
+
+/**
+ * Callback which passes back the recovery document and its possible
+ * policies. Also passes back the version of the document for the user
+ * to check.
+ *
+ * We find the selected challenge and try to answer it (or begin
+ * the process).
+ *
+ * @param cls a `struct SelectChallengeContext *`
+ * @param ri recovery information struct which contains the policies
+ */
+static void
+select_challenge_cb (void *cls,
+ const struct ANASTASIS_RecoveryInformation *ri)
+{
+ struct SelectChallengeContext *sctx = cls;
+ const struct ANASTASIS_PaymentSecretP *psp = NULL;
+ struct ANASTASIS_PaymentSecretP ps;
+ struct GNUNET_TIME_Relative timeout = GNUNET_TIME_UNIT_ZERO;
+ struct GNUNET_JSON_Specification tspec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_relative_time ("timeout",
+ &timeout)),
+ GNUNET_JSON_spec_end ()
+ };
+ struct GNUNET_JSON_Specification pspec[] = {
+ GNUNET_JSON_spec_fixed_auto ("payment_secret",
+ &ps),
+ GNUNET_JSON_spec_end ()
+ };
+
+
+ if (NULL == ri)
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "recovery information could not be deserialized");
+ sctx_free (sctx);
+ return;
+ }
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (sctx->args,
+ tspec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'timeout' malformed");
+ sctx_free (sctx);
+ return;
+ }
+
+ /* NOTE: do we need both ways to pass payment secrets? */
+ if (NULL !=
+ json_object_get (sctx->args,
+ "payment_secret"))
+ {
+ /* check if we got payment secret in args */
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (sctx->args,
+ pspec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'payment_secret' malformed");
+ sctx_free (sctx);
+ return;
+ }
+ psp = &ps;
+ }
+ else
+ {
+ /* Check if we got a payment_secret in state */
+ json_t *challenge = find_challenge_in_ri (sctx->state,
+ &sctx->uuid);
+
+ if (NULL == challenge)
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "challenge not found");
+ sctx_free (sctx);
+ return;
+ }
+ if (NULL !=
+ json_object_get (challenge,
+ "payment_secret"))
+ {
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (challenge,
+ pspec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'payment_secret' malformed");
+ sctx_free (sctx);
+ return;
+ }
+ psp = &ps;
+ }
+ }
+
+ for (unsigned int i = 0; i<ri->cs_len; i++)
+ {
+ struct ANASTASIS_Challenge *ci = ri->cs[i];
+ const struct ANASTASIS_ChallengeDetails *cd;
+ int ret;
+
+ cd = ANASTASIS_challenge_get_details (ci);
+ if (0 !=
+ GNUNET_memcmp (&sctx->uuid,
+ &cd->uuid))
+ continue;
+ if (cd->solved)
+ {
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "Selected challenge already solved");
+ sctx_free (sctx);
+ return;
+ }
+ GNUNET_assert (
+ 0 ==
+ json_object_set_new (sctx->state,
+ "selected_challenge_uuid",
+ GNUNET_JSON_from_data_auto (&cd->uuid)));
+ if (0 == strcmp ("question",
+ cd->type))
+ {
+ /* security question, immediately request user to answer it */
+ set_state (sctx->state,
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING);
+ sctx->cb (sctx->cb_cls,
+ TALER_EC_NONE,
+ sctx->state);
+ sctx_free (sctx);
+ return;
+ }
+ /* trigger challenge */
+ ret = ANASTASIS_challenge_start (ci,
+ psp,
+ timeout,
+ NULL, /* no answer */
+ &answer_feedback_cb,
+ sctx);
+ if (GNUNET_OK != ret)
+ {
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "Failed to begin answering challenge");
+ sctx_free (sctx);
+ return;
+ }
+ return; /* await answer feedback */
+ }
+ ANASTASIS_redux_fail_ (sctx->cb,
+ sctx->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'uuid' not in list of challenges");
+ sctx_free (sctx);
+}
+
+
+/**
+ * The user selected a challenge to be solved. Begin the solving
+ * process.
+ *
+ * @param[in] state we are in
+ * @param arguments our arguments with the solution
+ * @param cb functiont o call with the new state
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel challenge selection step
+ */
+static struct ANASTASIS_ReduxAction *
+select_challenge (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ struct SelectChallengeContext *sctx
+ = GNUNET_new (struct SelectChallengeContext);
+ json_t *rd;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("uuid",
+ &sctx->uuid),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (NULL == arguments)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "arguments missing");
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (arguments,
+ spec,
+ NULL, NULL))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'uuid' missing");
+ return NULL;
+ }
+ rd = json_object_get (state,
+ "recovery_document");
+ if (NULL == rd)
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "select_challenge");
+ return NULL;
+ }
+ sctx->cb = cb;
+ sctx->cb_cls = cb_cls;
+ sctx->state = json_incref (state);
+ sctx->args = json_incref ((json_t*) arguments);
+ sctx->r = ANASTASIS_recovery_deserialize (ANASTASIS_REDUX_ctx_,
+ rd,
+ &select_challenge_cb,
+ sctx,
+ &core_secret_cb,
+ sctx);
+ if (NULL == sctx->r)
+ {
+ json_decref (sctx->state);
+ json_decref (sctx->args);
+ GNUNET_free (sctx);
+ GNUNET_break_op (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'recovery_document' invalid");
+ return NULL;
+ }
+ sctx->ra.cleanup = &sctx_free;
+ sctx->ra.cleanup_cls = sctx;
+ return &sctx->ra;
+}
+
+
+/**
+ * The user pressed "back" during challenge solving.
+ * Transition back to selecting another challenge.
+ *
+ * @param[in] state we are in
+ * @param arguments our arguments (unused)
+ * @param cb functiont o call with the new state
+ * @param cb_cls closure for @a cb
+ * @return NULL (synchronous operation)
+ */
+static struct ANASTASIS_ReduxAction *
+back_challenge_solving (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ (void) arguments;
+ GNUNET_assert (0 ==
+ json_object_del (state,
+ "selected_challenge_uuid"));
+ set_state (state,
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+/**
+ * The user wants us to change the policy version. Download another version.
+ *
+ * @param[in] state we are in
+ * @param arguments our arguments with the solution
+ * @param cb functiont o call with the new state
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel challenge selection step
+ */
+static struct ANASTASIS_ReduxAction *
+change_version (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ uint64_t version;
+ const char *provider_url;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_uint64 ("version",
+ &version),
+ GNUNET_JSON_spec_string ("provider_url",
+ &provider_url),
+ GNUNET_JSON_spec_end ()
+ };
+ json_t *ia;
+ json_t *args;
+ struct ANASTASIS_ReduxAction *ra;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (arguments,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'version' invalid");
+ return NULL;
+ }
+ GNUNET_assert (NULL != provider_url);
+ ia = json_object_get (state,
+ "identity_attributes");
+ if (NULL == ia)
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'identity_attributes' missing");
+ return NULL;
+ }
+ args = json_pack ("{s:I, s:O, s:s}",
+ "version",
+ (json_int_t) version,
+ "identity_attributes",
+ ia,
+ "provider_url",
+ provider_url);
+ if (NULL == args)
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ NULL);
+ return NULL;
+ }
+ ra = ANASTASIS_REDUX_recovery_challenge_begin_ (state,
+ args,
+ cb,
+ cb_cls);
+ json_decref (args);
+ return ra;
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "next" action in "secret_selecting" state.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+static struct ANASTASIS_ReduxAction *
+done_secret_selecting (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ const json_t *ri;
+
+ ri = json_object_get (state,
+ "recovery_information");
+ if ( (NULL == ri) ||
+ (NULL == json_object_get (ri,
+ "challenges")) )
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID_FOR_STATE,
+ "no valid version selected");
+ return NULL;
+ }
+ set_state (state,
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+/**
+ * Signature of callback function that implements a state transition.
+ *
+ * @param state current state
+ * @param arguments arguments for the state transition
+ * @param cb function to call when done
+ * @param cb_cls closure for @a cb
+ */
+typedef struct ANASTASIS_ReduxAction *
+(*DispatchHandler)(json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Operates on a recovery state depending on given #ANASTASIS_RecoveryState
+ * and #ANASTASIS_RecoveryAction. The new #ANASTASIS_RecoveryState is returned
+ * by a callback function.
+ * This function can do network access to talk to anastasis service providers.
+ *
+ * @param[in,out] state input/output state (to be modified)
+ * @param action what action to perform
+ * @param arguments data for the @a action
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ */
+struct ANASTASIS_ReduxAction *
+ANASTASIS_recovery_action_ (json_t *state,
+ const char *action,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ struct Dispatcher
+ {
+ enum ANASTASIS_RecoveryState recovery_state;
+ const char *recovery_action;
+ DispatchHandler fun;
+ } dispatchers[] = {
+ {
+ ANASTASIS_RECOVERY_STATE_SECRET_SELECTING,
+ "change_version",
+ &change_version
+ },
+ {
+ ANASTASIS_RECOVERY_STATE_SECRET_SELECTING,
+ "next",
+ &done_secret_selecting
+ },
+ {
+ ANASTASIS_RECOVERY_STATE_SECRET_SELECTING,
+ "back",
+ &ANASTASIS_back_generic_decrement_
+ },
+ {
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING,
+ "select_challenge",
+ &select_challenge
+ },
+ {
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING,
+ "back",
+ &ANASTASIS_back_generic_decrement_
+ },
+ {
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_PAYING,
+ "pay",
+ &pay_challenge
+ },
+ {
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_PAYING,
+ "back",
+ &ANASTASIS_back_generic_decrement_
+ },
+ {
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING,
+ "solve_challenge",
+ &solve_challenge
+ },
+ {
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING,
+ "back",
+ &back_challenge_solving
+ },
+ { ANASTASIS_RECOVERY_STATE_ERROR, NULL, NULL }
+ };
+ const char *s = json_string_value (json_object_get (state,
+ "recovery_state"));
+ enum ANASTASIS_RecoveryState rs;
+
+ GNUNET_assert (NULL != s);
+ rs = ANASTASIS_recovery_state_from_string_ (s);
+ if (ANASTASIS_RECOVERY_STATE_ERROR == rs)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'recovery_state' field invalid");
+ return NULL;
+ }
+ for (unsigned int i = 0; NULL != dispatchers[i].fun; i++)
+ {
+ if ( (rs == dispatchers[i].recovery_state) &&
+ (0 == strcmp (action,
+ dispatchers[i].recovery_action)) )
+ {
+ return dispatchers[i].fun (state,
+ arguments,
+ cb,
+ cb_cls);
+ }
+ }
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_ACTION_INVALID,
+ action);
+ return NULL;
+}
+
+
+/**
+ * State for a "recover secret" CMD.
+ */
+struct RecoverSecretState;
+
+
+/**
+ * State for a "policy download" as part of a recovery operation.
+ */
+struct PolicyDownloadEntry
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct PolicyDownloadEntry *prev;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct PolicyDownloadEntry *next;
+
+ /**
+ * Backend we are querying.
+ */
+ char *backend_url;
+
+ /**
+ * Salt to be used to derive the id for this provider
+ */
+ struct ANASTASIS_CRYPTO_ProviderSaltP salt;
+
+ /**
+ * Context we operate in.
+ */
+ struct RecoverSecretState *rss;
+
+ /**
+ * The /policy GET operation handle.
+ */
+ struct ANASTASIS_Recovery *recovery;
+
+};
+
+
+/**
+ * Entry in the list of all known applicable Anastasis providers.
+ * Used to wait for it to complete downloading /config.
+ */
+struct RecoveryStartStateProviderEntry
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct RecoveryStartStateProviderEntry *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct RecoveryStartStateProviderEntry *prev;
+
+ /**
+ * Main operation this entry is part of.
+ */
+ struct RecoverSecretState *rss;
+
+ /**
+ * Resulting provider information, NULL if not (yet) available.
+ */
+ json_t *istate;
+
+ /**
+ * Ongoing reducer action to obtain /config, NULL if completed.
+ */
+ struct ANASTASIS_ReduxAction *ra;
+
+ /**
+ * Final result of the operation (once completed).
+ */
+ enum TALER_ErrorCode ec;
+};
+
+
+/**
+ * State for a "recover secret" CMD.
+ */
+struct RecoverSecretState
+{
+
+ /**
+ * Redux action handle associated with this state.
+ */
+ struct ANASTASIS_ReduxAction ra;
+
+ /**
+ * Head of list of provider /config operations we are doing.
+ */
+ struct RecoveryStartStateProviderEntry *pe_head;
+
+ /**
+ * Tail of list of provider /config operations we are doing.
+ */
+ struct RecoveryStartStateProviderEntry *pe_tail;
+
+ /**
+ * Identification data from the user
+ */
+ json_t *id_data;
+
+ /**
+ * Head of DLL of policy downloads.
+ */
+ struct PolicyDownloadEntry *pd_head;
+
+ /**
+ * Tail of DLL of policy downloads.
+ */
+ struct PolicyDownloadEntry *pd_tail;
+
+ /**
+ * Reference to our state.
+ */
+ json_t *state;
+
+ /**
+ * callback to call during/after operation
+ */
+ ANASTASIS_ActionCallback cb;
+
+ /**
+ * closure for action callback @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Set if recovery must be done with this provider.
+ */
+ char *provider_url;
+
+ /**
+ * version of the recovery document to request.
+ */
+ unsigned int version;
+
+ /**
+ * Number of provider /config operations in @e ba_head that
+ * are still awaiting completion.
+ */
+ unsigned int pending;
+
+ /**
+ * Is @e version set?
+ */
+ bool have_version;
+};
+
+
+/**
+ * Function to free a #RecoverSecretState.
+ *
+ * @param cls closure for a #RecoverSecretState.
+ */
+static void
+free_rss (void *cls)
+{
+ struct RecoverSecretState *rss = cls;
+ struct PolicyDownloadEntry *pd;
+ struct RecoveryStartStateProviderEntry *pe;
+
+ while (NULL != (pe = rss->pe_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (rss->pe_head,
+ rss->pe_tail,
+ pe);
+ ANASTASIS_redux_action_cancel (pe->ra);
+ rss->pending--;
+ GNUNET_free (pe);
+ }
+ while (NULL != (pd = rss->pd_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (rss->pd_head,
+ rss->pd_tail,
+ pd);
+ if (NULL != pd->recovery)
+ {
+ ANASTASIS_recovery_abort (pd->recovery);
+ pd->recovery = NULL;
+ }
+ GNUNET_free (pd->backend_url);
+ GNUNET_free (pd);
+ }
+ json_decref (rss->state);
+ json_decref (rss->id_data);
+ GNUNET_assert (0 == rss->pending);
+ GNUNET_free (rss->provider_url);
+ GNUNET_free (rss);
+}
+
+
+/**
+ * This function is called whenever the recovery process ends.
+ * In this case, that should not be possible as this callback
+ * is used before we even begin with the challenges. So if
+ * we are called, it is because of some fatal error.
+ *
+ * @param cls a `struct PolicyDownloadEntry`
+ * @param ec error code
+ * @param secret contains the core secret which is passed to the user
+ * @param secret_size defines the size of the core secret
+ */
+static void
+core_early_secret_cb (void *cls,
+ enum ANASTASIS_RecoveryStatus rc,
+ const void *secret,
+ size_t secret_size)
+{
+ struct PolicyDownloadEntry *pd = cls;
+ struct RecoverSecretState *rss = pd->rss;
+ enum TALER_ErrorCode ec;
+
+ pd->recovery = NULL;
+ GNUNET_assert (NULL == secret);
+ GNUNET_CONTAINER_DLL_remove (rss->pd_head,
+ rss->pd_tail,
+ pd);
+ GNUNET_free (pd->backend_url);
+ GNUNET_free (pd);
+ if (NULL != rss->pd_head)
+ return; /* wait for another one */
+ /* all failed! report failure! */
+ GNUNET_assert (ANASTASIS_RS_SUCCESS != rc);
+ ec = update_state_by_error (rss->state,
+ rc);
+ rss->cb (rss->cb_cls,
+ ec,
+ rss->state);
+ rss->cb = NULL;
+ free_rss (rss);
+}
+
+
+/**
+ * Determine recovery @a cost of solving a challenge of type @a type
+ * at @a provider_url by inspecting @a state.
+ *
+ * @param state the state to inspect
+ * @param provider_url the provider to lookup config info from
+ * @param type the method to lookup the cost of
+ * @param[out] cost the recovery cost to return
+ * @return #GNUNET_OK on success, #GNUNET_NO if not found, #GNUNET_SYSERR on state error
+ */
+static int
+lookup_cost (const json_t *state,
+ const char *provider_url,
+ const char *type,
+ struct TALER_Amount *cost)
+{
+ const json_t *providers;
+ const json_t *provider;
+ const json_t *methods;
+
+ providers = json_object_get (state,
+ "authentication_providers");
+ if (NULL == providers)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ provider = json_object_get (providers,
+ provider_url);
+ if (NULL == provider)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ methods = json_object_get (provider,
+ "methods");
+ if ( (NULL == methods) ||
+ (! json_is_array (methods)) )
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ {
+ size_t index;
+ json_t *method;
+
+ json_array_foreach (methods, index, method) {
+ const char *t;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("type",
+ &t),
+ TALER_JSON_spec_amount_any ("usage_fee",
+ cost),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (method,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ continue;
+ }
+ if (0 == strcmp (t,
+ type))
+ return GNUNET_OK;
+ }
+ }
+ return GNUNET_NO; /* not found */
+}
+
+
+/**
+ * We failed to download a policy. Show an error to the user and
+ * allow the user to specify alternative providers and/or policy
+ * versions.
+ *
+ * @param[in] rss state to fail with the policy download
+ * @param offline true of the reason to show is that all providers
+ * were offline / did not return a salt to us
+ */
+static void
+return_no_policy (struct RecoverSecretState *rss,
+ bool offline)
+{
+ json_t *msg;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "No provider online, need user to manually specify providers!\n");
+ msg = json_pack ("{s:s, s:b}",
+ "hint",
+ offline ? "could not contact provider" :
+ "provider does not know you",
+ "offline",
+ offline);
+ GNUNET_assert (0 ==
+ json_object_set_new (rss->state,
+ "recovery_error",
+ msg));
+ /* In case there are old ones, remove them! */
+ (void) json_object_del (rss->state,
+ "recovery_document");
+ (void) json_object_del (rss->state,
+ "recovery_information");
+ set_state (rss->state,
+ ANASTASIS_RECOVERY_STATE_SECRET_SELECTING);
+ rss->cb (rss->cb_cls,
+ TALER_EC_NONE,
+ rss->state);
+ free_rss (rss);
+}
+
+
+/**
+ * Callback which passes back the recovery document and its possible
+ * policies. Also passes back the version of the document for the user
+ * to check.
+ *
+ * Once the first policy lookup succeeds, we update our state and
+ * cancel all of the others, passing the obtained recovery information
+ * back to the user.
+ *
+ * @param cls closure for the callback
+ * @param ri recovery information struct which contains the policies
+ */
+static void
+policy_lookup_cb (void *cls,
+ const struct ANASTASIS_RecoveryInformation *ri)
+{
+ struct PolicyDownloadEntry *pd = cls;
+ struct RecoverSecretState *rss = pd->rss;
+ json_t *policies;
+ json_t *challenges;
+ json_t *recovery_information;
+
+ if (NULL == ri)
+ {
+ /* Woopsie, failed hard. */
+ GNUNET_CONTAINER_DLL_remove (rss->pd_head,
+ rss->pd_tail,
+ pd);
+ ANASTASIS_recovery_abort (pd->recovery);
+ GNUNET_free (pd->backend_url);
+ GNUNET_free (pd);
+ if (NULL != rss->pd_head)
+ return; /* wait for another one */
+ /* all failed! report failure! */
+ return_no_policy (rss,
+ false);
+ return;
+ }
+ policies = json_array ();
+ GNUNET_assert (NULL != policies);
+ for (unsigned int i = 0; i<ri->dps_len; i++)
+ {
+ struct ANASTASIS_DecryptionPolicy *dps = ri->dps[i];
+ json_t *pchallenges;
+
+ pchallenges = json_array ();
+ GNUNET_assert (NULL != pchallenges);
+ for (unsigned int j = 0; j<dps->challenges_length; j++)
+ {
+ struct ANASTASIS_Challenge *c = dps->challenges[j];
+ const struct ANASTASIS_ChallengeDetails *cd;
+ json_t *cj;
+
+ cd = ANASTASIS_challenge_get_details (c);
+ cj = json_pack ("{s:o}",
+ "uuid",
+ GNUNET_JSON_from_data_auto (&cd->uuid));
+ GNUNET_assert (NULL != cj);
+ GNUNET_assert (0 ==
+ json_array_append_new (pchallenges,
+ cj));
+
+ }
+ GNUNET_assert (0 ==
+ json_array_append_new (policies,
+ pchallenges));
+ } /* end for all policies */
+ challenges = json_array ();
+ GNUNET_assert (NULL != challenges);
+ for (unsigned int i = 0; i<ri->cs_len; i++)
+ {
+ struct ANASTASIS_Challenge *c = ri->cs[i];
+ const struct ANASTASIS_ChallengeDetails *cd;
+ json_t *cj;
+ struct TALER_Amount cost;
+ int ret;
+
+ cd = ANASTASIS_challenge_get_details (c);
+ ret = lookup_cost (rss->state,
+ cd->provider_url,
+ cd->type,
+ &cost);
+ if (GNUNET_SYSERR == ret)
+ {
+ json_decref (challenges);
+ json_decref (policies);
+ set_state (rss->state,
+ ANASTASIS_GENERIC_STATE_ERROR);
+ ANASTASIS_redux_fail_ (rss->cb,
+ rss->cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "failed to 'lookup_cost'");
+ free_rss (rss);
+ return;
+ }
+
+ cj = json_pack ("{s:o,s:o?,s:s,s:s}",
+ "uuid",
+ GNUNET_JSON_from_data_auto (&cd->uuid),
+ "cost",
+ (GNUNET_NO == ret)
+ ? NULL
+ : TALER_JSON_from_amount (&cost),
+ "type",
+ cd->type,
+ "instructions",
+ cd->instructions);
+ GNUNET_assert (NULL != cj);
+ GNUNET_assert (0 ==
+ json_array_append_new (challenges,
+ cj));
+ } /* end for all challenges */
+ recovery_information = json_pack ("{s:o, s:o, s:s?, s:s, s:I}",
+ "challenges", challenges,
+ "policies", policies,
+ "secret_name", ri->secret_name,
+ "provider_url", pd->backend_url,
+ "version", (json_int_t) ri->version);
+ GNUNET_assert (NULL != recovery_information);
+ GNUNET_assert (0 ==
+ json_object_set_new (rss->state,
+ "recovery_information",
+ recovery_information));
+ {
+ json_t *rd;
+
+ rd = ANASTASIS_recovery_serialize (pd->recovery);
+ if (NULL == rd)
+ {
+ GNUNET_break (0);
+ set_state (rss->state,
+ ANASTASIS_GENERIC_STATE_ERROR);
+ rss->cb (rss->cb_cls,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ rss->state);
+ free_rss (rss);
+ return;
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (rss->state,
+ "recovery_document",
+ rd));
+ }
+ /* In case there is an old error remove it! */
+ (void) json_object_del (rss->state,
+ "recovery_error");
+ set_state (rss->state,
+ ANASTASIS_RECOVERY_STATE_SECRET_SELECTING);
+ rss->cb (rss->cb_cls,
+ TALER_EC_NONE,
+ rss->state);
+ free_rss (rss);
+}
+
+
+/**
+ * Try to launch recovery at provider @a provider_url with config @a p_cfg.
+ *
+ * @param[in,out] rss recovery context
+ * @param provider_url base URL of the provider to try
+ * @param p_cfg configuration of the provider
+ * @return true if a recovery was launched
+ */
+static bool
+launch_recovery (struct RecoverSecretState *rss,
+ const char *provider_url,
+ const json_t *p_cfg)
+{
+ struct PolicyDownloadEntry *pd = GNUNET_new (struct PolicyDownloadEntry);
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("salt",
+ &pd->salt),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (p_cfg,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "No salt for `%s', provider offline?\n",
+ provider_url);
+ GNUNET_free (pd);
+ return false;
+ }
+ pd->backend_url = GNUNET_strdup (provider_url);
+ pd->rss = rss;
+ pd->recovery = ANASTASIS_recovery_begin (ANASTASIS_REDUX_ctx_,
+ rss->id_data,
+ rss->have_version
+ ? rss->version
+ : 0,
+ pd->backend_url,
+ &pd->salt,
+ &policy_lookup_cb,
+ pd,
+ &core_early_secret_cb,
+ pd);
+ if (NULL != pd->recovery)
+ {
+ GNUNET_CONTAINER_DLL_insert (rss->pd_head,
+ rss->pd_tail,
+ pd);
+ return true;
+ }
+ GNUNET_free (pd->backend_url);
+ GNUNET_free (pd);
+ return false;
+}
+
+
+/**
+ * We finished downloading /config from all providers, merge
+ * into the main state, trigger the continuation and free our
+ * state.
+ *
+ * @param[in] rss main state to merge into
+ */
+static void
+providers_complete (struct RecoverSecretState *rss)
+{
+ bool launched = false;
+ struct RecoveryStartStateProviderEntry *pe;
+ json_t *tlist;
+
+ tlist = json_object_get (rss->state,
+ "authentication_providers");
+ if (NULL == tlist)
+ {
+ tlist = json_object ();
+ GNUNET_assert (NULL != tlist);
+ GNUNET_assert (0 ==
+ json_object_set_new (rss->state,
+ "authentication_providers",
+ tlist));
+ }
+ while (NULL != (pe = rss->pe_head))
+ {
+ json_t *provider_list;
+
+ GNUNET_CONTAINER_DLL_remove (rss->pe_head,
+ rss->pe_tail,
+ pe);
+ provider_list = json_object_get (pe->istate,
+ "authentication_providers");
+ /* merge provider_list into tlist (overriding existing entries) */
+ if (NULL != provider_list)
+ {
+ const char *url;
+ json_t *value;
+
+ json_object_foreach (provider_list, url, value) {
+ GNUNET_assert (0 ==
+ json_object_set (tlist,
+ url,
+ value));
+ }
+ }
+ json_decref (pe->istate);
+ GNUNET_free (pe);
+ }
+
+ /* now iterate over providers and begin downloading */
+ if (NULL != rss->provider_url)
+ {
+ json_t *p_cfg;
+
+ p_cfg = json_object_get (tlist,
+ rss->provider_url);
+ if (NULL != p_cfg)
+ launched = launch_recovery (rss,
+ rss->provider_url,
+ p_cfg);
+ }
+ else
+ {
+ json_t *p_cfg;
+ const char *provider_url;
+
+ json_object_foreach (tlist, provider_url, p_cfg)
+ {
+ launched |= launch_recovery (rss,
+ provider_url,
+ p_cfg);
+ }
+ }
+ if (! launched)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "No provider online, need user to specify different provider!\n");
+ return_no_policy (rss,
+ true);
+ return;
+ }
+}
+
+
+/**
+ * Function called when the complete information about a provider
+ * was added to @a new_state.
+ *
+ * @param cls a `struct RecoveryStartStateProviderEntry`
+ * @param error error code
+ * @param new_state resulting new state
+ */
+static void
+provider_added_cb (void *cls,
+ enum TALER_ErrorCode error,
+ json_t *new_state)
+{
+ struct RecoveryStartStateProviderEntry *pe = cls;
+
+ pe->ra = NULL;
+ pe->istate = json_incref (new_state);
+ pe->ec = error;
+ pe->rss->pending--;
+ if (0 == pe->rss->pending)
+ providers_complete (pe->rss);
+}
+
+
+/**
+ * Start to query provider for recovery document.
+ *
+ * @param[in,out] rss overall recovery state
+ * @param provider_url base URL of the provider to query
+ */
+static void
+begin_query_provider (struct RecoverSecretState *rss,
+ const char *provider_url)
+{
+ struct RecoveryStartStateProviderEntry *pe;
+ json_t *istate;
+
+ pe = GNUNET_new (struct RecoveryStartStateProviderEntry);
+ pe->rss = rss;
+ istate = json_object ();
+ GNUNET_assert (NULL != istate);
+ GNUNET_CONTAINER_DLL_insert (rss->pe_head,
+ rss->pe_tail,
+ pe);
+ pe->ra = ANASTASIS_REDUX_add_provider_to_state_ (provider_url,
+ istate,
+ &provider_added_cb,
+ pe);
+ json_decref (istate);
+ if (NULL != pe->ra)
+ rss->pending++;
+}
+
+
+struct ANASTASIS_ReduxAction *
+ANASTASIS_REDUX_recovery_challenge_begin_ (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ json_t *version;
+ json_t *providers;
+ const json_t *attributes;
+ struct RecoverSecretState *rss;
+ const char *provider_url;
+
+ providers = json_object_get (state,
+ "authentication_providers");
+ if ( (NULL == providers) ||
+ (! json_is_object (providers)) )
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'authentication_providers' missing");
+ return NULL;
+ }
+ attributes = json_object_get (arguments,
+ "identity_attributes");
+ if ( (NULL == attributes) ||
+ (! json_is_object (attributes)) )
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'identity_attributes' missing");
+ return NULL;
+ }
+ rss = GNUNET_new (struct RecoverSecretState);
+ rss->id_data = json_incref ((json_t *) attributes);
+ version = json_object_get (arguments,
+ "version");
+ if (NULL != version)
+ {
+ rss->version = (unsigned int) json_integer_value (version);
+ rss->have_version = true;
+ }
+ rss->state = json_incref (state);
+ rss->cb = cb;
+ rss->cb_cls = cb_cls;
+ rss->pending = 1; /* decremented after initialization loop */
+
+ provider_url = json_string_value (json_object_get (arguments,
+ "provider_url"));
+ if (NULL != provider_url)
+ {
+ rss->provider_url = GNUNET_strdup (provider_url);
+ begin_query_provider (rss,
+ provider_url);
+ }
+ else
+ {
+ json_t *prov;
+ const char *url;
+
+ json_object_foreach (providers, url, prov) {
+ begin_query_provider (rss,
+ url);
+ }
+ }
+ rss->pending--;
+ if (0 == rss->pending)
+ {
+ providers_complete (rss);
+ if (NULL == rss->cb)
+ return NULL;
+ }
+ rss->ra.cleanup = &free_rss;
+ rss->ra.cleanup_cls = rss;
+ return &rss->ra;
+}
diff --git a/src/reducer/anastasis_api_redux.c b/src/reducer/anastasis_api_redux.c
new file mode 100644
index 0000000..eb7e362
--- /dev/null
+++ b/src/reducer/anastasis_api_redux.c
@@ -0,0 +1,1730 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 2021 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/anastasis_api_redux.c
+ * @brief anastasis reducer api
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ */
+#include <platform.h>
+#include <jansson.h>
+#include "anastasis_redux.h"
+#include "anastasis_error_codes.h"
+#include <taler/taler_json_lib.h>
+#include "anastasis_api_redux.h"
+#include <dlfcn.h>
+
+
+/**
+ * How long do we wait at most for a /config reply from an Anastasis provider.
+ * 60s is very generous, given the tiny bandwidth required, even for the most
+ * remote locations.
+ */
+#define CONFIG_GENERIC_TIMEOUT GNUNET_TIME_UNIT_MINUTES
+
+
+#define GENERATE_STRING(STRING) #STRING,
+static const char *generic_strings[] = {
+ ANASTASIS_GENERIC_STATES (GENERATE_STRING)
+};
+#undef GENERATE_STRING
+
+
+/**
+ * #ANASTASIS_REDUX_add_provider_to_state_ waiting for the
+ * configuration request to complete or fail.
+ */
+struct ConfigReduxWaiting
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct ConfigReduxWaiting *prev;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct ConfigReduxWaiting *next;
+
+ /**
+ * Associated redux action.
+ */
+ struct ANASTASIS_ReduxAction ra;
+
+ /**
+ * Config request we are waiting for.
+ */
+ struct ConfigRequest *cr;
+
+ /**
+ * State we are processing.
+ */
+ json_t *state;
+
+ /**
+ * Function to call with updated @e state.
+ */
+ ANASTASIS_ActionCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+};
+
+
+/**
+ * Anastasis authorization method configuration
+ */
+struct AuthorizationMethodConfig
+{
+ /**
+ * Type of the method, i.e. "question".
+ */
+ char *type;
+
+ /**
+ * Fee charged for accessing key share using this method.
+ */
+ struct TALER_Amount usage_fee;
+};
+
+
+/**
+ * State for a "get config" operation.
+ */
+struct ConfigRequest
+{
+
+ /**
+ * Kept in a DLL, given that we may have multiple backends.
+ */
+ struct ConfigRequest *next;
+
+ /**
+ * Kept in a DLL, given that we may have multiple backends.
+ */
+ struct ConfigRequest *prev;
+
+ /**
+ * Head of DLL of REDUX operations waiting for an answer.
+ */
+ struct ConfigReduxWaiting *w_head;
+
+ /**
+ * Tail of DLL of REDUX operations waiting for an answer.
+ */
+ struct ConfigReduxWaiting *w_tail;
+
+ /**
+ * Obtained status code.
+ */
+ unsigned int http_status;
+
+ /**
+ * The /config GET operation handle.
+ */
+ struct ANASTASIS_ConfigOperation *co;
+
+ /**
+ * URL of the anastasis backend.
+ */
+ char *url;
+
+ /**
+ * Business name of the anastasis backend.
+ */
+ char *business_name;
+
+ /**
+ * currency used by the anastasis backend.
+ */
+ char *currency;
+
+ /**
+ * Array of authorization methods supported by the server.
+ */
+ struct AuthorizationMethodConfig *methods;
+
+ /**
+ * Length of the @e methods array.
+ */
+ unsigned int methods_length;
+
+ /**
+ * Maximum size of an upload in megabytes.
+ */
+ uint32_t storage_limit_in_megabytes;
+
+ /**
+ * Annual fee for an account / policy upload.
+ */
+ struct TALER_Amount annual_fee;
+
+ /**
+ * Fee for a truth upload.
+ */
+ struct TALER_Amount truth_upload_fee;
+
+ /**
+ * Maximum legal liability for data loss covered by the
+ * provider.
+ */
+ struct TALER_Amount liability_limit;
+
+ /**
+ * Server salt.
+ */
+ struct ANASTASIS_CRYPTO_ProviderSaltP salt;
+
+ /**
+ * Task to timeout /config requests.
+ */
+ struct GNUNET_SCHEDULER_Task *tt;
+
+ /**
+ * Status of the /config request.
+ */
+ enum TALER_ErrorCode ec;
+};
+
+
+/**
+ * Reducer API's CURL context handle.
+ */
+struct GNUNET_CURL_Context *ANASTASIS_REDUX_ctx_;
+
+/**
+ * JSON containing country specific identity attributes to ask the user for.
+ */
+static json_t *redux_id_attr;
+
+/**
+ * Head of DLL of Anastasis backend configuration requests.
+ */
+static struct ConfigRequest *cr_head;
+
+/**
+ * Tail of DLL of Anastasis backend configuration requests.
+ */
+static struct ConfigRequest *cr_tail;
+
+/**
+ * JSON containing country specific information.
+ */
+static json_t *redux_countries;
+
+/**
+ * List of Anastasis providers.
+ */
+static json_t *provider_list;
+
+
+/**
+ * Extract the mode of a state from json
+ *
+ * @param state the state to operate on
+ * @return "backup_state" or "recovery_state"
+ */
+static const char *
+get_state_mode (const json_t *state)
+{
+ if (json_object_get (state, "backup_state"))
+ return "backup_state";
+ if (json_object_get (state, "recovery_state"))
+ return "recovery_state";
+ GNUNET_assert (0);
+ return NULL;
+}
+
+
+enum ANASTASIS_GenericState
+ANASTASIS_generic_state_from_string_ (const char *state_string)
+{
+ for (enum ANASTASIS_GenericState i = 0;
+ i < sizeof (generic_strings) / sizeof(*generic_strings);
+ i++)
+ if (0 == strcmp (state_string,
+ generic_strings[i]))
+ return i;
+ return ANASTASIS_GENERIC_STATE_ERROR;
+}
+
+
+const char *
+ANASTASIS_generic_state_to_string_ (enum ANASTASIS_GenericState gs)
+{
+ if ( (gs < 0) ||
+ (gs >= sizeof (generic_strings) / sizeof(*generic_strings)) )
+ {
+ GNUNET_break_op (0);
+ return NULL;
+ }
+ return generic_strings[gs];
+}
+
+
+void
+ANASTASIS_redux_fail_ (ANASTASIS_ActionCallback cb,
+ void *cb_cls,
+ enum TALER_ErrorCode ec,
+ const char *detail)
+{
+ json_t *estate;
+
+ estate = json_pack ("{s:s?, s:I, s:s}",
+ "detail", detail,
+ "code", (json_int_t) ec,
+ "hint", TALER_ErrorCode_get_hint (ec));
+ cb (cb_cls,
+ ec,
+ estate);
+ json_decref (estate);
+}
+
+
+/**
+ * Transition the @a state to @a gs.
+ *
+ * @param[in,out] state to transition
+ * @param gs state to transition to
+ */
+static void
+redux_transition (json_t *state,
+ enum ANASTASIS_GenericState gs)
+{
+ const char *s_mode = get_state_mode (state);
+
+ GNUNET_assert (0 ==
+ json_object_set_new (
+ state,
+ s_mode,
+ json_string (
+ ANASTASIS_generic_state_to_string_ (gs))));
+
+}
+
+
+void
+ANASTASIS_redux_init (struct GNUNET_CURL_Context *ctx)
+{
+ ANASTASIS_REDUX_ctx_ = ctx;
+}
+
+
+/**
+ * Function to free a #ConfigRequest, an async operation.
+ *
+ * @param cr state for a "get config" operation
+ */
+static void
+free_config_request (struct ConfigRequest *cr)
+{
+ GNUNET_assert (NULL == cr->w_head);
+ if (NULL != cr->co)
+ ANASTASIS_config_cancel (cr->co);
+ if (NULL != cr->tt)
+ GNUNET_SCHEDULER_cancel (cr->tt);
+ GNUNET_free (cr->currency);
+ GNUNET_free (cr->url);
+ GNUNET_free (cr->business_name);
+ for (unsigned int i = 0; i<cr->methods_length; i++)
+ GNUNET_free (cr->methods[i].type);
+ GNUNET_free (cr->methods);
+ GNUNET_free (cr);
+}
+
+
+void
+ANASTASIS_redux_done ()
+{
+ struct ConfigRequest *cr;
+
+ while (NULL != (cr = cr_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (cr_head,
+ cr_tail,
+ cr);
+ free_config_request (cr);
+ }
+ ANASTASIS_REDUX_ctx_ = NULL;
+ if (NULL != redux_countries)
+ {
+ json_decref (redux_countries);
+ redux_countries = NULL;
+ }
+ if (NULL != redux_id_attr)
+ {
+ json_decref (redux_id_attr);
+ redux_id_attr = NULL;
+ }
+ if (NULL != provider_list)
+ {
+ json_decref (provider_list);
+ provider_list = NULL;
+ }
+}
+
+
+const json_t *
+ANASTASIS_redux_countries_init_ (void)
+{
+ char *dn;
+ json_error_t error;
+
+ if (NULL != redux_countries)
+ return redux_countries;
+
+ {
+ char *path;
+
+ path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
+ if (NULL == path)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ GNUNET_asprintf (&dn,
+ "%s/redux.countries.json",
+ path);
+ GNUNET_free (path);
+ }
+ redux_countries = json_load_file (dn,
+ JSON_COMPACT,
+ &error);
+ if (NULL == redux_countries)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse `%s': %s at %d:%d (%d)\n",
+ dn,
+ error.text,
+ error.line,
+ error.column,
+ error.position);
+ GNUNET_free (dn);
+ return NULL;
+ }
+ GNUNET_free (dn);
+ return redux_countries;
+}
+
+
+/**
+ * Abort waiting for /config reply.
+ *
+ * @param cls a `struct ConfigReduxWaiting` handle.
+ */
+static void
+abort_provider_config_cb (void *cls)
+{
+ struct ConfigReduxWaiting *w = cls;
+ struct ConfigRequest *cr = w->cr;
+
+ GNUNET_CONTAINER_DLL_remove (cr->w_head,
+ cr->w_tail,
+ w);
+ json_decref (w->state);
+ GNUNET_free (w);
+}
+
+
+/**
+ * Notify anyone waiting on @a cr that the request is done
+ * (successful or failed).
+ *
+ * @param[in,out] cr request that completed
+ */
+static void
+notify_waiting (struct ConfigRequest *cr)
+{
+ struct ConfigReduxWaiting *w;
+
+ while (NULL != (w = cr->w_head))
+ {
+ json_t *provider_list;
+ json_t *prov;
+
+ if (NULL == (provider_list = json_object_get (w->state,
+ "authentication_providers")))
+ {
+ GNUNET_assert (0 ==
+ json_object_set_new (w->state,
+ "authentication_providers",
+ provider_list = json_object ()));
+ }
+ provider_list = json_object_get (w->state,
+ "authentication_providers");
+ GNUNET_assert (NULL != provider_list);
+
+ if (TALER_EC_NONE != cr->ec)
+ {
+ prov = json_pack ("{s:I, s:I}",
+ "error_code",
+ (json_int_t) cr->ec,
+ "http_status",
+ (json_int_t) cr->http_status);
+ }
+ else
+ {
+ json_t *methods_list;
+
+ methods_list = json_array ();
+ GNUNET_assert (NULL != methods_list);
+ for (unsigned int i = 0; i<cr->methods_length; i++)
+ {
+ struct AuthorizationMethodConfig *method = &cr->methods[i];
+ json_t *mj = json_pack ("{s:s, s:o}",
+ "type",
+ method->type,
+ "usage_fee",
+ TALER_JSON_from_amount (&method->usage_fee));
+
+ GNUNET_assert (NULL != mj);
+ GNUNET_assert (0 ==
+ json_array_append_new (methods_list,
+ mj));
+ }
+ prov = json_pack ("{s:o, s:o, s:o, s:o, s:s,"
+ " s:s, s:I, s:o, s:I}",
+ "methods",
+ methods_list,
+ "annual_fee",
+ TALER_JSON_from_amount (&cr->annual_fee),
+ "truth_upload_fee",
+ TALER_JSON_from_amount (&cr->truth_upload_fee),
+ "liability_limit",
+ TALER_JSON_from_amount (&cr->liability_limit),
+ "currency",
+ cr->currency,
+ /* 6 */
+ "business_name",
+ cr->business_name,
+ "storage_limit_in_megabytes",
+ (json_int_t) cr->storage_limit_in_megabytes,
+ "salt",
+ GNUNET_JSON_from_data_auto (&cr->salt),
+ "http_status",
+ (json_int_t) cr->http_status);
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (provider_list,
+ cr->url,
+ prov));
+ w->cb (w->cb_cls,
+ cr->ec,
+ w->state);
+ abort_provider_config_cb (w);
+ }
+
+}
+
+
+/**
+ * Function called with the results of a #ANASTASIS_get_config().
+ *
+ * @param cls closure
+ * @param http_status HTTP status of the request
+ * @param acfg anastasis configuration
+ */
+static void
+config_cb (void *cls,
+ unsigned int http_status,
+ const struct ANASTASIS_Config *acfg)
+{
+ struct ConfigRequest *cr = cls;
+
+ cr->co = NULL;
+ GNUNET_SCHEDULER_cancel (cr->tt);
+ cr->tt = NULL;
+ cr->http_status = http_status;
+ if (MHD_HTTP_OK != http_status)
+ cr->ec = TALER_EC_ANASTASIS_REDUCER_PROVIDER_CONFIG_FAILED;
+ if ( (MHD_HTTP_OK == http_status) &&
+ (NULL == acfg) )
+ {
+ cr->http_status = MHD_HTTP_NOT_FOUND;
+ cr->ec = TALER_EC_ANASTASIS_REDUCER_PROVIDER_CONFIG_FAILED;
+ }
+ else if (NULL != acfg)
+ {
+ if (0 == acfg->storage_limit_in_megabytes)
+ {
+ cr->http_status = 0;
+ cr->ec = TALER_EC_ANASTASIS_REDUCER_PROVIDER_INVALID_CONFIG;
+ }
+ else
+ {
+ cr->currency = GNUNET_strdup (acfg->currency);
+ cr->business_name = GNUNET_strdup (acfg->business_name);
+ cr->methods = GNUNET_new_array (acfg->methods_length,
+ struct AuthorizationMethodConfig);
+ for (unsigned int i = 0; i<acfg->methods_length; i++)
+ {
+ cr->methods[i].type = GNUNET_strdup (acfg->methods[i].type);
+ cr->methods[i].usage_fee = acfg->methods[i].usage_fee;
+ }
+ cr->methods_length = acfg->methods_length;
+ cr->storage_limit_in_megabytes = acfg->storage_limit_in_megabytes;
+ cr->annual_fee = acfg->annual_fee;
+ cr->truth_upload_fee = acfg->truth_upload_fee;
+ cr->liability_limit = acfg->liability_limit;
+ cr->salt = acfg->salt;
+ }
+ }
+ notify_waiting (cr);
+}
+
+
+/**
+ * Aborts a "get config" after timeout.
+ *
+ * @param cls closure for a "get config" request
+ */
+static void
+config_request_timeout (void *cls)
+{
+ struct ConfigRequest *cr = cls;
+
+ cr->tt = NULL;
+ ANASTASIS_config_cancel (cr->co);
+ cr->co = NULL;
+ cr->http_status = 0;
+ cr->ec = TALER_EC_GENERIC_TIMEOUT;
+ notify_waiting (cr);
+}
+
+
+/**
+ * Schedule job to obtain Anastasis provider configuration at @a url.
+ *
+ * @param url base URL of Anastasis provider
+ * @return check config handle
+ */
+static struct ConfigRequest *
+check_config (const char *url)
+{
+ struct ConfigRequest *cr;
+
+ for (cr = cr_head; NULL != cr; cr = cr->next)
+ {
+ if (0 != strcmp (url,
+ cr->url))
+ continue;
+ if (NULL != cr->co)
+ return cr; /* already on it */
+ break;
+ }
+ if (NULL == cr)
+ {
+ cr = GNUNET_new (struct ConfigRequest);
+ cr->url = GNUNET_strdup (url);
+ GNUNET_CONTAINER_DLL_insert (cr_head,
+ cr_tail,
+ cr);
+ }
+ cr->co = ANASTASIS_get_config (ANASTASIS_REDUX_ctx_,
+ cr->url,
+ &config_cb,
+ cr);
+ if (NULL == cr->co)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ else
+ {
+ cr->tt = GNUNET_SCHEDULER_add_delayed (CONFIG_GENERIC_TIMEOUT,
+ &config_request_timeout,
+ cr);
+ }
+ return cr;
+}
+
+
+/**
+ * Begin asynchronous check for provider configurations.
+ *
+ * @param currencies the currencies to initiate the provider checks for
+ * @param[in,out] state to set provider list for
+ * @return #TALER_EC_NONE on success
+ */
+static enum TALER_ErrorCode
+begin_provider_config_check (const json_t *currencies,
+ json_t *state)
+{
+ if (NULL == provider_list)
+ {
+ json_error_t error;
+ char *dn;
+ char *path;
+
+ path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
+ if (NULL == path)
+ {
+ GNUNET_break (0);
+ return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ }
+ GNUNET_asprintf (&dn,
+ "%s/provider-list.json",
+ path);
+ GNUNET_free (path);
+ provider_list = json_load_file (dn,
+ JSON_COMPACT,
+ &error);
+ if (NULL == provider_list)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse `%s': %s at %d:%d (%d)\n",
+ dn,
+ error.text,
+ error.line,
+ error.column,
+ error.position);
+ GNUNET_free (dn);
+ return TALER_EC_ANASTASIS_REDUCER_RESOURCE_MALFORMED;
+ }
+ GNUNET_free (dn);
+ }
+
+ {
+ size_t index;
+ json_t *provider;
+ const json_t *provider_arr = json_object_get (provider_list,
+ "anastasis_provider");
+ json_t *pl;
+
+ pl = json_object ();
+ json_array_foreach (provider_arr, index, provider)
+ {
+ const char *url;
+ const char *cur;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("url",
+ &url),
+ GNUNET_JSON_spec_string ("currency",
+ &cur),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (provider,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ json_decref (pl);
+ return TALER_EC_ANASTASIS_REDUCER_RESOURCE_MALFORMED;
+ }
+
+ {
+ bool found = false;
+ json_t *cu;
+ size_t off;
+
+ json_array_foreach (currencies, off, cu)
+ {
+ const char *currency;
+
+ currency = json_string_value (cu);
+ if (NULL == currency)
+ {
+ json_decref (pl);
+ return TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID;
+ }
+ found = (0 == strcasecmp (currency,
+ cur));
+ }
+ if (! found)
+ continue;
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (pl,
+ url,
+ json_object ()));
+ check_config (url);
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (state,
+ "authentication_providers",
+ pl));
+ }
+ return TALER_EC_NONE;
+}
+
+
+/**
+ * Function to validate an input by regular expression ("validation-regex").
+ *
+ * @param input text to validate
+ * @param regexp regular expression to validate
+ * @return true if validation passed, else false
+ */
+static bool
+validate_regex (const char *input,
+ const char *regexp)
+{
+ regex_t regex;
+
+ if (0 != regcomp (&regex,
+ regexp,
+ REG_EXTENDED))
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to compile regular expression `%s'.",
+ regexp);
+ return true;
+ }
+ /* check if input has correct form */
+ if (0 != regexec (&regex,
+ input,
+ 0,
+ NULL,
+ 0))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Input `%s' does not match regex `%s'\n",
+ input,
+ regexp);
+ regfree (&regex);
+ return false;
+ }
+ regfree (&regex);
+ return true;
+}
+
+
+/**
+ * Function to load json containing country specific identity
+ * attributes. Uses a single-slot cache to avoid loading
+ * exactly the same attributes twice.
+ *
+ * @param country_code country code (e.g. "de")
+ * @return NULL on error
+ */
+static const json_t *
+redux_id_attr_init (const char *country_code)
+{
+ static char redux_id_cc[3];
+ char *dn;
+ json_error_t error;
+
+ if (0 == strcmp (country_code,
+ redux_id_cc))
+ return redux_id_attr;
+
+ if (NULL != redux_id_attr)
+ {
+ json_decref (redux_id_attr);
+ redux_id_attr = NULL;
+ }
+ {
+ char *path;
+
+ path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
+ if (NULL == path)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ GNUNET_asprintf (&dn,
+ "%s/redux.%s.json",
+ path,
+ country_code);
+ GNUNET_free (path);
+ }
+ redux_id_attr = json_load_file (dn,
+ JSON_COMPACT,
+ &error);
+ if (NULL == redux_id_attr)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse `%s': %s at %d:%d (%d)\n",
+ dn,
+ error.text,
+ error.line,
+ error.column,
+ error.position);
+ GNUNET_free (dn);
+ return NULL;
+ }
+ GNUNET_free (dn);
+ strncpy (redux_id_cc,
+ country_code,
+ sizeof (redux_id_cc));
+ redux_id_cc[2] = '\0';
+ return redux_id_attr;
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "select_continent" action.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+static struct ANASTASIS_ReduxAction *
+select_continent (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ const json_t *redux_countries = ANASTASIS_redux_countries_init_ ();
+ const json_t *root = json_object_get (redux_countries,
+ "countries");
+ const json_t *continent;
+ json_t *countries;
+
+ if (NULL == root)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_RESOURCE_MALFORMED,
+ "'countries' missing");
+ return NULL;
+ }
+ if (NULL == arguments)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "arguments missing");
+ return NULL;
+ }
+ continent = json_object_get (arguments,
+ "continent");
+ if (NULL == continent)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'continent' missing");
+ return NULL;
+ }
+ countries = json_array ();
+ GNUNET_assert (NULL != countries);
+ {
+ size_t index;
+ const json_t *country;
+ bool found = false;
+
+ json_array_foreach (root, index, country)
+ {
+ json_t *temp_continent = json_object_get (country,
+ "continent");
+ if (1 == json_equal (continent,
+ temp_continent))
+ {
+ GNUNET_assert (0 ==
+ json_array_append (countries,
+ (json_t *) country));
+ found = true;
+ }
+ }
+ if (! found)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'continent' unknown");
+ return NULL;
+ }
+ }
+ redux_transition (state,
+ ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING);
+ GNUNET_assert (0 ==
+ json_object_set (state,
+ "selected_continent",
+ (json_t *) continent));
+ GNUNET_assert (0 ==
+ json_object_set_new (state,
+ "countries",
+ countries));
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "select_country" action.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return #ANASTASIS_ReduxAction
+ */
+static struct ANASTASIS_ReduxAction *
+select_country (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ const json_t *required_attrs;
+ const json_t *country_code;
+ const json_t *currencies;
+ const json_t *redux_id_attr;
+
+ if (NULL == arguments)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "arguments missing");
+ return NULL;
+ }
+ country_code = json_object_get (arguments,
+ "country_code");
+ if (NULL == country_code)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'country_code' missing");
+ return NULL;
+ }
+
+ {
+ json_t *countries = json_object_get (state,
+ "countries");
+ size_t index;
+ json_t *country;
+ bool found = false;
+
+ json_array_foreach (countries, index, country)
+ {
+ json_t *cc = json_object_get (country,
+ "code");
+ if (1 == json_equal (country_code,
+ cc))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (! found)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "specified country not on selected continent");
+ return NULL;
+ }
+ }
+
+ currencies = json_object_get (arguments,
+ "currencies");
+ if ( (NULL == currencies) ||
+ (! json_is_array (currencies)) )
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'currencies' missing");
+ return NULL;
+ }
+ /* We now have an idea of the currency, begin fetching
+ provider /configs (we likely need them later) */
+ {
+ enum TALER_ErrorCode ec;
+
+ ec = begin_provider_config_check (currencies,
+ state);
+ if (TALER_EC_NONE != ec)
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ ec,
+ NULL);
+ return NULL;
+ }
+ }
+ redux_id_attr = redux_id_attr_init (json_string_value (country_code));
+ if (NULL == redux_id_attr)
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_RESOURCE_MISSING,
+ json_string_value (country_code));
+ return NULL;
+ }
+ required_attrs = json_object_get (redux_id_attr,
+ "required_attributes");
+ if (NULL == required_attrs)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_RESOURCE_MALFORMED,
+ "'required_attributes' missing");
+ return NULL;
+ }
+ redux_transition (state,
+ ANASTASIS_GENERIC_STATE_USER_ATTRIBUTES_COLLECTING);
+ GNUNET_assert (0 ==
+ json_object_set (state,
+ "selected_country",
+ (json_t *) country_code));
+ GNUNET_assert (0 ==
+ json_object_set (state,
+ "currencies",
+ (json_t *) currencies));
+ GNUNET_assert (0 ==
+ json_object_set (state,
+ "required_attributes",
+ (json_t *) required_attrs));
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "unselect_continent" action.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+static struct ANASTASIS_ReduxAction *
+unselect_continent (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ redux_transition (state,
+ ANASTASIS_GENERIC_STATE_CONTINENT_SELECTING);
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+struct ANASTASIS_ReduxAction *
+ANASTASIS_REDUX_add_provider_to_state_ (const char *url,
+ json_t *state,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ struct ConfigRequest *cr;
+ struct ConfigReduxWaiting *w;
+
+ cr = check_config (url);
+ w = GNUNET_new (struct ConfigReduxWaiting);
+ w->cr = cr;
+ w->state = json_incref (state);
+ w->cb = cb;
+ w->cb_cls = cb_cls;
+ w->ra.cleanup = &abort_provider_config_cb;
+ w->ra.cleanup_cls = w;
+ GNUNET_CONTAINER_DLL_insert (cr->w_head,
+ cr->w_tail,
+ w);
+ if (NULL == cr->co)
+ {
+ notify_waiting (cr);
+ return NULL;
+ }
+ return &w->ra;
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "enter_user_attributes" action.
+ * Returns an #ANASTASIS_ReduxAction if operation is async.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+static struct ANASTASIS_ReduxAction *
+enter_user_attributes (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ const json_t *attributes;
+ const json_t *required_attributes;
+
+ if (NULL == arguments)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "arguments missing");
+ return NULL;
+ }
+ attributes = json_object_get (arguments,
+ "identity_attributes");
+ if (NULL == attributes)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'identity_attributes' missing");
+ return NULL;
+ }
+ GNUNET_assert (0 ==
+ json_object_set (state,
+ "identity_attributes",
+ (json_t *) attributes));
+
+ /* Verify required attributes are present and well-formed */
+ required_attributes = json_object_get (state,
+ "required_attributes");
+ if ( (NULL == required_attributes) ||
+ (! json_is_array (required_attributes)) )
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'required_attributes' must be an array");
+ return NULL;
+ }
+ {
+ size_t index;
+ json_t *required_attribute;
+
+ json_array_foreach (required_attributes, index, required_attribute)
+ {
+ const char *name;
+ const char *attribute_value;
+ const char *regexp = NULL;
+ const char *reglog = NULL;
+ int optional = false;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("name",
+ &name),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("validation-regex",
+ &regexp)),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("validation-logic",
+ &reglog)),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_boolean ("optional",
+ &optional)),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (required_attribute,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ "'required_attributes' lacks required fields");
+ return NULL;
+ }
+ attribute_value = json_string_value (json_object_get (attributes,
+ name));
+ if (NULL == attribute_value)
+ {
+ if (optional)
+ continue;
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Request is missing required attribute `%s'\n",
+ name);
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_GENERIC_PARAMETER_MISSING,
+ name);
+ return NULL;
+ }
+ if ( (NULL != regexp) &&
+ (! validate_regex (attribute_value,
+ regexp)) )
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_REGEX_FAILED,
+ name);
+ return NULL;
+ }
+
+ if (NULL != reglog)
+ {
+ bool (*regfun)(const char *);
+
+ regfun = dlsym (RTLD_DEFAULT,
+ reglog);
+ if (NULL == regfun)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Custom validation function `%s' is not available: %s\n",
+ reglog,
+ dlerror ());
+ }
+ else if (! regfun (attribute_value))
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_VALIDATION_FAILED,
+ name);
+ return NULL;
+ }
+ }
+ } /* end for all attributes loop */
+ } /* end for all attributes scope */
+
+ /* Transition based on mode */
+ {
+ const char *s_mode = get_state_mode (state);
+
+ if (0 == strcmp (s_mode,
+ "backup_state"))
+ {
+ GNUNET_assert (0 ==
+ json_object_set_new (
+ state,
+ "backup_state",
+ json_string (
+ ANASTASIS_backup_state_to_string_ (
+ ANASTASIS_BACKUP_STATE_AUTHENTICATIONS_EDITING))));
+ return ANASTASIS_REDUX_backup_begin_ (state,
+ arguments,
+ cb,
+ cb_cls);
+ }
+ else
+ {
+ GNUNET_assert (0 ==
+ json_object_set_new (
+ state,
+ "recovery_state",
+ json_string (
+ ANASTASIS_recovery_state_to_string_ (
+ ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING))));
+ return ANASTASIS_REDUX_recovery_challenge_begin_ (state,
+ arguments,
+ cb,
+ cb_cls);
+ }
+ }
+}
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "add_provider" action. Adds another Anastasis provider
+ * to the list of available providers for storing information.
+ *
+ * @param state state to operate on
+ * @param arguments arguments with a provider URL to add
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ */
+static struct ANASTASIS_ReduxAction *
+add_provider (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ if (ANASTASIS_add_provider_ (state,
+ arguments,
+ cb,
+ cb_cls))
+ return NULL;
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+bool
+ANASTASIS_add_provider_ (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ json_t *urls;
+ json_t *tlist;
+
+ tlist = json_object_get (state,
+ "authentication_providers");
+ if (NULL == tlist)
+ {
+ tlist = json_object ();
+ GNUNET_assert (NULL != tlist);
+ GNUNET_assert (0 ==
+ json_object_set_new (state,
+ "authentication_providers",
+ tlist));
+ }
+ if (NULL == arguments)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "arguments missing");
+ return true;
+ }
+ urls = json_object_get (arguments,
+ "urls");
+ if (NULL == urls)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'urls' missing");
+ return true;
+ }
+ {
+ size_t index;
+ json_t *url;
+
+ json_array_foreach (urls, index, url)
+ {
+ const char *url_str = json_string_value (url);
+
+ if (NULL == url_str)
+ {
+ ANASTASIS_redux_fail_ (cb,
+ cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
+ "'urls' must be strings");
+ return true;
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (tlist,
+ url_str,
+ json_object ()));
+ }
+ }
+ return false;
+}
+
+
+struct ANASTASIS_ReduxAction *
+ANASTASIS_back_generic_decrement_ (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ const char *s_mode = get_state_mode (state);
+ const char *state_string = json_string_value (json_object_get (state,
+ s_mode));
+
+ (void) arguments;
+ GNUNET_assert (NULL != state_string);
+ if (0 == strcmp ("backup_state",
+ s_mode))
+ {
+ enum ANASTASIS_BackupState state_index;
+
+ state_index = ANASTASIS_backup_state_from_string_ (state_string);
+ GNUNET_assert (state_index > 0);
+ state_index = state_index - 1;
+
+ GNUNET_assert (0 ==
+ json_object_set_new (
+ state,
+ s_mode,
+ json_string (
+ ANASTASIS_backup_state_to_string_ (state_index))));
+ }
+ else
+ {
+ enum ANASTASIS_RecoveryState state_index;
+
+ state_index = ANASTASIS_recovery_state_from_string_ (state_string);
+ GNUNET_assert (state_index > 0);
+ state_index = state_index - 1;
+ GNUNET_assert (0 ==
+ json_object_set_new (
+ state,
+ s_mode,
+ json_string (
+ ANASTASIS_recovery_state_to_string_ (state_index))));
+ }
+ cb (cb_cls,
+ TALER_EC_NONE,
+ state);
+ return NULL;
+}
+
+
+/**
+ * Callback function which is called by the reducer in dependence of
+ * given state and action.
+ *
+ * @param state the previous state to operate on
+ * @param arguments the arguments needed by operation to operate on state
+ * @param cb Callback function which returns the new state
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel async actions, NULL if @a cb was already called
+ */
+typedef struct ANASTASIS_ReduxAction *
+(*DispatchHandler)(json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls);
+
+
+struct ANASTASIS_ReduxAction *
+ANASTASIS_redux_action (const json_t *state,
+ const char *action,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls)
+{
+ struct Dispatcher
+ {
+ enum ANASTASIS_GenericState redux_state;
+ const char *redux_action;
+ DispatchHandler fun;
+ } dispatchers[] = {
+ {
+ ANASTASIS_GENERIC_STATE_CONTINENT_SELECTING,
+ "select_continent",
+ &select_continent
+ },
+ {
+ ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING,
+ "unselect_continent",
+ &unselect_continent
+ },
+ {
+ ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING,
+ "select_country",
+ &select_country
+ },
+ {
+ ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING,
+ "select_continent",
+ &select_continent
+ },
+ {
+ ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING,
+ "unselect_continent",
+ &unselect_continent
+ },
+ {
+ ANASTASIS_GENERIC_STATE_USER_ATTRIBUTES_COLLECTING,
+ "enter_user_attributes",
+ &enter_user_attributes
+ },
+ {
+ ANASTASIS_GENERIC_STATE_USER_ATTRIBUTES_COLLECTING,
+ "add_provider",
+ &add_provider
+ },
+ {
+ ANASTASIS_GENERIC_STATE_USER_ATTRIBUTES_COLLECTING,
+ "back",
+ &ANASTASIS_back_generic_decrement_
+ },
+ { ANASTASIS_GENERIC_STATE_ERROR, NULL, NULL }
+ };
+ bool recovery_mode = false;
+ const char *s = json_string_value (json_object_get (state,
+ "backup_state"));
+ enum ANASTASIS_GenericState gs;
+
+ if (NULL == s)
+ {
+ s = json_string_value (json_object_get (state,
+ "recovery_state"));
+ if (NULL == s)
+ {
+ GNUNET_break_op (0);
+ cb (cb_cls,
+ TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
+ NULL);
+ return NULL;
+ }
+ recovery_mode = true;
+ }
+ gs = ANASTASIS_generic_state_from_string_ (s);
+ {
+ json_t *new_state;
+ struct ANASTASIS_ReduxAction *ret;
+
+ new_state = json_deep_copy (state);
+ GNUNET_assert (NULL != new_state);
+ if (gs != ANASTASIS_GENERIC_STATE_ERROR)
+ {
+ for (unsigned int i = 0; NULL != dispatchers[i].fun; i++)
+ {
+ if ( (gs == dispatchers[i].redux_state) &&
+ (0 == strcmp (action,
+ dispatchers[i].redux_action)) )
+ {
+ ret = dispatchers[i].fun (new_state,
+ arguments,
+ cb,
+ cb_cls);
+ json_decref (new_state);
+ return ret;
+ }
+ }
+ }
+ if (recovery_mode)
+ {
+ ret = ANASTASIS_recovery_action_ (new_state,
+ action,
+ arguments,
+ cb,
+ cb_cls);
+ }
+ else
+ {
+ ret = ANASTASIS_backup_action_ (new_state,
+ action,
+ arguments,
+ cb,
+ cb_cls);
+ }
+ json_decref (new_state);
+ return ret;
+ }
+}
+
+
+void
+ANASTASIS_redux_action_cancel (struct ANASTASIS_ReduxAction *ra)
+{
+ ra->cleanup (ra->cleanup_cls);
+}
+
+
+json_t *
+ANASTASIS_REDUX_load_continents_ ()
+{
+ const json_t *countries;
+ json_t *continents;
+ const json_t *redux_countries = ANASTASIS_redux_countries_init_ ();
+
+ if (NULL == redux_countries)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ countries = json_object_get (redux_countries,
+ "countries");
+ if (NULL == countries)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ continents = json_array ();
+ GNUNET_assert (NULL != continents);
+
+ {
+ json_t *country;
+ size_t index;
+
+ json_array_foreach (countries, index, country)
+ {
+ json_t *ex = NULL;
+ const json_t *continent;
+
+ continent = json_object_get (country,
+ "continent");
+ if ( (NULL == continent) ||
+ (! json_is_string (continent)) )
+ {
+ GNUNET_break (0);
+ continue;
+ }
+ {
+ size_t inner_index;
+ json_t *inner_continent;
+
+ json_array_foreach (continents, inner_index, inner_continent)
+ {
+ const json_t *name;
+
+ name = json_object_get (inner_continent,
+ "name");
+ if (1 == json_equal (continent,
+ name))
+ {
+ ex = inner_continent;
+ break;
+ }
+ }
+ }
+ if (NULL == ex)
+ {
+ ex = json_pack ("{s:O}",
+ "name",
+ continent);
+ GNUNET_assert (0 ==
+ json_array_append_new (continents,
+ ex));
+ }
+
+ {
+ json_t *i18n_continent;
+ json_t *name_ex;
+
+ i18n_continent = json_object_get (country,
+ "continent_i18n");
+ name_ex = json_object_get (ex,
+ "name_i18n");
+ if (NULL != i18n_continent)
+ {
+ const char *lang;
+ json_t *trans;
+
+ json_object_foreach (i18n_continent, lang, trans)
+ {
+ if (NULL == name_ex)
+ {
+ name_ex = json_object ();
+ json_object_set_new (ex,
+ "name_i18n",
+ name_ex);
+ }
+ if (NULL == json_object_get (name_ex,
+ lang))
+ {
+ json_object_set (name_ex,
+ lang,
+ trans);
+ }
+ }
+ }
+ }
+ }
+ }
+ return json_pack ("{s:o}",
+ "continents",
+ continents);
+}
diff --git a/src/reducer/anastasis_api_redux.h b/src/reducer/anastasis_api_redux.h
new file mode 100644
index 0000000..19d9466
--- /dev/null
+++ b/src/reducer/anastasis_api_redux.h
@@ -0,0 +1,347 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 2021 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file redux/anastasis_api_redux.h
+ * @brief anastasis reducer api, internal data structures
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ */
+#ifndef ANASTASIS_API_REDUX_H
+#define ANASTASIS_API_REDUX_H
+
+
+#define ANASTASIS_GENERIC_STATES(REDUX_STATE) \
+ REDUX_STATE (ERROR) \
+ REDUX_STATE (CONTINENT_SELECTING) \
+ REDUX_STATE (COUNTRY_SELECTING) \
+ REDUX_STATE (USER_ATTRIBUTES_COLLECTING)
+
+#define GENERATE_GENERIC_ENUM(ENUM) ANASTASIS_GENERIC_STATE_ ## ENUM,
+
+enum ANASTASIS_GenericState
+{
+ ANASTASIS_GENERIC_STATES (GENERATE_GENERIC_ENUM)
+};
+
+#undef GENERATE_GENERIC_ENUM
+
+#define ANASTASIS_BACKUP_STATES(REDUX_STATE) \
+ ANASTASIS_GENERIC_STATES (REDUX_STATE) \
+ REDUX_STATE (AUTHENTICATIONS_EDITING) \
+ REDUX_STATE (POLICIES_REVIEWING) \
+ REDUX_STATE (SECRET_EDITING) \
+ REDUX_STATE (TRUTHS_PAYING) \
+ REDUX_STATE (POLICIES_PAYING) \
+ REDUX_STATE (BACKUP_FINISHED)
+
+#define GENERATE_BACKUP_ENUM(ENUM) ANASTASIS_BACKUP_STATE_ ## ENUM,
+
+enum ANASTASIS_BackupState
+{
+ ANASTASIS_BACKUP_STATES (GENERATE_BACKUP_ENUM)
+};
+
+#undef GENERATE_BACKUP_ENUM
+
+#define ANASTASIS_RECOVERY_STATES(REDUX_STATE) \
+ ANASTASIS_GENERIC_STATES (REDUX_STATE) \
+ REDUX_STATE (SECRET_SELECTING) \
+ REDUX_STATE (CHALLENGE_SELECTING) \
+ REDUX_STATE (CHALLENGE_PAYING) \
+ REDUX_STATE (CHALLENGE_SOLVING) \
+ REDUX_STATE (RECOVERY_FINISHED)
+
+#define GENERATE_RECOVERY_ENUM(ENUM) ANASTASIS_RECOVERY_STATE_ ## ENUM,
+
+enum ANASTASIS_RecoveryState
+{
+ ANASTASIS_RECOVERY_STATES (GENERATE_RECOVERY_ENUM)
+};
+
+#undef GENERATE_RECOVERY_ENUM
+
+
+/**
+ * CURL context to be used by all operations.
+ */
+extern struct GNUNET_CURL_Context *ANASTASIS_REDUX_ctx_;
+
+
+/**
+ * Initialize reducer subsystem.
+ *
+ * @param ctx context to use for CURL requests.
+ */
+void
+ANASTASIS_redux_init (struct GNUNET_CURL_Context *ctx);
+
+
+/**
+ * Terminate reducer subsystem.
+ */
+void
+ANASTASIS_redux_done (void);
+
+
+/**
+ * Produce an initial state with an initialized list of
+ * continents.
+ */
+json_t *
+ANASTASIS_REDUX_load_continents_ (void);
+
+
+/**
+ * Returns the enum value to a string value of a state.
+ *
+ * @param state_string
+ * @return #ANASTASIS_GENERIC_STATE_ERROR on error
+ */
+enum ANASTASIS_GenericState
+ANASTASIS_generic_state_from_string_ (const char *state_string);
+
+
+/**
+ * Returns the string value of a state.
+ *
+ * @param state_string
+ * @return NULL on error
+ */
+const char *
+ANASTASIS_generic_state_to_string_ (enum ANASTASIS_GenericState gs);
+
+
+/**
+ * Returns the enum value to a string value of a state.
+ *
+ * @param state_string
+ * @return #ANASTASIS_BACKUP_STATE_ERROR on error
+ */
+enum ANASTASIS_BackupState
+ANASTASIS_backup_state_from_string_ (const char *state_string);
+
+
+/**
+ * Returns the string value of a state.
+ *
+ * @param state_string
+ * @return NULL on error
+ */
+const char *
+ANASTASIS_backup_state_to_string_ (enum ANASTASIS_BackupState bs);
+
+
+/**
+ * Returns the enum value to a string value of a state.
+ *
+ * @param state_string
+ * @return XXX on error
+ */
+enum ANASTASIS_RecoveryState
+ANASTASIS_recovery_state_from_string_ (const char *state_string);
+
+
+/**
+ * Returns the string value of a state.
+ *
+ * @param state_string
+ * @return NULL on error
+ */
+const char *
+ANASTASIS_recovery_state_to_string_ (enum ANASTASIS_RecoveryState rs);
+
+
+/**
+ * Function to return a json error response.
+ *
+ * @param cb callback to give error to
+ * @param cb_cls callback closure
+ * @param ec error code
+ * @param detail error detail
+ */
+void
+ANASTASIS_redux_fail_ (ANASTASIS_ActionCallback cb,
+ void *cb_cls,
+ enum TALER_ErrorCode ec,
+ const char *detail);
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "add_provider" action. Adds another Anastasis provider
+ * to the list of available providers for storing information.
+ *
+ * @param state state to operate on
+ * @param arguments arguments with a provider URL to add
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return true if @a cb was invoked
+ */
+bool
+ANASTASIS_add_provider_ (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Adds the server configuration of the Anastasis provider
+ * at @a url to the json @a state. Checks if we have
+ * the provider information already available. If so,
+ * imports it into @a state. If not, queries the provider,
+ * generating a success or failure outcome asynchronously.
+ *
+ * @param cr the config request
+ * @param[in,out] state the json state to operate on
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return handle to cancel asynchronous operation, NULL if
+ * we completed synchronously
+ */
+struct ANASTASIS_ReduxAction *
+ANASTASIS_REDUX_add_provider_to_state_ (const char *url,
+ json_t *state,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls);
+
+
+/**
+ * A generic DispatchHandler/Callback function which is called for a
+ * "back" action.
+ *
+ * @param state state to operate on
+ * @param arguments arguments to use for operation on state
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL (no asynchronous action)
+ */
+struct ANASTASIS_ReduxAction *
+ANASTASIS_back_generic_decrement_ (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Function to load json containing all countries.
+ * Returns the countries.
+ *
+ * @return json_t *
+ */
+const json_t *
+ANASTASIS_redux_countries_init_ (void);
+
+
+/**
+ * Operates on a recovery state depending on given #ANASTASIS_RecoveryState
+ * and #ANASTASIS_RecoveryAction. The new #ANASTASIS_RecoveryState is returned
+ * by a callback function.
+ * This function can do network access to talk to anastasis service providers.
+ *
+ * @param ctx the CURL context used to connect to the backend
+ * @param[in,out] state input/output state (to be modified)
+ * @param action what action to perform
+ * @param arguments data for the @a action
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ */
+struct ANASTASIS_ReduxAction *
+ANASTASIS_recovery_action_ (json_t *state,
+ const char *action,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls);
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "enter_user_attributes" action after verifying that the
+ * arguments provided were OK and the state transition was
+ * initiated. Begins the actual recovery logic.
+ *
+ * Returns an #ANASTASIS_ReduxAction.
+ *
+ * @param state state to operate on
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ * @return NULL
+ */
+struct ANASTASIS_ReduxAction *
+ANASTASIS_REDUX_recovery_challenge_begin_ (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls);
+
+
+/**
+ * DispatchHandler/Callback function which is called for a
+ * "enter_user_attributes" action after verifying that the
+ * arguments provided were OK and the state transition was
+ * initiated. Begins the actual backup logic.
+ *
+ * Returns an #ANASTASIS_ReduxAction.
+ *
+ * @param state state to operate on
+ * @param cb callback to call during/after operation
+ * @param cb_cls callback closure
+ */
+struct ANASTASIS_ReduxAction *
+ANASTASIS_REDUX_backup_begin_ (json_t *state,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Operates on a backup state depending on given #ANASTASIS_BackupState
+ * and #ANASTASIS_BackupAction. The new #ANASTASIS_BackupState is returned
+ * by a callback function.
+ * This function can do network access to talk to anastasis service providers.
+ *
+ * @param ctx the CURL context used to connect to the backend
+ * @param[in,out] state input/output state (to be modified)
+ * @param action what action to perform
+ * @param arguments data for the @a action
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ */
+struct ANASTASIS_ReduxAction *
+ANASTASIS_backup_action_ (json_t *state,
+ const char *action,
+ const json_t *arguments,
+ ANASTASIS_ActionCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Generic container for an action with asynchronous activities.
+ */
+struct ANASTASIS_ReduxAction
+{
+ /**
+ * Function to call to clean up.
+ */
+ void (*cleanup)(void *cls);
+
+ /**
+ * Action-specific state, closure for @e cleanup.
+ */
+ void *cleanup_cls;
+};
+
+
+#endif
diff --git a/src/reducer/validation_CH_AHV.c b/src/reducer/validation_CH_AHV.c
new file mode 100644
index 0000000..e6a2f25
--- /dev/null
+++ b/src/reducer/validation_CH_AHV.c
@@ -0,0 +1,57 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 2021 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file redux/validation_CH_AHV.c
+ * @brief anastasis reducer api
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ */
+#include <string.h>
+#include <stdbool.h>
+
+/**
+ * Function to validate a Swiss AHV number.
+ *
+ * @param avh_number ahv number to validate (input)
+ * @return true if validation passed, else false
+ */
+bool
+CH_AHV_check (const char *ahv_number)
+{
+ unsigned int checknum;
+ unsigned int next_ten;
+ const char *pos = &ahv_number[strlen (ahv_number) - 1];
+ bool phase = true;
+ unsigned int calculation = 0;
+
+ checknum = *pos - 48;
+ while (pos > ahv_number)
+ {
+ pos--;
+ if ('.' == *pos)
+ continue;
+ if (phase)
+ calculation += ((*pos - 48) * 3);
+ else
+ calculation += *pos - 48;
+ phase = ! phase;
+ }
+ /* round up to the next ten */
+ next_ten = ((calculation + 9) / 10) * 10;
+ calculation = next_ten - calculation;
+ return (checknum == calculation);
+}
diff --git a/src/reducer/validation_CZ_BN.c b/src/reducer/validation_CZ_BN.c
new file mode 100644
index 0000000..4598c42
--- /dev/null
+++ b/src/reducer/validation_CZ_BN.c
@@ -0,0 +1,59 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 2021 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file redux/validation_CZ_BN.c
+ * @brief validation of Czeck Birth Numbers
+ * @author Christian Grothoff
+ */
+#include <string.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <gcrypt.h>
+
+/**
+ * Function to validate a Check birth number. Basically,
+ * if it has 10 digits, it must be divisible by 11.
+ *
+ * @param b_number birth number to validate (input)
+ * @return true if b_number is valid
+ */
+bool
+CZ_BN_check (const char *b_number)
+{
+ unsigned long long n;
+ char dummy;
+ char in[11];
+
+ if (10 == strlen (b_number))
+ return true;
+ if (11 != strlen (b_number))
+ return false;
+ if (b_number[6] != '/')
+ return false;
+ memcpy (in,
+ b_number,
+ 6);
+ memcpy (&in[6],
+ &b_number[7],
+ 4);
+ in[10] = '\0';
+ if (1 != sscanf (in,
+ "%llu%c",
+ &n,
+ &dummy))
+ return false;
+ return 0 == (n % 11);
+}
diff --git a/src/reducer/validation_DE_SVN.c b/src/reducer/validation_DE_SVN.c
new file mode 100644
index 0000000..bfc406d
--- /dev/null
+++ b/src/reducer/validation_DE_SVN.c
@@ -0,0 +1,98 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 2021 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file redux/validation_DE_SVN.c
+ * @brief
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ */
+#include <string.h>
+#include <stdbool.h>
+
+/**
+ * Sum up all digits in @a v and return the result.
+ */
+static unsigned int
+q (unsigned int v)
+{
+ unsigned int r = 0;
+
+ while (0 != v)
+ {
+ r += v % 10;
+ v = v / 10;
+ }
+ return r;
+}
+
+
+/**
+ * Function to validate a German Social Security number.
+ *
+ * See https://www.financescout24.de/wissen/ratgeber/sozialversicherungsnummer
+ * and https://de.wikipedia.org/wiki/Versicherungsnummer
+ * for the structure!
+ *
+ * @param avh_number ahv number to validate (input)
+ * @return true if validation passed, else false
+ */
+bool
+DE_SVN_check (const char *ssn_number)
+{
+ static const unsigned int factors[] = {
+ 2, 1, 2, 5, 7, 1, 2, 1, 2, 1, 2, 1
+ };
+ unsigned int sum = 0;
+
+ if (strlen (ssn_number) != 12)
+ return false;
+ for (unsigned int i = 0; i<8; i++)
+ {
+ unsigned char c = (unsigned char) ssn_number[i];
+
+ if ( ('0' > c) || ('9' < c) )
+ return false;
+ sum += q ((c - '0') * factors[i]);
+ }
+ {
+ unsigned char c = (unsigned char) ssn_number[8];
+ unsigned int v = (c - 'A' + 1);
+
+ if ( ('A' > c) || ('Z' < c) )
+ return false;
+ sum += q ((v / 10) * factors[8]);
+ sum += q ((v % 10) * factors[9]);
+ }
+ for (unsigned int i = 9; i<11; i++)
+ {
+ unsigned char c = ssn_number[i];
+
+ if ( ('0' > c) || ('9' < c) )
+ return false;
+ sum += q ((c - '0') * factors[i + 1]);
+ }
+ if (ssn_number[11] != '0' + (sum % 10))
+ return false;
+ {
+ unsigned int month = (ssn_number[4] - '0') * 10 + (ssn_number[5] - '0');
+
+ if ( (0 == month) ||
+ (12 < month) )
+ return false;
+ }
+ return true;
+}
diff --git a/src/reducer/validation_DE_TIN.c b/src/reducer/validation_DE_TIN.c
new file mode 100644
index 0000000..9c97365
--- /dev/null
+++ b/src/reducer/validation_DE_TIN.c
@@ -0,0 +1,57 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 2021 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file redux/validation_DE_TIN.c
+ * @brief validation logic for German taxpayer identification numbers
+ * @author Christian Grothoff
+ */
+#include <string.h>
+#include <stdbool.h>
+
+
+/**
+ * Function to validate a German Taxpayer identification number.
+ *
+ * See https://de.wikipedia.org/wiki/Steuerliche_Identifikationsnummer
+ * for the structure!
+ *
+ * @param tin_number tax number to validate (input)
+ * @return true if validation passed, else false
+ */
+bool
+DE_TIN_check (const char *tin_number)
+{
+ unsigned int csum;
+ unsigned int product = 10;
+
+ if (strlen (tin_number) != 11)
+ return false;
+ for (unsigned int i = 0; i<10; i++)
+ {
+ unsigned int sum = ((tin_number[i] - '0') + product) % 10;
+ if (0 == sum)
+ sum = 10;
+ product = sum * 2 % 11;
+ }
+ csum = 11 - product;
+ if (10 == csum)
+ csum = 0;
+ if (tin_number[10] != '0' + csum)
+ return false;
+ if (tin_number[0] == '0')
+ return false;
+ return true;
+}
diff --git a/src/reducer/validation_IN_AADHAR.c b/src/reducer/validation_IN_AADHAR.c
new file mode 100644
index 0000000..939ee72
--- /dev/null
+++ b/src/reducer/validation_IN_AADHAR.c
@@ -0,0 +1,113 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 2021 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file redux/validation_IN_AADHAR.c
+ * @brief validation logic for Indian Aadhar numbers
+ * @author Christian Grothoff
+ */
+#include <string.h>
+#include <stdbool.h>
+#include <ctype.h>
+
+/**
+ * The multiplication table.
+ */
+static int m[10][10] = {
+ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
+ {1, 2, 3, 4, 0, 6, 7, 8, 9, 5},
+ {2, 3, 4, 0, 1, 7, 8, 9, 5, 6},
+ {3, 4, 0, 1, 2, 8, 9, 5, 6, 7},
+ {4, 0, 1, 2, 3, 9, 5, 6, 7, 8},
+ {5, 9, 8, 7, 6, 0, 4, 3, 2, 1},
+ {6, 5, 9, 8, 7, 1, 0, 4, 3, 2},
+ {7, 6, 5, 9, 8, 2, 1, 0, 4, 3},
+ {8, 7, 6, 5, 9, 3, 2, 1, 0, 4},
+ {9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
+};
+
+
+/**
+ * The permutation table.
+ */
+static int p[10][10] = {
+ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
+ {1, 5, 7, 6, 2, 8, 3, 0, 9, 4},
+ {5, 8, 0, 3, 7, 9, 6, 1, 4, 2},
+ {8, 9, 1, 6, 0, 4, 3, 5, 2, 7},
+ {9, 4, 5, 3, 1, 2, 6, 8, 7, 0},
+ {4, 2, 8, 6, 5, 7, 3, 9, 0, 1},
+ {2, 7, 9, 3, 8, 0, 6, 4, 1, 5},
+ {7, 0, 4, 6, 9, 1, 3, 2, 5, 8}
+};
+
+
+/**
+ * Converts a string to a reversed integer array.
+ *
+ * @param input The numeric string data converted to reversed int array.
+ * @param[out] Integer array containing the digits in the numeric string
+ * in reverse order
+ */
+static bool
+string_to_vals (const char *input,
+ int output[12])
+{
+ unsigned int off = 0;
+
+ for (unsigned int i = 0; i < 12;)
+ {
+ int c = input[i + off];
+
+ if (0 == c)
+ return false;
+ if (isspace (c))
+ {
+ off++;
+ continue;
+ }
+ if (! isdigit (c))
+ return false;
+ output[11 - i++] = c - '0';
+ }
+ if ('\0' != input[12 + off])
+ return false;
+ return true;
+}
+
+
+/**
+ * Function to validate an Indian Aadhar number.
+ *
+ * See https://www.geeksforgeeks.org/how-to-check-aadhar-number-is-valid-or-not-using-regular-expression/
+ * and http://en.wikipedia.org/wiki/Verhoeff_algorithm/.
+ *
+ * @param aadhar_number aadhar number to validate (input)
+ * @return true if validation passed, else false
+ */
+bool
+IN_AADHAR_check (const char *aadhar_number)
+{
+ int c = 0;
+ int vals[12];
+
+ if (! string_to_vals (aadhar_number,
+ vals))
+ return false;
+ for (unsigned int i = 0; i < 12; i++)
+ c = m[c][p[(i % 8)][vals[i]]];
+
+ return (0 == c);
+}
diff --git a/src/reducer/validation_IT_CF.c b/src/reducer/validation_IT_CF.c
new file mode 100644
index 0000000..ecdad90
--- /dev/null
+++ b/src/reducer/validation_IT_CF.c
@@ -0,0 +1,198 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2021 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file redux/validation_IT_CF.c
+ * @brief validation of Italian Code Fiscales
+ * @author Christian Grothoff
+ */
+#include <string.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <math.h>
+
+struct MapEntry
+{
+ char in;
+ unsigned int out;
+};
+
+
+static const struct MapEntry odd[] = {
+ { '0', 1},
+ { '9', 21},
+ { 'I', 19 },
+ { 'R', 8},
+ { '1', 0},
+ { 'A', 1},
+ { 'J', 21},
+ { 'S', 12},
+ {'2', 5},
+ {'B', 0},
+ {'K', 2},
+ {'T', 14},
+ {'3', 7},
+ {'C', 5},
+ {'L', 4},
+ {'U', 16},
+ {'4', 9},
+ {'D', 7},
+ {'M', 18},
+ {'V', 10},
+ {'5', 13},
+ {'E', 9},
+ {'N', 20},
+ {'W', 22},
+ {'6', 15},
+ {'F', 13},
+ {'O', 11},
+ {'X', 25},
+ {'7', 17},
+ {'G', 15},
+ {'P', 3},
+ {'Y', 24},
+ {'8', 19},
+ {'H', 17},
+ {'Q', 6},
+ {'Z', 23},
+ {'\0', 0}
+};
+
+
+static const struct MapEntry even[] = {
+ { '0', 0},
+ { '1', 1},
+ { '2', 2 },
+ { '3', 3},
+ { '4', 4},
+ { '5', 5},
+ { '6', 6 },
+ { '7', 7 },
+ {'8', 8},
+ {'9', 9},
+ {'A', 0},
+ {'B', 1},
+ {'C', 2},
+ {'D', 3},
+ {'E', 4},
+ {'F', 5},
+ {'G', 6},
+ {'H', 7},
+ {'I', 8},
+ {'J', 9},
+ {'K', 10},
+ {'L', 11},
+ {'M', 12},
+ {'N', 13},
+ {'O', 14},
+ {'P', 15},
+ {'Q', 16},
+ {'R', 17},
+ {'S', 18},
+ {'T', 19},
+ {'U', 20},
+ {'V', 21},
+ {'W', 22},
+ {'X', 23},
+ {'Y', 24},
+ {'Z', 25},
+ {'\0', 0}
+};
+
+
+static const struct MapEntry rem[] = {
+ {'A', 0},
+ {'B', 1},
+ {'C', 2},
+ {'D', 3},
+ {'E', 4},
+ {'F', 5},
+ {'G', 6},
+ {'H', 7},
+ {'I', 8},
+ {'J', 9},
+ {'K', 10},
+ {'L', 11},
+ {'M', 12},
+ {'N', 13},
+ {'O', 14},
+ {'P', 15},
+ {'Q', 16},
+ {'R', 17},
+ {'S', 18},
+ {'T', 19},
+ {'U', 20},
+ {'V', 21},
+ {'W', 22},
+ {'X', 23},
+ {'Y', 24},
+ {'Z', 25},
+ {'\0', 0}
+};
+
+
+/**
+ * Lookup @a in in @a map. Set @a fail to true if @a in is not found.
+ *
+ * @param map character map to search
+ * @param in character to search for
+ * @param[out] fail set to true on error
+ * @return map value, 0 on error
+ */
+static unsigned int
+lookup (const struct MapEntry *map,
+ char in,
+ bool *fail)
+{
+ for (unsigned int i = 0; '\0' != map[i].in; i++)
+ if (in == map[i].in)
+ return map[i].out;
+ *fail = true;
+ return 0;
+}
+
+
+/**
+ * Function to validate an italian code fiscale number.
+ * See https://en.wikipedia.org/wiki/Italian_fiscal_code
+ *
+ * @param cf_number square number to validate (input)
+ * @return true if @a cf_number is a valid, else false
+ */
+bool
+IT_CF_check (const char *cf_number)
+{
+ unsigned int sum = 0;
+ bool fail = false;
+
+ if (strlen (cf_number) != 16)
+ return false;
+ for (unsigned int i = 0; i<15; i += 2)
+ sum += lookup (odd,
+ cf_number[i],
+ &fail);
+ for (unsigned int i = 1; i<15; i += 2)
+ sum += lookup (even,
+ cf_number[i],
+ &fail);
+ sum %= 26;
+ if (sum != lookup (rem,
+ cf_number[15],
+ &fail))
+ return false;
+ if (fail)
+ return false;
+ return true;
+}
diff --git a/src/reducer/validation_XX_SQUARE.c b/src/reducer/validation_XX_SQUARE.c
new file mode 100644
index 0000000..fa3ebfb
--- /dev/null
+++ b/src/reducer/validation_XX_SQUARE.c
@@ -0,0 +1,48 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 2021 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file redux/validation_XX_PRIME.c
+ * @brief anastasis reducer api
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ */
+#include <string.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <math.h>
+
+/**
+ * Function to validate a square number.
+ *
+ * @param sq_number square number to validate (input)
+ * @return true if sq_number is a square, else false
+ */
+bool
+XX_SQUARE_check (const char *sq_number)
+{
+ unsigned long long n;
+ unsigned long long r;
+ char dummy;
+
+ if (1 != sscanf (sq_number,
+ "%llu%c",
+ &n,
+ &dummy))
+ return false;
+ r = sqrt (n);
+ return (n == r * r);
+}
diff --git a/src/reducer/validation_XY_PRIME.c b/src/reducer/validation_XY_PRIME.c
new file mode 100644
index 0000000..ab24014
--- /dev/null
+++ b/src/reducer/validation_XY_PRIME.c
@@ -0,0 +1,53 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 2021 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file redux/validation_XY_PRIME.c
+ * @brief anastasis reducer api
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ */
+#include <string.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <gcrypt.h>
+
+/**
+ * Function to validate a prime number.
+ *
+ * @param pr_number prime number to validate (input)
+ * @return true if pr_number is a prime, else false
+ */
+bool
+XY_PRIME_check (const char *pr_number)
+{
+ unsigned long long n;
+ char dummy;
+ gcry_mpi_t p;
+ bool is_prime;
+
+ if (1 != sscanf (pr_number,
+ "%llu%c",
+ &n,
+ &dummy))
+ return false;
+ p = gcry_mpi_set_ui (NULL,
+ (unsigned long) n);
+ is_prime = (0 == gcry_prime_check (p,
+ 0));
+ gcry_mpi_release (p);
+ return is_prime;
+}
diff --git a/src/restclient/Makefile.am b/src/restclient/Makefile.am
new file mode 100644
index 0000000..075d3a7
--- /dev/null
+++ b/src/restclient/Makefile.am
@@ -0,0 +1,42 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include -I$(top_srcdir)/src/backend -I$(top_srcdir)/src/lib
+
+if USE_COVERAGE
+ AM_CFLAGS = --coverage -O0
+ XLIB = -lgcov
+endif
+
+.NOTPARALLEL:
+
+lib_LTLIBRARIES = \
+ libanastasisrest.la
+
+libanastasisrest_la_LDFLAGS = \
+ -version-info 0:0:0 \
+ -no-undefined
+libanastasisrest_la_SOURCES = \
+ anastasis_api_config.c \
+ anastasis_api_policy_store.c \
+ anastasis_api_truth_store.c \
+ anastasis_api_policy_lookup.c \
+ anastasis_api_keyshare_lookup.c \
+ anastasis_api_curl_defaults.c anastasis_api_curl_defaults.h
+libanastasisrest_la_LIBADD = \
+ -lgnunetcurl \
+ -lgnunetjson \
+ -lgnunetutil \
+ -ljansson \
+ -ltalerjson \
+ -ltalerutil \
+ -ltalermerchant \
+ -ltalerjson \
+ $(XLIB)
+
+if HAVE_LIBCURL
+libanastasisrest_la_LIBADD += -lcurl
+else
+if HAVE_LIBGNURL
+libanastasisrest_la_LIBADD += -lgnurl
+endif
+endif
+
diff --git a/src/restclient/anastasis_api_config.c b/src/restclient/anastasis_api_config.c
new file mode 100644
index 0000000..cf0846b
--- /dev/null
+++ b/src/restclient/anastasis_api_config.c
@@ -0,0 +1,317 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 2021 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/anastasis_api_config.c
+ * @brief Implementation of the /config GET
+ * @author Christian Grothoff
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include "anastasis_service.h"
+#include "anastasis_api_curl_defaults.h"
+#include <gnunet/gnunet_json_lib.h>
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * Which version of the Taler protocol is implemented
+ * by this library? Used to determine compatibility.
+ */
+#define ANASTASIS_PROTOCOL_CURRENT 0
+
+/**
+ * How many versions are we backwards compatible with?
+ */
+#define ANASTASIS_PROTOCOL_AGE 0
+
+
+struct ANASTASIS_ConfigOperation
+{
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * The callback to pass the backend response to
+ */
+ ANASTASIS_ConfigCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /config request.
+ *
+ * @param cls the `struct ANASTASIS_ConfigOperation`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_config_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct ANASTASIS_ConfigOperation *co = cls;
+ const json_t *json = response;
+
+ co->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ /* Hard error */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Backend `%s' failed to respond to GET /config\n",
+ co->url);
+ break;
+ case MHD_HTTP_OK:
+ {
+ const char *name;
+ struct ANASTASIS_Config acfg;
+ json_t *methods;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("name",
+ &name),
+ GNUNET_JSON_spec_string ("business_name",
+ &acfg.business_name),
+ GNUNET_JSON_spec_string ("version",
+ &acfg.version),
+ GNUNET_JSON_spec_string ("currency",
+ &acfg.currency),
+ GNUNET_JSON_spec_json ("methods",
+ &methods),
+ GNUNET_JSON_spec_uint32 ("storage_limit_in_megabytes",
+ &acfg.storage_limit_in_megabytes),
+ TALER_JSON_spec_amount_any ("annual_fee",
+ &acfg.annual_fee),
+ TALER_JSON_spec_amount_any ("truth_upload_fee",
+ &acfg.truth_upload_fee),
+ TALER_JSON_spec_amount_any ("liability_limit",
+ &acfg.liability_limit),
+ GNUNET_JSON_spec_fixed_auto ("server_salt",
+ &acfg.salt),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ response_code = 0;
+ break;
+ }
+ if (0 != strcmp (name,
+ "anastasis"))
+ {
+ GNUNET_JSON_parse_free (spec);
+ response_code = 0;
+ break;
+ }
+ {
+ unsigned int age;
+ unsigned int revision;
+ unsigned int current;
+ char dummy;
+
+ if (3 != sscanf (acfg.version,
+ "%u:%u:%u%c",
+ &current,
+ &revision,
+ &age,
+ &dummy))
+ {
+ GNUNET_break_op (0);
+ response_code = 0;
+ GNUNET_JSON_parse_free (spec);
+ break;
+ }
+ if ( (ANASTASIS_PROTOCOL_CURRENT < current) &&
+ (ANASTASIS_PROTOCOL_CURRENT < current - age) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Provider protocol version too new\n");
+ response_code = 0;
+ GNUNET_JSON_parse_free (spec);
+ break;
+ }
+ if ( (ANASTASIS_PROTOCOL_CURRENT > current) &&
+ (ANASTASIS_PROTOCOL_CURRENT - ANASTASIS_PROTOCOL_AGE > current) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Provider protocol version too old\n");
+ GNUNET_break_op (0);
+ response_code = 0;
+ GNUNET_JSON_parse_free (spec);
+ break;
+ }
+ }
+ if ( (GNUNET_OK !=
+ TALER_amount_cmp_currency (&acfg.liability_limit,
+ &acfg.annual_fee)) ||
+ (0 !=
+ strcasecmp (acfg.currency,
+ acfg.annual_fee.currency)) )
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ response_code = 0;
+ break;
+ }
+
+ if (! json_is_array (methods))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ response_code = 0;
+ break;
+ }
+ acfg.methods_length = json_array_size (methods);
+ {
+ struct ANASTASIS_AuthorizationMethodConfig mcfg[GNUNET_NZL (
+ acfg.methods_length)];
+
+ for (unsigned int i = 0; i<acfg.methods_length; i++)
+ {
+ struct ANASTASIS_AuthorizationMethodConfig *m = &mcfg[i];
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("type",
+ &m->type),
+ TALER_JSON_spec_amount_any ("cost",
+ &m->usage_fee),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if ( (GNUNET_OK !=
+ GNUNET_JSON_parse (json_array_get (methods,
+ i),
+ spec,
+ NULL, NULL)) ||
+ (0 !=
+ strcasecmp (m->usage_fee.currency,
+ acfg.currency)) )
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ response_code = 0;
+ goto end;
+ }
+ }
+ acfg.methods = mcfg;
+ co->cb (co->cb_cls,
+ MHD_HTTP_OK,
+ &acfg);
+ GNUNET_JSON_parse_free (spec);
+ ANASTASIS_config_cancel (co);
+ return;
+ }
+ }
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the anastasis server is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify */
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u\n",
+ (unsigned int) response_code);
+ GNUNET_break_op (0);
+ break;
+ }
+end:
+ co->cb (co->cb_cls,
+ response_code,
+ NULL);
+ ANASTASIS_config_cancel (co);
+}
+
+
+struct ANASTASIS_ConfigOperation *
+ANASTASIS_get_config (struct GNUNET_CURL_Context *ctx,
+ const char *base_url,
+ ANASTASIS_ConfigCallback cb,
+ void *cb_cls)
+{
+ struct ANASTASIS_ConfigOperation *co;
+
+ co = GNUNET_new (struct ANASTASIS_ConfigOperation);
+ co->url = TALER_url_join (base_url,
+ "config",
+ NULL);
+ co->ctx = ctx;
+ co->cb = cb;
+ co->cb_cls = cb_cls;
+ {
+ CURL *eh;
+
+ eh = ANASTASIS_curl_easy_get_ (co->url);
+ co->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ GNUNET_NO,
+ &handle_config_finished,
+ co);
+ }
+ if (NULL == co->job)
+ {
+ GNUNET_free (co->url);
+ GNUNET_free (co);
+ return NULL;
+ }
+ return co;
+}
+
+
+void
+ANASTASIS_config_cancel (struct ANASTASIS_ConfigOperation *co)
+{
+ if (NULL != co->job)
+ {
+ GNUNET_CURL_job_cancel (co->job);
+ co->job = NULL;
+ }
+ GNUNET_free (co->url);
+ GNUNET_free (co);
+}
+
+
+/* end of anastasis_api_config.c */
diff --git a/src/restclient/anastasis_api_curl_defaults.c b/src/restclient/anastasis_api_curl_defaults.c
new file mode 100644
index 0000000..27b965f
--- /dev/null
+++ b/src/restclient/anastasis_api_curl_defaults.c
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2019 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 lib/anastasis_api_curl_defaults.c
+ * @brief curl easy handle defaults
+ * @author Florian Dold
+ */
+#include "platform.h"
+#include "anastasis_api_curl_defaults.h"
+
+CURL *
+ANASTASIS_curl_easy_get_ (const char *url)
+{
+ CURL *eh;
+
+ eh = curl_easy_init ();
+ if (NULL == eh)
+ return NULL;
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_URL,
+ url));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_FOLLOWLOCATION,
+ 1L));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_TCP_FASTOPEN,
+ 1L));
+ return eh;
+}
diff --git a/src/restclient/anastasis_api_curl_defaults.h b/src/restclient/anastasis_api_curl_defaults.h
new file mode 100644
index 0000000..62e0081
--- /dev/null
+++ b/src/restclient/anastasis_api_curl_defaults.h
@@ -0,0 +1,38 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2019 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 lib/anastasis_api_curl_defaults.h
+ * @brief curl easy handle defaults
+ * @author Florian Dold
+ */
+
+#ifndef _ANASTASIS_API_CURL_DEFAULTS_H
+#define _ANASTASIS_API_CURL_DEFAULTS_H
+
+#include <gnunet/gnunet_curl_lib.h>
+
+
+/**
+ * Get a curl handle with the right defaults
+ * for the exchange lib. In the future, we might manage a pool of connections here.
+ *
+ * @param url URL to query
+ */
+CURL *
+ANASTASIS_curl_easy_get_ (const char *url);
+
+#endif /* _TALER_CURL_DEFAULTS_H */
diff --git a/src/restclient/anastasis_api_keyshare_lookup.c b/src/restclient/anastasis_api_keyshare_lookup.c
new file mode 100644
index 0000000..a2e4316
--- /dev/null
+++ b/src/restclient/anastasis_api_keyshare_lookup.c
@@ -0,0 +1,508 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 2021 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/anastasis_api_keyshare_lookup.c
+ * @brief Implementation of the GET /truth client
+ * @author Christian Grothoff
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include "anastasis_service.h"
+#include "anastasis_api_curl_defaults.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_merchant_service.h>
+
+
+/**
+ * @brief A Contract Operation Handle
+ */
+struct ANASTASIS_KeyShareLookupOperation
+{
+ /**
+ * The url for this request, including parameters.
+ */
+ char *url;
+
+ /**
+ * The url for this request, without response parameter.
+ */
+ char *display_url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ ANASTASIS_KeyShareLookupCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * Identification of the Truth Object
+ */
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_public_key;
+
+ /**
+ * Key to decrypt the truth on the server
+ */
+ const struct ANASTASIS_CRYPTO_TruthKeyP *truth_key;
+
+ /**
+ * Hash of the response (security question)
+ */
+ const struct GNUNET_HashCode *hashed_answer;
+
+ /**
+ * Payment URI we received from the service, or NULL.
+ */
+ char *pay_uri;
+
+ /**
+ * Location URI we received from the service, or NULL.
+ */
+ char *location;
+
+ /**
+ * Content type of the body.
+ */
+ char *content_type;
+};
+
+
+void
+ANASTASIS_keyshare_lookup_cancel (
+ struct ANASTASIS_KeyShareLookupOperation *kslo)
+{
+ if (NULL != kslo->job)
+ {
+ GNUNET_CURL_job_cancel (kslo->job);
+ kslo->job = NULL;
+ }
+ GNUNET_free (kslo->location);
+ GNUNET_free (kslo->pay_uri);
+ GNUNET_free (kslo->display_url);
+ GNUNET_free (kslo->url);
+ GNUNET_free (kslo->content_type);
+ GNUNET_free (kslo);
+}
+
+
+/**
+ * Process GET /truth response
+ *
+ * @param cls our `struct ANASTASIS_KeyShareLookupOperation *`
+ * @param response_code the HTTP status
+ * @param data the body of the response
+ * @param data_size number of bytes in @a data
+ */
+static void
+handle_keyshare_lookup_finished (void *cls,
+ long response_code,
+ const void *data,
+ size_t data_size)
+{
+ struct ANASTASIS_KeyShareLookupOperation *kslo = cls;
+ struct ANASTASIS_KeyShareDownloadDetails kdd;
+
+ kslo->job = NULL;
+ memset (&kdd,
+ 0,
+ sizeof (kdd));
+ kdd.server_url = kslo->display_url;
+ switch (response_code)
+ {
+ case 0:
+ /* Hard error */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Backend didn't even return from GET /truth\n");
+ kdd.status = ANASTASIS_KSD_SERVER_ERROR;
+ break;
+ case MHD_HTTP_OK:
+ if (sizeof (struct ANASTASIS_CRYPTO_EncryptedKeyShareP) != data_size)
+ {
+ GNUNET_break_op (0);
+ kdd.status = ANASTASIS_KSD_SERVER_ERROR;
+ break;
+ }
+ /* Success, call callback with all details! */
+ memcpy (&kdd.details.eks,
+ data,
+ data_size);
+ kslo->cb (kslo->cb_cls,
+ &kdd);
+ ANASTASIS_keyshare_lookup_cancel (kslo);
+ return;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the anastasis server is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ GNUNET_break (0);
+ kdd.status = ANASTASIS_KSD_CLIENT_FAILURE;
+ break;
+ case MHD_HTTP_PAYMENT_REQUIRED:
+ {
+ struct TALER_MERCHANT_PayUriData pd;
+
+ if ( (NULL == kslo->pay_uri) ||
+ (GNUNET_OK !=
+ TALER_MERCHANT_parse_pay_uri (kslo->pay_uri,
+ &pd)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse `%s'\n",
+ kslo->pay_uri);
+ kdd.status = ANASTASIS_KSD_SERVER_ERROR;
+ break;
+ }
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (
+ pd.order_id,
+ strlen (pd.order_id),
+ &kdd.details.payment_required.payment_secret,
+ sizeof (kdd.details.payment_required.payment_secret)))
+ {
+ GNUNET_break (0);
+ kdd.status = ANASTASIS_KSD_SERVER_ERROR;
+ TALER_MERCHANT_parse_pay_uri_free (&pd);
+ break;
+ }
+ kdd.status = ANASTASIS_KSD_PAYMENT_REQUIRED;
+ kdd.details.payment_required.taler_pay_uri = kslo->pay_uri;
+ kslo->cb (kslo->cb_cls,
+ &kdd);
+ ANASTASIS_keyshare_lookup_cancel (kslo);
+ TALER_MERCHANT_parse_pay_uri_free (&pd);
+ return;
+ }
+ break;
+ case MHD_HTTP_SEE_OTHER:
+ /* Nothing really to verify, authentication required/failed */
+ kdd.status = ANASTASIS_KSD_REDIRECT_FOR_AUTHENTICATION;
+ kdd.details.redirect_url = kslo->location;
+ kslo->cb (kslo->cb_cls,
+ &kdd);
+ ANASTASIS_keyshare_lookup_cancel (kslo);
+ return;
+ case MHD_HTTP_ALREADY_REPORTED:
+ case MHD_HTTP_FORBIDDEN:
+ /* Nothing really to verify, authentication required/failed */
+ {
+ kdd.status = ANASTASIS_KSD_INVALID_ANSWER;
+ kdd.details.open_challenge.body = data;
+ kdd.details.open_challenge.body_size = data_size;
+ kdd.details.open_challenge.content_type = kslo->content_type;
+ kdd.details.open_challenge.http_status = response_code;
+ kslo->cb (kslo->cb_cls,
+ &kdd);
+ }
+ ANASTASIS_keyshare_lookup_cancel (kslo);
+ return;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify */
+ kdd.status = ANASTASIS_KSD_TRUTH_UNKNOWN;
+ break;
+ case MHD_HTTP_GONE:
+ /* Nothing really to verify */
+ kdd.status = ANASTASIS_KSD_TRUTH_UNKNOWN;
+ break;
+ case MHD_HTTP_EXPECTATION_FAILED:
+ /* Nothing really to verify */
+ kdd.status = ANASTASIS_KSD_CLIENT_FAILURE;
+ break;
+ case MHD_HTTP_TOO_MANY_REQUESTS:
+ kdd.status = ANASTASIS_KSD_RATE_LIMIT_EXCEEDED;
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ kdd.status = ANASTASIS_KSD_SERVER_ERROR;
+ kdd.details.server_failure.ec = TALER_JSON_get_error_code2 (data,
+ data_size);
+ kdd.details.server_failure.http_status = response_code;
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u to GET /truth\n",
+ (unsigned int) response_code);
+ GNUNET_break (0);
+ kdd.status = ANASTASIS_KSD_SERVER_ERROR;
+ kdd.details.server_failure.ec = TALER_JSON_get_error_code2 (data,
+ data_size);
+ kdd.details.server_failure.http_status = response_code;
+ break;
+ }
+ kslo->cb (kslo->cb_cls,
+ &kdd);
+ ANASTASIS_keyshare_lookup_cancel (kslo);
+}
+
+
+/**
+ * Patch value in @a val, replacing new line with '\0'.
+ *
+ * @param[in,out] 0-terminated string to replace '\n'/'\r' with '\0' in.
+ */
+static void
+patch_value (char *val)
+{
+ size_t len;
+
+ /* found location URI we care about! */
+ len = strlen (val);
+ while ( (len > 0) &&
+ ( ('\n' == val[len - 1]) ||
+ ('\r' == val[len - 1]) ) )
+ {
+ len--;
+ val[len] = '\0';
+ }
+}
+
+
+/**
+ * Handle HTTP header received by curl.
+ *
+ * @param buffer one line of HTTP header data
+ * @param size size of an item
+ * @param nitems number of items passed
+ * @param userdata our `struct ANASTASIS_StorePolicyOperation *`
+ * @return `size * nitems`
+ */
+static size_t
+handle_header (char *buffer,
+ size_t size,
+ size_t nitems,
+ void *userdata)
+{
+ struct ANASTASIS_KeyShareLookupOperation *kslo = userdata;
+ size_t total = size * nitems;
+ char *ndup;
+ const char *hdr_type;
+ char *hdr_val;
+ char *sp;
+
+ ndup = GNUNET_strndup (buffer,
+ total);
+ hdr_type = strtok_r (ndup,
+ ":",
+ &sp);
+ if (NULL == hdr_type)
+ {
+ GNUNET_free (ndup);
+ return total;
+ }
+ hdr_val = strtok_r (NULL,
+ "",
+ &sp);
+ if (NULL == hdr_val)
+ {
+ GNUNET_free (ndup);
+ return total;
+ }
+ if (' ' == *hdr_val)
+ hdr_val++;
+ if (0 == strcasecmp (hdr_type,
+ ANASTASIS_HTTP_HEADER_TALER))
+ {
+ /* found payment URI we care about! */
+ GNUNET_free (kslo->pay_uri);
+ kslo->pay_uri = GNUNET_strdup (hdr_val);
+ patch_value (kslo->pay_uri);
+ }
+ if (0 == strcasecmp (hdr_type,
+ MHD_HTTP_HEADER_LOCATION))
+ {
+ /* found location URI we care about! */
+ GNUNET_free (kslo->location);
+ kslo->location = GNUNET_strdup (hdr_val);
+ patch_value (kslo->location);
+ }
+ if (0 == strcasecmp (hdr_type,
+ MHD_HTTP_HEADER_CONTENT_TYPE))
+ {
+ /* found location URI we care about! */
+ GNUNET_free (kslo->content_type);
+ kslo->content_type = GNUNET_strdup (hdr_val);
+ patch_value (kslo->content_type);
+ }
+ GNUNET_free (ndup);
+ return total;
+}
+
+
+struct ANASTASIS_KeyShareLookupOperation *
+ANASTASIS_keyshare_lookup (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ const struct ANASTASIS_CRYPTO_TruthKeyP *truth_key,
+ const struct ANASTASIS_PaymentSecretP *payment_secret,
+ struct GNUNET_TIME_Relative timeout,
+ const struct GNUNET_HashCode *hashed_answer,
+ ANASTASIS_KeyShareLookupCallback cb,
+ void *cb_cls)
+{
+ struct ANASTASIS_KeyShareLookupOperation *kslo;
+ CURL *eh;
+ struct curl_slist *job_headers;
+ char *path;
+ char *answer_s;
+ unsigned long long tms;
+
+ tms = (unsigned long long) (timeout.rel_value_us
+ / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
+ job_headers = NULL;
+ {
+ struct curl_slist *ext;
+ char *val;
+ char *hdr;
+
+ /* Set Truth-Decryption-Key header */
+ val = GNUNET_STRINGS_data_to_string_alloc (truth_key,
+ sizeof (*truth_key));
+ GNUNET_asprintf (&hdr,
+ "%s: %s",
+ ANASTASIS_HTTP_HEADER_TRUTH_DECRYPTION_KEY,
+ val);
+ GNUNET_free (val);
+ ext = curl_slist_append (job_headers,
+ hdr);
+ GNUNET_free (hdr);
+ if (NULL == ext)
+ {
+ GNUNET_break (0);
+ curl_slist_free_all (job_headers);
+ return NULL;
+ }
+ job_headers = ext;
+
+ /* Setup Payment-Identifier header */
+ if (NULL != payment_secret)
+ {
+ char *paid_order_id;
+
+ paid_order_id = GNUNET_STRINGS_data_to_string_alloc (
+ payment_secret,
+ sizeof (*payment_secret));
+ GNUNET_asprintf (&hdr,
+ "%s: %s",
+ ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER,
+ paid_order_id);
+ GNUNET_free (paid_order_id);
+ ext = curl_slist_append (job_headers,
+ hdr);
+ GNUNET_free (hdr);
+ if (NULL == ext)
+ {
+ GNUNET_break (0);
+ curl_slist_free_all (job_headers);
+ return NULL;
+ }
+ job_headers = ext;
+ }
+ }
+ kslo = GNUNET_new (struct ANASTASIS_KeyShareLookupOperation);
+ kslo->ctx = ctx;
+ kslo->truth_key = truth_key;
+ {
+ char *uuid_str;
+
+ uuid_str = GNUNET_STRINGS_data_to_string_alloc (truth_uuid,
+ sizeof (*truth_uuid));
+ GNUNET_asprintf (&path,
+ "truth/%s",
+ uuid_str);
+ GNUNET_free (uuid_str);
+ }
+ {
+ char timeout_ms[32];
+
+ GNUNET_snprintf (timeout_ms,
+ sizeof (timeout_ms),
+ "%llu",
+ tms);
+ if (NULL != hashed_answer)
+ {
+ answer_s = GNUNET_STRINGS_data_to_string_alloc (hashed_answer,
+ sizeof (*hashed_answer));
+ kslo->url = TALER_url_join (backend_url,
+ path,
+ "response",
+ answer_s,
+ "timeout_ms",
+ (0 != timeout.rel_value_us)
+ ? timeout_ms
+ : NULL,
+ NULL);
+ GNUNET_free (answer_s);
+ }
+ else
+ {
+ kslo->url = TALER_url_join (backend_url,
+ path,
+ "timeout_ms",
+ (0 != timeout.rel_value_us)
+ ? timeout_ms
+ : NULL,
+ NULL);
+ }
+ }
+ kslo->display_url = TALER_url_join (backend_url,
+ path,
+ NULL);
+ GNUNET_free (path);
+ eh = ANASTASIS_curl_easy_get_ (kslo->url);
+ if (0 != tms)
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_TIMEOUT_MS,
+ (long) (tms + 5000)));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_HEADERFUNCTION,
+ &handle_header));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_HEADERDATA,
+ kslo));
+ kslo->cb = cb;
+ kslo->cb_cls = cb_cls;
+ kslo->job = GNUNET_CURL_job_add_raw (ctx,
+ eh,
+ job_headers,
+ &handle_keyshare_lookup_finished,
+ kslo);
+ curl_slist_free_all (job_headers);
+ return kslo;
+}
+
+
+/* end of anastasis_api_keyshare_lookup.c */
diff --git a/src/restclient/anastasis_api_policy_lookup.c b/src/restclient/anastasis_api_policy_lookup.c
new file mode 100644
index 0000000..1af95d7
--- /dev/null
+++ b/src/restclient/anastasis_api_policy_lookup.c
@@ -0,0 +1,356 @@
+/*
+ This file is part of ANASTASIS
+ Copyright (C) 2014-2019 GNUnet e.V. and INRIA
+
+ ANASTASIS is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1,
+ or (at your option) any later version.
+
+ ANASTASIS 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with ANASTASIS; see the file COPYING.LGPL. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file lib/anastasis_api_policy_lookup.c
+ * @brief Implementation of the /policy GET and POST
+ * @author Christian Grothoff
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include "anastasis_service.h"
+#include "anastasis_api_curl_defaults.h"
+#include <taler/taler_signatures.h>
+
+
+/**
+ * @brief A Contract Operation Handle
+ */
+struct ANASTASIS_PolicyLookupOperation
+{
+
+ /**
+ * The url for this request, including parameters.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ ANASTASIS_PolicyLookupCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * Public key of the account we are downloading from.
+ */
+ struct ANASTASIS_CRYPTO_AccountPublicKeyP account_pub;
+
+ /**
+ * Signature returned in the #ANASTASIS_HTTP_HEADER_POLICY_SIGNATURE
+ * header, or all zeros for none.
+ */
+ struct ANASTASIS_AccountSignatureP account_sig;
+
+ /**
+ * Version of the policy.
+ */
+ unsigned int version;
+
+};
+
+
+void
+ANASTASIS_policy_lookup_cancel (struct ANASTASIS_PolicyLookupOperation *plo)
+{
+ if (NULL != plo->job)
+ {
+ GNUNET_CURL_job_cancel (plo->job);
+ plo->job = NULL;
+ }
+ GNUNET_free (plo->url);
+ GNUNET_free (plo);
+}
+
+
+/**
+ * Process GET /policy response
+ */
+static void
+handle_policy_lookup_finished (void *cls,
+ long response_code,
+ const void *data,
+ size_t data_size)
+{
+ struct ANASTASIS_PolicyLookupOperation *plo = cls;
+
+ plo->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ /* Hard error */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Backend didn't even return from GET /policy\n");
+ break;
+ case MHD_HTTP_OK:
+ {
+ struct ANASTASIS_DownloadDetails dd;
+ struct ANASTASIS_UploadSignaturePS usp = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_ANASTASIS_POLICY_UPLOAD),
+ .purpose.size = htonl (sizeof (usp)),
+ };
+
+ GNUNET_CRYPTO_hash (data,
+ data_size,
+ &usp.new_recovery_data_hash);
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_ANASTASIS_POLICY_UPLOAD,
+ &usp,
+ &plo->account_sig.eddsa_sig,
+ &plo->account_pub.pub))
+ {
+ GNUNET_break_op (0);
+ response_code = 0;
+ break;
+ }
+ /* Success, call callback with all details! */
+ memset (&dd, 0, sizeof (dd));
+ dd.sig = plo->account_sig;
+ dd.curr_policy_hash = usp.new_recovery_data_hash;
+ dd.policy = data;
+ dd.policy_size = data_size;
+ dd.version = plo->version;
+ plo->cb (plo->cb_cls,
+ response_code,
+ &dd);
+ plo->cb = NULL;
+ ANASTASIS_policy_lookup_cancel (plo);
+ return;
+ }
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the anastasis server is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify */
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u\n",
+ (unsigned int) response_code);
+ GNUNET_break (0);
+ response_code = 0;
+ break;
+ }
+ plo->cb (plo->cb_cls,
+ response_code,
+ NULL);
+ plo->cb = NULL;
+ ANASTASIS_policy_lookup_cancel (plo);
+}
+
+
+/**
+ * Handle HTTP header received by curl.
+ *
+ * @param buffer one line of HTTP header data
+ * @param size size of an item
+ * @param nitems number of items passed
+ * @param userdata our `struct ANASTASIS_PolicyLookupOperation *`
+ * @return `size * nitems`
+ */
+static size_t
+handle_header (char *buffer,
+ size_t size,
+ size_t nitems,
+ void *userdata)
+{
+ struct ANASTASIS_PolicyLookupOperation *plo = userdata;
+ size_t total = size * nitems;
+ char *ndup;
+ const char *hdr_type;
+ char *hdr_val;
+ char *sp;
+
+ ndup = GNUNET_strndup (buffer,
+ total);
+ hdr_type = strtok_r (ndup,
+ ":",
+ &sp);
+ if (NULL == hdr_type)
+ {
+ GNUNET_free (ndup);
+ return total;
+ }
+ hdr_val = strtok_r (NULL,
+ "\n\r",
+ &sp);
+ if (NULL == hdr_val)
+ {
+ GNUNET_free (ndup);
+ return total;
+ }
+ if (' ' == *hdr_val)
+ hdr_val++;
+ if (0 == strcasecmp (hdr_type,
+ ANASTASIS_HTTP_HEADER_POLICY_SIGNATURE))
+ {
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (
+ hdr_val,
+ strlen (hdr_val),
+ &plo->account_sig,
+ sizeof (struct ANASTASIS_AccountSignatureP)))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (ndup);
+ return 0;
+ }
+ }
+ if (0 == strcasecmp (hdr_type,
+ ANASTASIS_HTTP_HEADER_POLICY_VERSION))
+ {
+ char dummy;
+
+ if (1 !=
+ sscanf (hdr_val,
+ "%u%c",
+ &plo->version,
+ &dummy))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (ndup);
+ return 0;
+ }
+ }
+ GNUNET_free (ndup);
+ return total;
+}
+
+
+struct ANASTASIS_PolicyLookupOperation *
+ANASTASIS_policy_lookup (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *anastasis_pub,
+ ANASTASIS_PolicyLookupCallback cb,
+ void *cb_cls)
+{
+ struct ANASTASIS_PolicyLookupOperation *plo;
+ CURL *eh;
+ char *acc_pub_str;
+ char *path;
+
+ GNUNET_assert (NULL != cb);
+ plo = GNUNET_new (struct ANASTASIS_PolicyLookupOperation);
+ plo->account_pub = *anastasis_pub;
+ acc_pub_str = GNUNET_STRINGS_data_to_string_alloc (anastasis_pub,
+ sizeof (*anastasis_pub));
+ GNUNET_asprintf (&path,
+ "policy/%s",
+ acc_pub_str);
+ GNUNET_free (acc_pub_str);
+ plo->url = TALER_url_join (backend_url,
+ path,
+ NULL);
+ GNUNET_free (path);
+ eh = ANASTASIS_curl_easy_get_ (plo->url);
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_HEADERFUNCTION,
+ &handle_header));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_HEADERDATA,
+ plo));
+ plo->cb = cb;
+ plo->cb_cls = cb_cls;
+ plo->job = GNUNET_CURL_job_add_raw (ctx,
+ eh,
+ GNUNET_NO,
+ &handle_policy_lookup_finished,
+ plo);
+ return plo;
+}
+
+
+struct ANASTASIS_PolicyLookupOperation *
+ANASTASIS_policy_lookup_version (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *anastasis_pub,
+ ANASTASIS_PolicyLookupCallback cb,
+ void *cb_cls,
+ unsigned int version)
+{
+ struct ANASTASIS_PolicyLookupOperation *plo;
+ CURL *eh;
+ char *acc_pub_str;
+ char *path;
+ char version_s[14];
+
+ GNUNET_assert (NULL != cb);
+ plo = GNUNET_new (struct ANASTASIS_PolicyLookupOperation);
+ plo->account_pub = *anastasis_pub;
+ acc_pub_str = GNUNET_STRINGS_data_to_string_alloc (anastasis_pub,
+ sizeof (*anastasis_pub));
+ GNUNET_asprintf (&path,
+ "policy/%s",
+ acc_pub_str);
+ GNUNET_free (acc_pub_str);
+ GNUNET_snprintf (version_s,
+ sizeof (version_s),
+ "%u",
+ version);
+ plo->url = TALER_url_join (backend_url,
+ path,
+ "version",
+ version_s,
+ NULL);
+ GNUNET_free (path);
+ eh = ANASTASIS_curl_easy_get_ (plo->url);
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_HEADERFUNCTION,
+ &handle_header));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_HEADERDATA,
+ plo));
+ plo->cb = cb;
+ plo->cb_cls = cb_cls;
+ plo->job = GNUNET_CURL_job_add_raw (ctx,
+ eh,
+ GNUNET_NO,
+ &handle_policy_lookup_finished,
+ plo);
+ return plo;
+}
diff --git a/src/restclient/anastasis_api_policy_store.c b/src/restclient/anastasis_api_policy_store.c
new file mode 100644
index 0000000..7c6c244
--- /dev/null
+++ b/src/restclient/anastasis_api_policy_store.c
@@ -0,0 +1,527 @@
+/*
+ This file is part of ANASTASIS
+ Copyright (C) 2014-2021 Anastasis SARL
+
+ ANASTASIS is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1,
+ or (at your option) any later version.
+
+ ANASTASIS 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with ANASTASIS; see the file COPYING.LGPL. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file lib/anastasis_api_policy_store.c
+ * @brief Implementation of the /policy GET and POST
+ * @author Christian Grothoff
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include "anastasis_service.h"
+#include "anastasis_api_curl_defaults.h"
+#include <taler/taler_signatures.h>
+#include <taler/taler_merchant_service.h>
+#include <taler/taler_json_lib.h>
+
+
+struct ANASTASIS_PolicyStoreOperation
+{
+ /**
+ * Complete URL where the backend offers /policy
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * The CURL context to connect to the backend
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * The callback to pass the backend response to
+ */
+ ANASTASIS_PolicyStoreCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Payment URI we received from the service, or NULL.
+ */
+ char *pay_uri;
+
+ /**
+ * Policy version we received from the service, or NULL.
+ */
+ char *policy_version;
+
+ /**
+ * Policy expiration we received from the service, or NULL.
+ */
+ char *policy_expiration;
+
+ /**
+ * Copy of the uploaded data. Needed by curl.
+ */
+ void *postcopy;
+
+ /**
+ * Hash of the data we are uploading.
+ */
+ struct GNUNET_HashCode new_upload_hash;
+};
+
+
+void
+ANASTASIS_policy_store_cancel (
+ struct ANASTASIS_PolicyStoreOperation *pso)
+{
+ if (NULL != pso->job)
+ {
+ GNUNET_CURL_job_cancel (pso->job);
+ pso->job = NULL;
+ }
+ GNUNET_free (pso->policy_version);
+ GNUNET_free (pso->policy_expiration);
+ GNUNET_free (pso->pay_uri);
+ GNUNET_free (pso->url);
+ GNUNET_free (pso->postcopy);
+ GNUNET_free (pso);
+}
+
+
+/**
+ * Callback to process POST /policy response
+ *
+ * @param cls the `struct ANASTASIS_PolicyStoreOperation`
+ * @param response_code HTTP response code, 0 on error
+ * @param data response body
+ * @param data_size number of bytes in @a data
+ */
+static void
+handle_policy_store_finished (void *cls,
+ long response_code,
+ const void *data,
+ size_t data_size)
+{
+ struct ANASTASIS_PolicyStoreOperation *pso = cls;
+ struct ANASTASIS_UploadDetails ud;
+
+ pso->job = NULL;
+ memset (&ud, 0, sizeof (ud));
+ ud.http_status = response_code;
+ ud.ec = TALER_EC_NONE;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Policy store finished with HTTP status %u\n",
+ (unsigned int) response_code);
+ switch (response_code)
+ {
+ case 0:
+ ud.us = ANASTASIS_US_SERVER_ERROR;
+ ud.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_NO_CONTENT:
+ case MHD_HTTP_NOT_MODIFIED:
+ {
+ unsigned long long version;
+ unsigned long long expiration;
+ char dummy;
+
+ if (1 != sscanf (pso->policy_version,
+ "%llu%c",
+ &version,
+ &dummy))
+ {
+ ud.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ ud.us = ANASTASIS_US_SERVER_ERROR;
+ break;
+ }
+ if (1 != sscanf (pso->policy_expiration,
+ "%llu%c",
+ &expiration,
+ &dummy))
+ {
+ ud.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ ud.us = ANASTASIS_US_SERVER_ERROR;
+ break;
+ }
+ ud.us = ANASTASIS_US_SUCCESS;
+ ud.details.success.curr_backup_hash = &pso->new_upload_hash;
+ ud.details.success.policy_expiration
+ = GNUNET_TIME_absolute_add (
+ GNUNET_TIME_UNIT_ZERO_ABS,
+ GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_SECONDS,
+ expiration));
+ ud.details.success.policy_version = version;
+ }
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ GNUNET_break (0);
+ ud.us = ANASTASIS_US_CLIENT_ERROR;
+ ud.ec = TALER_JSON_get_error_code2 (data,
+ data_size);
+ break;
+ case MHD_HTTP_PAYMENT_REQUIRED:
+ {
+ struct TALER_MERCHANT_PayUriData pd;
+
+ if ( (NULL == pso->pay_uri) ||
+ (GNUNET_OK !=
+ TALER_MERCHANT_parse_pay_uri (pso->pay_uri,
+ &pd)) )
+ {
+ GNUNET_break_op (0);
+ ud.ec = TALER_EC_ANASTASIS_GENERIC_INVALID_PAYMENT_REQUEST;
+ break;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Policy store operation requires payment `%s'\n",
+ pso->pay_uri);
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (
+ pd.order_id,
+ strlen (pd.order_id),
+ &ud.details.payment.ps,
+ sizeof (ud.details.payment.ps)))
+ {
+ GNUNET_break (0);
+ ud.ec = TALER_EC_ANASTASIS_GENERIC_INVALID_PAYMENT_REQUEST;
+ TALER_MERCHANT_parse_pay_uri_free (&pd);
+ break;
+ }
+ TALER_MERCHANT_parse_pay_uri_free (&pd);
+ }
+ ud.us = ANASTASIS_US_PAYMENT_REQUIRED;
+ ud.details.payment.payment_request = pso->pay_uri;
+ break;
+ case MHD_HTTP_PAYLOAD_TOO_LARGE:
+ ud.us = ANASTASIS_US_CLIENT_ERROR;
+ ud.ec = TALER_EC_GENERIC_UPLOAD_EXCEEDS_LIMIT;
+ break;
+ case MHD_HTTP_LENGTH_REQUIRED:
+ GNUNET_break (0);
+ ud.ec = TALER_JSON_get_error_code2 (data,
+ data_size);
+ ud.us = ANASTASIS_US_SERVER_ERROR;
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ ud.ec = TALER_JSON_get_error_code2 (data,
+ data_size);
+ ud.us = ANASTASIS_US_SERVER_ERROR;
+ break;
+ default:
+ ud.ec = TALER_JSON_get_error_code2 (data,
+ data_size);
+ ud.us = ANASTASIS_US_SERVER_ERROR;
+ break;
+ }
+ pso->cb (pso->cb_cls,
+ &ud);
+ pso->cb = NULL;
+ ANASTASIS_policy_store_cancel (pso);
+}
+
+
+/**
+ * Handle HTTP header received by curl.
+ *
+ * @param buffer one line of HTTP header data
+ * @param size size of an item
+ * @param nitems number of items passed
+ * @param userdata our `struct ANASTASIS_StorePolicyOperation *`
+ * @return `size * nitems`
+ */
+static size_t
+handle_header (char *buffer,
+ size_t size,
+ size_t nitems,
+ void *userdata)
+{
+ struct ANASTASIS_PolicyStoreOperation *pso = userdata;
+ size_t total = size * nitems;
+ char *ndup;
+ const char *hdr_type;
+ char *hdr_val;
+ char *sp;
+
+ ndup = GNUNET_strndup (buffer,
+ total);
+ hdr_type = strtok_r (ndup,
+ ":",
+ &sp);
+ if (NULL == hdr_type)
+ {
+ GNUNET_free (ndup);
+ return total;
+ }
+ hdr_val = strtok_r (NULL,
+ "",
+ &sp);
+ if (NULL == hdr_val)
+ {
+ GNUNET_free (ndup);
+ return total;
+ }
+ if (' ' == *hdr_val)
+ hdr_val++;
+ if (0 == strcasecmp (hdr_type,
+ "Taler"))
+ {
+ size_t len;
+
+ /* found payment URI we care about! */
+ GNUNET_free (pso->pay_uri); /* In case of duplicate header */
+ pso->pay_uri = GNUNET_strdup (hdr_val);
+ len = strlen (pso->pay_uri);
+ while ( (len > 0) &&
+ ( ('\n' == pso->pay_uri[len - 1]) ||
+ ('\r' == pso->pay_uri[len - 1]) ) )
+ {
+ len--;
+ pso->pay_uri[len] = '\0';
+ }
+ }
+
+ if (0 == strcasecmp (hdr_type,
+ ANASTASIS_HTTP_HEADER_POLICY_VERSION))
+ {
+ size_t len;
+
+ /* found policy version we care about! */
+ GNUNET_free (pso->policy_version); /* In case of duplicate header */
+ pso->policy_version = GNUNET_strdup (hdr_val);
+ len = strlen (pso->policy_version);
+ while ( (len > 0) &&
+ ( ('\n' == pso->policy_version[len - 1]) ||
+ ('\r' == pso->policy_version[len - 1]) ) )
+ {
+ len--;
+ pso->policy_version[len] = '\0';
+ }
+ }
+
+ if (0 == strcasecmp (hdr_type,
+ ANASTASIS_HTTP_HEADER_POLICY_EXPIRATION))
+ {
+ size_t len;
+
+ /* found policy expiration we care about! */
+ GNUNET_free (pso->policy_expiration); /* In case of duplicate header */
+ pso->policy_expiration = GNUNET_strdup (hdr_val);
+ len = strlen (pso->policy_expiration);
+ while ( (len > 0) &&
+ ( ('\n' == pso->policy_expiration[len - 1]) ||
+ ('\r' == pso->policy_expiration[len - 1]) ) )
+ {
+ len--;
+ pso->policy_expiration[len] = '\0';
+ }
+ }
+
+ GNUNET_free (ndup);
+ return total;
+}
+
+
+struct ANASTASIS_PolicyStoreOperation *
+ANASTASIS_policy_store (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const struct ANASTASIS_CRYPTO_AccountPrivateKeyP *anastasis_priv,
+ const void *recovery_data,
+ size_t recovery_data_size,
+ uint32_t payment_years_requested,
+ const struct ANASTASIS_PaymentSecretP *payment_secret,
+ struct GNUNET_TIME_Relative payment_timeout,
+ ANASTASIS_PolicyStoreCallback cb,
+ void *cb_cls)
+{
+ struct ANASTASIS_PolicyStoreOperation *pso;
+ struct ANASTASIS_AccountSignatureP account_sig;
+ unsigned long long tms;
+ CURL *eh;
+ struct curl_slist *job_headers;
+ struct ANASTASIS_UploadSignaturePS usp = {
+ .purpose.purpose = htonl (TALER_SIGNATURE_ANASTASIS_POLICY_UPLOAD),
+ .purpose.size = htonl (sizeof (usp))
+ };
+
+ tms = (unsigned long long) (payment_timeout.rel_value_us
+ / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
+ GNUNET_CRYPTO_hash (recovery_data,
+ recovery_data_size,
+ &usp.new_recovery_data_hash);
+ GNUNET_CRYPTO_eddsa_sign (&anastasis_priv->priv,
+ &usp,
+ &account_sig.eddsa_sig);
+ /* setup our HTTP headers */
+ job_headers = NULL;
+ {
+ struct curl_slist *ext;
+ char *val;
+ char *hdr;
+
+ /* Set Anastasis-Policy-Signature header */
+ val = GNUNET_STRINGS_data_to_string_alloc (&account_sig,
+ sizeof (account_sig));
+ GNUNET_asprintf (&hdr,
+ "%s: %s",
+ ANASTASIS_HTTP_HEADER_POLICY_SIGNATURE,
+ val);
+ GNUNET_free (val);
+ ext = curl_slist_append (job_headers,
+ hdr);
+ GNUNET_free (hdr);
+ if (NULL == ext)
+ {
+ GNUNET_break (0);
+ curl_slist_free_all (job_headers);
+ return NULL;
+ }
+ job_headers = ext;
+
+ /* set Etag header */
+ val = GNUNET_STRINGS_data_to_string_alloc (&usp.new_recovery_data_hash,
+ sizeof (struct GNUNET_HashCode));
+ GNUNET_asprintf (&hdr,
+ "%s: %s",
+ MHD_HTTP_HEADER_IF_NONE_MATCH,
+ val);
+ GNUNET_free (val);
+ ext = curl_slist_append (job_headers,
+ hdr);
+ GNUNET_free (hdr);
+ if (NULL == ext)
+ {
+ GNUNET_break (0);
+ curl_slist_free_all (job_headers);
+ return NULL;
+ }
+ job_headers = ext;
+
+ /* Setup Payment-Identifier header */
+ if (NULL != payment_secret)
+ {
+ char *paid_order_id;
+
+ paid_order_id = GNUNET_STRINGS_data_to_string_alloc (
+ payment_secret,
+ sizeof (*payment_secret));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Beginning policy store operation with payment secret `%s'\n",
+ paid_order_id);
+ GNUNET_asprintf (&hdr,
+ "%s: %s",
+ ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER,
+ paid_order_id);
+ GNUNET_free (paid_order_id);
+ ext = curl_slist_append (job_headers,
+ hdr);
+ GNUNET_free (hdr);
+ if (NULL == ext)
+ {
+ GNUNET_break (0);
+ curl_slist_free_all (job_headers);
+ return NULL;
+ }
+ job_headers = ext;
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Beginning policy store operation without payment secret\n");
+ }
+ }
+ /* Finished setting up headers */
+ pso = GNUNET_new (struct ANASTASIS_PolicyStoreOperation);
+ pso->postcopy = GNUNET_memdup (recovery_data,
+ recovery_data_size);
+ pso->new_upload_hash = usp.new_recovery_data_hash;
+ {
+ char *acc_pub_str;
+ char *path;
+ struct ANASTASIS_CRYPTO_AccountPublicKeyP pub;
+ char timeout_ms[32];
+ char pyrs[32];
+
+ GNUNET_snprintf (timeout_ms,
+ sizeof (timeout_ms),
+ "%llu",
+ tms);
+ GNUNET_snprintf (pyrs,
+ sizeof (pyrs),
+ "%u",
+ (unsigned int) payment_years_requested);
+ GNUNET_CRYPTO_eddsa_key_get_public (&anastasis_priv->priv,
+ &pub.pub);
+ acc_pub_str
+ = GNUNET_STRINGS_data_to_string_alloc (&pub,
+ sizeof (pub));
+ GNUNET_asprintf (&path,
+ "policy/%s",
+ acc_pub_str);
+ GNUNET_free (acc_pub_str);
+ pso->url = TALER_url_join (backend_url,
+ path,
+ "storage_duration",
+ (0 != payment_years_requested)
+ ? pyrs
+ : NULL,
+ "timeout_ms",
+ (0 != payment_timeout.rel_value_us)
+ ? timeout_ms
+ : NULL,
+ NULL);
+ GNUNET_free (path);
+ }
+ pso->ctx = ctx;
+ pso->cb = cb;
+ pso->cb_cls = cb_cls;
+ eh = ANASTASIS_curl_easy_get_ (pso->url);
+ if (0 != tms)
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_TIMEOUT_MS,
+ (long) (tms + 5000)));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_POSTFIELDS,
+ pso->postcopy));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_POSTFIELDSIZE,
+ (long) recovery_data_size));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_HEADERFUNCTION,
+ &handle_header));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_HEADERDATA,
+ pso));
+ pso->job = GNUNET_CURL_job_add_raw (ctx,
+ eh,
+ job_headers,
+ &handle_policy_store_finished,
+ pso);
+ curl_slist_free_all (job_headers);
+ return pso;
+}
diff --git a/src/restclient/anastasis_api_truth_store.c b/src/restclient/anastasis_api_truth_store.c
new file mode 100644
index 0000000..ebd7d10
--- /dev/null
+++ b/src/restclient/anastasis_api_truth_store.c
@@ -0,0 +1,354 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 2021 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/anastasis_api_truth_store.c
+ * @brief Implementation of the /truth GET and POST
+ * @author Christian Grothoff
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include "anastasis_service.h"
+#include "anastasis_api_curl_defaults.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_merchant_service.h>
+
+
+struct ANASTASIS_TruthStoreOperation
+{
+ /**
+ * Complete URL where the backend offers /truth
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * The CURL context to connect to the backend
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * The callback to pass the backend response to
+ */
+ ANASTASIS_TruthStoreCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to data (for cleanup).
+ */
+ char *data;
+
+ /**
+ * Payment URI we received from the service, or NULL.
+ */
+ char *pay_uri;
+};
+
+
+void
+ANASTASIS_truth_store_cancel (
+ struct ANASTASIS_TruthStoreOperation *tso)
+{
+ if (NULL != tso->job)
+ {
+ GNUNET_CURL_job_cancel (tso->job);
+ tso->job = NULL;
+ }
+ GNUNET_free (tso->pay_uri);
+ GNUNET_free (tso->url);
+ GNUNET_free (tso->data);
+ GNUNET_free (tso);
+}
+
+
+/**
+ * Callback to process POST /truth response
+ *
+ * @param cls the `struct ANASTASIS_TruthStoreOperation`
+ * @param response_code HTTP response code, 0 on error
+ * @param data
+ * @param data_size
+ */
+static void
+handle_truth_store_finished (void *cls,
+ long response_code,
+ const void *data,
+ size_t data_size)
+{
+ struct ANASTASIS_TruthStoreOperation *tso = cls;
+ struct ANASTASIS_UploadDetails ud;
+
+ tso->job = NULL;
+ memset (&ud, 0, sizeof (ud));
+ ud.http_status = response_code;
+ ud.ec = TALER_EC_NONE;
+ switch (response_code)
+ {
+ case 0:
+ break;
+ case MHD_HTTP_NO_CONTENT:
+ ud.us = ANASTASIS_US_SUCCESS;
+ break;
+ case MHD_HTTP_NOT_MODIFIED:
+ ud.us = ANASTASIS_US_SUCCESS;
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ GNUNET_break (0);
+ ud.ec = TALER_JSON_get_error_code2 (data,
+ data_size);
+ break;
+ case MHD_HTTP_PAYMENT_REQUIRED:
+ {
+ struct TALER_MERCHANT_PayUriData pd;
+
+ if ( (NULL == tso->pay_uri) ||
+ (GNUNET_OK !=
+ TALER_MERCHANT_parse_pay_uri (tso->pay_uri,
+ &pd)) )
+ {
+ GNUNET_break_op (0);
+ ud.ec = TALER_EC_ANASTASIS_GENERIC_INVALID_PAYMENT_REQUEST;
+ break;
+ }
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (
+ pd.order_id,
+ strlen (pd.order_id),
+ &ud.details.payment.ps,
+ sizeof (ud.details.payment.ps)))
+ {
+ GNUNET_break (0);
+ ud.ec = TALER_EC_ANASTASIS_GENERIC_INVALID_PAYMENT_REQUEST;
+ TALER_MERCHANT_parse_pay_uri_free (&pd);
+ break;
+ }
+ TALER_MERCHANT_parse_pay_uri_free (&pd);
+ }
+ ud.us = ANASTASIS_US_PAYMENT_REQUIRED;
+ ud.details.payment.payment_request = tso->pay_uri;
+ break;
+ case MHD_HTTP_CONFLICT:
+ ud.us = ANASTASIS_US_CONFLICTING_TRUTH;
+ break;
+ case MHD_HTTP_LENGTH_REQUIRED:
+ GNUNET_break (0);
+ break;
+ case MHD_HTTP_REQUEST_ENTITY_TOO_LARGE:
+ ud.ec = TALER_JSON_get_error_code2 (data,
+ data_size);
+ break;
+ case MHD_HTTP_TOO_MANY_REQUESTS:
+ ud.ec = TALER_JSON_get_error_code2 (data,
+ data_size);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ ud.ec = TALER_JSON_get_error_code2 (data,
+ data_size);
+ break;
+ default:
+ GNUNET_break (0);
+ ud.ec = TALER_JSON_get_error_code2 (data,
+ data_size);
+ break;
+ }
+ tso->cb (tso->cb_cls,
+ &ud);
+ tso->cb = NULL;
+ ANASTASIS_truth_store_cancel (tso);
+}
+
+
+/**
+ * Handle HTTP header received by curl.
+ *
+ * @param buffer one line of HTTP header data
+ * @param size size of an item
+ * @param nitems number of items passed
+ * @param userdata our `struct ANASTASIS_StorePolicyOperation *`
+ * @return `size * nitems`
+ */
+static size_t
+handle_header (char *buffer,
+ size_t size,
+ size_t nitems,
+ void *userdata)
+{
+ struct ANASTASIS_TruthStoreOperation *tso = userdata;
+ size_t total = size * nitems;
+ char *ndup;
+ const char *hdr_type;
+ char *hdr_val;
+ char *sp;
+
+ ndup = GNUNET_strndup (buffer,
+ total);
+ hdr_type = strtok_r (ndup,
+ ":",
+ &sp);
+ if (NULL == hdr_type)
+ {
+ GNUNET_free (ndup);
+ return total;
+ }
+ hdr_val = strtok_r (NULL,
+ "",
+ &sp);
+ if (NULL == hdr_val)
+ {
+ GNUNET_free (ndup);
+ return total;
+ }
+ if (' ' == *hdr_val)
+ hdr_val++;
+ if (0 == strcasecmp (hdr_type,
+ ANASTASIS_HTTP_HEADER_TALER))
+ {
+ size_t len;
+
+ /* found payment URI we care about! */
+ tso->pay_uri = GNUNET_strdup (hdr_val);
+ len = strlen (tso->pay_uri);
+ while ( (len > 0) &&
+ ( ('\n' == tso->pay_uri[len - 1]) ||
+ ('\r' == tso->pay_uri[len - 1]) ) )
+ {
+ len--;
+ tso->pay_uri[len] = '\0';
+ }
+ }
+ GNUNET_free (ndup);
+ return total;
+}
+
+
+struct ANASTASIS_TruthStoreOperation *
+ANASTASIS_truth_store (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid,
+ const char *type,
+ const struct ANASTASIS_CRYPTO_EncryptedKeyShareP *encrypted_keyshare,
+ const char *truth_mime,
+ size_t encrypted_truth_size,
+ const void *encrypted_truth,
+ uint32_t payment_years_requested,
+ struct GNUNET_TIME_Relative payment_timeout,
+ ANASTASIS_TruthStoreCallback cb,
+ void *cb_cls)
+{
+ struct ANASTASIS_TruthStoreOperation *tso;
+ CURL *eh;
+ char *json_str;
+ unsigned long long tms;
+
+ tms = (unsigned long long) (payment_timeout.rel_value_us
+ / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
+ tso = GNUNET_new (struct ANASTASIS_TruthStoreOperation);
+ {
+ char *uuid_str;
+ char *path;
+ char timeout_ms[32];
+
+ GNUNET_snprintf (timeout_ms,
+ sizeof (timeout_ms),
+ "%llu",
+ tms);
+ uuid_str = GNUNET_STRINGS_data_to_string_alloc (uuid,
+ sizeof (*uuid));
+ GNUNET_asprintf (&path,
+ "truth/%s",
+ uuid_str);
+ tso->url = TALER_url_join (backend_url,
+ path,
+ "timeout_ms",
+ (0 != payment_timeout.rel_value_us)
+ ? timeout_ms
+ : NULL,
+ NULL);
+ GNUNET_free (path);
+ GNUNET_free (uuid_str);
+ }
+ {
+ json_t *truth_data;
+
+ truth_data = json_pack ("{s:o," /* encrypted KeyShare */
+ " s:s," /* type */
+ " s:o," /* nonce */
+ " s:s," /* truth_mime */
+ " s:I}", /* payment years */
+ "keyshare_data",
+ GNUNET_JSON_from_data_auto (encrypted_keyshare),
+ "type",
+ type,
+ "encrypted_truth",
+ GNUNET_JSON_from_data (encrypted_truth,
+ encrypted_truth_size),
+ "truth_mime",
+ (NULL != truth_mime)
+ ? truth_mime
+ : "",
+ "storage_duration_years",
+ (json_int_t) payment_years_requested);
+ GNUNET_assert (NULL != truth_data);
+ json_str = json_dumps (truth_data,
+ JSON_COMPACT);
+ GNUNET_assert (NULL != json_str);
+ json_decref (truth_data);
+ }
+ tso->ctx = ctx;
+ tso->data = json_str;
+ tso->cb = cb;
+ tso->cb_cls = cb_cls;
+ eh = ANASTASIS_curl_easy_get_ (tso->url);
+ if (0 != tms)
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_TIMEOUT_MS,
+ (long) (tms + 5000)));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_POSTFIELDS,
+ json_str));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_POSTFIELDSIZE,
+ strlen (json_str)));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_HEADERFUNCTION,
+ &handle_header));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_HEADERDATA,
+ tso));
+ tso->job = GNUNET_CURL_job_add_raw (ctx,
+ eh,
+ GNUNET_NO,
+ &handle_truth_store_finished,
+ tso);
+ return tso;
+}
diff --git a/src/stasis/Datenbank-Schema.xml b/src/stasis/Datenbank-Schema.xml
new file mode 100644
index 0000000..0591d22
--- /dev/null
+++ b/src/stasis/Datenbank-Schema.xml
@@ -0,0 +1 @@
+<mxfile host="Electron" modified="2020-06-11T14:26:00.822Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/13.2.1 Chrome/83.0.4103.100 Electron/9.0.3 Safari/537.36" etag="akhPIe-celB06UuOFgWh" version="13.2.1" type="device"><diagram id="UF0pCA7TQTvobU7b0bed" name="Seite-1">7V1bc9o4GP01zOw+bMdXcB6B0sts0mYSdrd9YhRbwZ7aFiOLBPrrV7IlX5BpXGyDmVGbmaCrLX1HR0f6JDIy59HuIwYb/w55MBwZmrcbme9HhqHrmkF/sZh9FuPYWhaxxoHHMxURj8FPyCNFtm3gwaSSkSAUkmBTjXRRHEOXVOIAxui1mu0ZhdWnbsAaShGPLgjl2P8Cj/iiXeObIuETDNY+f7RjTLKECIjMvCWJDzz0WooyFyNzjhEi2adoN4ch6zzRL1m5D0dS8xfDMCZNCnBDvIBwy9v2TwIxfzeyFw1OXoMoBDENzZ5RTB55ikbDrh+E3i3Yoy17YEKA+0OEZj7CwU+aH4Q0SacRNBkTbk9jzGoLwnCOQoTT55hQY/8rJR9ZjfxZGCa07L1onX4QdQd2lYy3ICHiLVEYgk0SPKXvzQpGAK+DeIYIQRHPJFr5ofpSz+k/mg7CYB3TOJc+i3YS74usNbpFw3Lvc4O8QEzgrhTFrfERoggSvKdZeKp1w5HBh4ah8SpeC6Dl48CvYIzjm2N7nVddmJ9+4AioR4MpoWFL0bBiydPZ9+ViKgPDBxv2kbaZBCB8oKMNxGuWOiNow/s1hM/CDpi/MPv8JPpeL/VtlnfGOiygA27Ko6PA89Jay4CJUYrIZAPcIF7fZk8xrSLqgT+NRSFa5XOYjjWfVgZj9jaIAAKecixvUBCTtP/sGf2hvTzX3tkjm7Z0TsN6EaY/LDsmcxQnBIMgNTikgHuFDHQzD6PNkkIMitaWx419gB2jFjtGY+wIrIybQcXU2kPFkqBy/3drdDyVR2ONpZuCpIqIzgDxls1ZMzmc37auecS6+azFX6MyMVSsXjKz2ZOVdZkR4G4TYEACFK88QGjkdPn5bvG4nN7dX4wd0squnBq6pQK7IRUIymgFEpkLLs0EGSCugQb03nmgExPbkomXeEufqoTipYSioVWFol43+9s1eNCdLgAxlgBBGCAyrfjv9GH+afrwx9j6UynGMyrGfJi2kox1oOlETEyUZmw5WYyP2HdQorFmbQBjF+83BHqrH3C/oibGsFhS0kaZupY3uGuu0JR+PIEY6gRkHTF0oS4MWV1cmhauR0Dmo23YAtKRTAyoXKCtoL2eLiZplT4qiQdFBsMhA7G3eA4y0G8UGZwuEJwjph0UGQjnh7R4iAJa156ZVrHA8FhA15wzagJdwoiigcaaQDti22HRgOx8LNYJB+rAo13XrxNKccJJnGBaZ+QE2RuhOKExJxzzIQyKE0xZGhQOp/59TYoETiKBsXY+EjCVMGjhdL4KYWDKwoCakHXd3kPuNmLNUY6nSzmeJpOq48m0a0Z/na9Zd6wOsPGrM0oVv9O8tLNMW5dkE8jnL8t+pg7lkqpnnGOyo/UpJrsLNKljTG1nlN7PL3Rh57HsesR8RjlYWpacUcov1bvW/H12cBpONl3okLHstLg0N1yP1MyH3KCl5kSeACrEsPJB4it2uBZ20LUznnucKLf16fQwuQq39UQ+5nawlFCMMHBGEEXOwgiyzFSM0JgRej/c1omJaw6yuC7axmSVBGulFK6GF+waZ3ZvvKDOtLTghas402LJbol7sGdb1YlsarVXPbrMXrV103DQd3JFUnZjbDJIZLvVaiv6vFvRVnP4XOJCrezYUFvRvzVR5ONt0LcjbHlJeZZL9komnsIA57xHa8urxw8XZ4Dr0Yn2VawfLXnDeYMSskqXkEwG9ScLFAOcwgB1dx/6YgBL7Si3mP6vYkfZkqd/sShIoIsZlJUKGBoH1N586I0E1CZyCxK4ChEwlnmetjxOgJtediBBBNVZ54FSQd2Fh94OoMiThaKCxgdQjp1jHxQViMOzXZpYDffuhnvd1Ya64T7pYmtIXW1osfzv/WpDFyYeyz4BycTQW0Oxs82uQJL9AwzTC1CLIkXqEZ9Ewi8EY2/KvuWWBhcP1MxLdAfiPUvYBeSbyEQ/f2dmeWfz0Hvh70kDexGIaRO/lQOlUixYFEtDopxsIW6NBG2xy5vK97yJGMajYhuH9cIvR2jJWrpWY648Eqe99wIrb1FnRP6MewbSUemLtrISYkfQOoBB1h5eqECCVI9+UJF1WFHWC1JFKaTydjfzNzcgkh5RJhDzl/ZO0+wqbEzTfAM4aegeYvZdAGxDLIfk7yL3KALLcBPn1wcDt8nB97pJtNMUb4fAdfTe8ObIIgZE6ZmYF+asntKkz1+WjhI2vd3ZbKxrjjjGjXMenHWUzjld5zi965xOTCzrHM4Izxi4OSUoRhguI5zz4KyjvtmhBSMYR2w9LEaocXyC9NADjZx9/Xq7mH5RdDBcOhCnE85CB8oP2oIOhugHpcHij9pkK4ziTwOZi/8B</diagram></mxfile> \ No newline at end of file
diff --git a/src/stasis/Makefile.am b/src/stasis/Makefile.am
new file mode 100644
index 0000000..a1d6584
--- /dev/null
+++ b/src/stasis/Makefile.am
@@ -0,0 +1,95 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include
+
+plugindir = $(libdir)/anastasis
+
+if HAVE_POSTGRESQL
+plugin_LTLIBRARIES = \
+ libanastasis_plugin_db_postgres.la
+endif
+
+if USE_COVERAGE
+ AM_CFLAGS = --coverage -O0
+ XLIB = -lgcov
+endif
+
+sqldir = $(prefix)/share/anastasis/sql/
+
+sql_DATA = \
+ stasis-0000.sql \
+ stasis-0001.sql \
+ drop0001.sql
+
+pkgcfgdir = $(prefix)/share/anastasis/config.d/
+
+pkgcfg_DATA = \
+ stasis-postgres.conf
+
+bin_PROGRAMS = \
+ anastasis-dbinit
+
+anastasis_dbinit_SOURCES = \
+ anastasis-dbinit.c
+
+anastasis_dbinit_LDADD = \
+ $(LIBGCRYPT_LIBS) \
+ libanastasisdb.la \
+ $(top_builddir)/src/util/libanastasisutil.la \
+ -lgnunetutil \
+ -ltalerutil \
+ -ltalerpq \
+ $(XLIB)
+
+
+lib_LTLIBRARIES = \
+ libanastasisdb.la
+
+libanastasisdb_la_SOURCES = \
+ anastasis_db_plugin.c
+libanastasisdb_la_LIBADD = \
+ -lgnunetpq \
+ -lpq \
+ -lgnunetutil \
+ -lltdl \
+ $(XLIB)
+libanastasisdb_la_LDFLAGS = \
+ $(POSTGRESQL_LDFLAGS) \
+ -version-info 2:0:0 \
+ -no-undefined
+
+libanastasis_plugin_db_postgres_la_SOURCES = \
+ plugin_anastasis_postgres.c
+libanastasis_plugin_db_postgres_la_LIBADD = \
+ $(LTLIBINTL)
+libanastasis_plugin_db_postgres_la_LDFLAGS = \
+ $(top_builddir)/src/util/libanastasisutil.la \
+ $(ANASTASIS_PLUGIN_LDFLAGS) \
+ -lgnunetpq \
+ -lpq \
+ -ltalerpq \
+ -ltalerutil \
+ -lgnunetutil \
+ $(XLIB)
+
+check_PROGRAMS = \
+ $(TESTS)
+
+test_anastasis_db_postgres_SOURCES = \
+ test_anastasis_db.c
+test_anastasis_db_postgres_LDFLAGS = \
+ $(top_builddir)/src/util/libanastasisutil.la \
+ libanastasisdb.la \
+ -lgnunetutil \
+ -lgnunetpq \
+ -ltalerutil \
+ -ltalerpq \
+ -luuid \
+ $(XLIB)
+
+AM_TESTS_ENVIRONMENT=export ANASTASIS_PREFIX=$${ANASTASIS_PREFIX:-@libdir@};export PATH=$${ANASTASIS_PREFIX:-@prefix@}/bin:$$PATH;unset XDG_DATA_HOME;unset XDG_CONFIG_HOME;
+TESTS = \
+ test_anastasis_db-postgres
+
+EXTRA_DIST = \
+ test_anastasis_db_postgres.conf \
+ $(sql_DATA)
diff --git a/src/stasis/anastasis-dbinit.c b/src/stasis/anastasis-dbinit.c
new file mode 100644
index 0000000..5c0a174
--- /dev/null
+++ b/src/stasis/anastasis-dbinit.c
@@ -0,0 +1,112 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015 GNUnet e.V.
+
+ 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 util/anastasis-dbinit.c
+ * @brief Create tables for the merchant database.
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ */
+#include "platform.h"
+#include "anastasis_database_lib.h"
+
+
+/**
+ * Return value from main().
+ */
+static int global_ret;
+
+/**
+ * -r option: do full DB reset
+ */
+static int reset_db;
+
+/**
+ * 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)
+{
+ struct ANASTASIS_DatabasePlugin *plugin;
+
+ if (NULL ==
+ (plugin = ANASTASIS_DB_plugin_load (cfg)))
+ {
+ fprintf (stderr,
+ "Failed to initialize database plugin.\n");
+ global_ret = 1;
+ return;
+ }
+ if (reset_db)
+ {
+ (void) plugin->drop_tables (plugin->cls);
+ ANASTASIS_DB_plugin_unload (plugin);
+ plugin = ANASTASIS_DB_plugin_load (cfg);
+ }
+ ANASTASIS_DB_plugin_unload (plugin);
+}
+
+
+/**
+ * The main function of the database initialization tool.
+ * Used to initialize the Anastasis' database.
+ *
+ * @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_flag ('r',
+ "reset",
+ "reset database (DANGEROUS: all existing data is lost!)",
+ &reset_db),
+
+ 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_OS_init (ANASTASIS_project_data_default ());
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_log_setup ("anastasis-dbinit",
+ "INFO",
+ NULL));
+ if (GNUNET_OK !=
+ GNUNET_PROGRAM_run (argc, argv,
+ "anastasis-dbinit",
+ "Initialize anastasis database",
+ options,
+ &run, NULL))
+ return 1;
+ return global_ret;
+}
+
+
+/* end of anastasis-dbinit.c */
diff --git a/src/stasis/anastasis_db_plugin.c b/src/stasis/anastasis_db_plugin.c
new file mode 100644
index 0000000..6b5332c
--- /dev/null
+++ b/src/stasis/anastasis_db_plugin.c
@@ -0,0 +1,146 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015, 2016 GNUnet e.V. and INRIA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser 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 merchantdb/merchantdb_plugin.c
+ * @brief Logic to load database plugin
+ * @author Christian Grothoff
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ */
+#include "platform.h"
+#include "anastasis_database_plugin.h"
+#include <ltdl.h>
+
+
+/**
+ * Initialize the plugin.
+ *
+ * @param cfg configuration to use
+ * @return #GNUNET_OK on success
+ */
+struct ANASTASIS_DatabasePlugin *
+ANASTASIS_DB_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ char *plugin_name;
+ char *lib_name;
+ struct ANASTASIS_DatabasePlugin *plugin;
+
+ if (GNUNET_SYSERR ==
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "anastasis",
+ "db",
+ &plugin_name))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "anastasis",
+ "db");
+ return NULL;
+ }
+ (void) GNUNET_asprintf (&lib_name,
+ "libanastasis_plugin_db_%s",
+ plugin_name);
+ GNUNET_free (plugin_name);
+ plugin = GNUNET_PLUGIN_load (lib_name,
+ (void *) cfg);
+ if (NULL != plugin)
+ plugin->library_name = lib_name;
+ else
+ lib_name = NULL;
+ return plugin;
+}
+
+
+/**
+ * Shutdown the plugin.
+ *
+ * @param plugin the plugin to unload
+ */
+void
+ANASTASIS_DB_plugin_unload (struct ANASTASIS_DatabasePlugin *plugin)
+{
+ char *lib_name;
+
+ if (NULL == plugin)
+ return;
+ lib_name = plugin->library_name;
+ GNUNET_assert (NULL == GNUNET_PLUGIN_unload (lib_name,
+ plugin));
+ GNUNET_free (lib_name);
+}
+
+
+/**
+ * Libtool search path before we started.
+ */
+static char *old_dlsearchpath;
+
+
+/**
+ * Setup libtool paths.
+ */
+void __attribute__ ((constructor))
+plugin_init ()
+{
+ int err;
+ const char *opath;
+ char *path;
+ char *cpath;
+
+ err = lt_dlinit ();
+ if (err > 0)
+ {
+ fprintf (stderr,
+ _ ("Initialization of plugin mechanism failed: %s!\n"),
+ lt_dlerror ());
+ return;
+ }
+ opath = lt_dlgetsearchpath ();
+ if (NULL != opath)
+ old_dlsearchpath = GNUNET_strdup (opath);
+ path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_LIBDIR);
+ if (NULL != path)
+ {
+ if (NULL != opath)
+ {
+ GNUNET_asprintf (&cpath, "%s:%s", opath, path);
+ lt_dlsetsearchpath (cpath);
+ GNUNET_free (path);
+ GNUNET_free (cpath);
+ }
+ else
+ {
+ lt_dlsetsearchpath (path);
+ GNUNET_free (path);
+ }
+ }
+}
+
+
+/**
+ * Shutdown libtool.
+ */
+void __attribute__ ((destructor))
+plugin_fini ()
+{
+ lt_dlsetsearchpath (old_dlsearchpath);
+ if (NULL != old_dlsearchpath)
+ {
+ GNUNET_free (old_dlsearchpath);
+ }
+ lt_dlexit ();
+}
+
+
+/* end of anastasis_db_plugin.c */
diff --git a/src/stasis/anastasis_db_postgres.conf b/src/stasis/anastasis_db_postgres.conf
new file mode 100644
index 0000000..71a21ac
--- /dev/null
+++ b/src/stasis/anastasis_db_postgres.conf
@@ -0,0 +1,7 @@
+[anastasis]
+#The DB plugin to use
+DB = postgres
+
+[stasis-postgres]
+#The connection string the plugin has to use for connecting to the database
+CONFIG = postgres:///anastasis
diff --git a/src/stasis/drop0001.sql b/src/stasis/drop0001.sql
new file mode 100644
index 0000000..afc457d
--- /dev/null
+++ b/src/stasis/drop0001.sql
@@ -0,0 +1,37 @@
+--
+-- This file is part of ANASTASIS
+-- Copyright (C) 2014--2020 Anastasis Systems SA
+--
+-- ANASTASIS 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.
+--
+-- ANASTASIS 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
+-- ANASTASIS; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+-- Everything in one big transaction
+BEGIN;
+
+-- This script DROPs all of the tables we create.
+--
+-- Unlike the other SQL files, it SHOULD be updated to reflect the
+-- latest requirements for dropping tables.
+
+-- Drops for 0001.sql
+DROP TABLE IF EXISTS anastasis_truth CASCADE;
+DROP TABLE IF EXISTS anastasis_user CASCADE;
+DROP TABLE IF EXISTS anastasis_recdoc_payment;
+DROP TABLE IF EXISTS anastasis_recoverydocument;
+DROP TABLE IF EXISTS anastasis_challengecode;
+DROP TABLE IF EXISTS anastasis_challenge_payment;
+
+-- Unregister patch (0001.sql)
+SELECT _v.unregister_patch('stasis-0001');
+
+-- And we're out of here...
+COMMIT;
diff --git a/src/stasis/plugin_anastasis_postgres.c b/src/stasis/plugin_anastasis_postgres.c
new file mode 100644
index 0000000..4aba97c
--- /dev/null
+++ b/src/stasis/plugin_anastasis_postgres.c
@@ -0,0 +1,2301 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 2021 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file anastasis/plugin_anastasisdb_postgres.c
+ * @brief database helper functions for postgres used by the anastasis
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ * @author Christian Grothoff
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include "anastasis_database_plugin.h"
+#include "anastasis_database_lib.h"
+#include <taler/taler_pq_lib.h>
+
+/**
+ * How long do we keep transient accounts open (those that have
+ * not been paid at all, but are awaiting payment). This puts
+ * a cap on how long users have to make a payment after a payment
+ * request was generated.
+ */
+#define TRANSIENT_LIFETIME GNUNET_TIME_UNIT_WEEKS
+
+/**
+ * How often do we re-try if we run into a DB serialization error?
+ */
+#define MAX_RETRIES 3
+
+
+/**
+ * Type of the "cls" argument given to each of the functions in
+ * our API.
+ */
+struct PostgresClosure
+{
+
+ /**
+ * Postgres connection handle.
+ */
+ struct GNUNET_PQ_Context *conn;
+
+ /**
+ * Underlying configuration.
+ */
+ const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+ /**
+ * Name of the currently active transaction, NULL if none is active.
+ */
+ const char *transaction_name;
+
+ /**
+ * Currency we accept payments in.
+ */
+ char *currency;
+
+};
+
+
+/**
+ * Drop anastasis tables
+ *
+ * @param cls closure our `struct Plugin`
+ * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
+ */
+static int
+postgres_drop_tables (void *cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_Context *conn;
+
+ conn = GNUNET_PQ_connect_with_cfg (pg->cfg,
+ "stasis-postgres",
+ "drop",
+ NULL,
+ NULL);
+ if (NULL == conn)
+ return GNUNET_SYSERR;
+ GNUNET_PQ_disconnect (conn);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Check that the database connection is still up.
+ *
+ * @param pg connection to check
+ */
+static void
+check_connection (void *cls)
+{
+ struct PostgresClosure *pg = cls;
+
+ GNUNET_PQ_reconnect_if_down (pg->conn);
+}
+
+
+/**
+ * Do a pre-flight check that we are not in an uncommitted transaction.
+ * If we are, try to commit the previous transaction and output a warning.
+ * Does not return anything, as we will continue regardless of the outcome.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ */
+static void
+postgres_preflight (void *cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_execute ("COMMIT"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
+
+ if (NULL == pg->transaction_name)
+ return; /* all good */
+ if (GNUNET_OK ==
+ GNUNET_PQ_exec_statements (pg->conn,
+ es))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "BUG: Preflight check committed transaction `%s'!\n",
+ pg->transaction_name);
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "BUG: Preflight check failed to commit transaction `%s'!\n",
+ pg->transaction_name);
+ }
+ pg->transaction_name = NULL;
+}
+
+
+/**
+ * Start a transaction.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param name unique name identifying the transaction (for debugging),
+ * must point to a constant
+ * @return #GNUNET_OK on success
+ */
+static int
+begin_transaction (void *cls,
+ const char *name)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL SERIALIZABLE"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
+
+ check_connection (pg);
+ postgres_preflight (pg);
+ pg->transaction_name = name;
+ if (GNUNET_OK !=
+ GNUNET_PQ_exec_statements (pg->conn,
+ es))
+ {
+ TALER_LOG_ERROR ("Failed to start transaction\n");
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+* Roll back the current transaction of a database connection.
+*
+* @param cls the `struct PostgresClosure` with the plugin-specific state
+* @return #GNUNET_OK on success
+*/
+static void
+rollback (void *cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_execute ("ROLLBACK"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_exec_statements (pg->conn,
+ es))
+ {
+ TALER_LOG_ERROR ("Failed to rollback transaction\n");
+ GNUNET_break (0);
+ }
+ pg->transaction_name = NULL;
+}
+
+
+/**
+ * Commit the current transaction of a database connection.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @return transaction status code
+ */
+static enum GNUNET_DB_QueryStatus
+commit_transaction (void *cls)
+{
+ struct PostgresClosure *pg = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_PQ_QueryParam no_params[] = {
+ GNUNET_PQ_query_param_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "do_commit",
+ no_params);
+ pg->transaction_name = NULL;
+ return qs;
+}
+
+
+/**
+ * Function called to perform "garbage collection" on the
+ * database, expiring records we no longer require. Deletes
+ * all user records that are not paid up (and by cascade deletes
+ * the associated recovery documents). Also deletes expired
+ * truth and financial records older than @a fin_expire.
+ *
+ * @param cls closure
+ * @param expire_backups backups older than the given time stamp should be garbage collected
+ * @param expire_pending_payments payments still pending from since before
+ * this value should be garbage collected
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_gc (void *cls,
+ struct GNUNET_TIME_Absolute expire_backups,
+ struct GNUNET_TIME_Absolute expire_pending_payments)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_absolute_time (&expire_backups),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_QueryParam params2[] = {
+ GNUNET_PQ_query_param_absolute_time (&expire_pending_payments),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ postgres_preflight (pg);
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "gc_accounts",
+ params);
+ if (qs < 0)
+ return qs;
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "gc_recdoc_pending_payments",
+ params2);
+}
+
+
+/**
+ * Store encrypted recovery document.
+ *
+ * @param cls closure
+ * @param anastasis_pub public key of the user's account
+ * @param account_sig signature affirming storage request
+ * @param data_hash hash of @a data
+ * @param data contains encrypted_recovery_document
+ * @param data_size size of data blob
+ * @param payment_secret identifier for the payment, used to later charge on uploads
+ * @param[out] version set to the version assigned to the document by the database
+ * @return transaction status, 0 if upload could not be finished because @a payment_secret
+ * did not have enough upload left; HARD error if @a payment_secret is unknown, ...
+ */
+static enum ANASTASIS_DB_StoreStatus
+postgres_store_recovery_document (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *anastasis_pub,
+ const struct ANASTASIS_AccountSignatureP *account_sig,
+ const struct GNUNET_HashCode *recovery_data_hash,
+ const void *recovery_data,
+ size_t recovery_data_size,
+ const struct ANASTASIS_PaymentSecretP *payment_secret,
+ uint32_t *version)
+{
+ struct PostgresClosure *pg = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ postgres_preflight (pg);
+ for (unsigned int retry = 0; retry<MAX_RETRIES; retry++)
+ {
+ if (GNUNET_OK !=
+ begin_transaction (pg,
+ "store_recovery_document"))
+ {
+ GNUNET_break (0);
+ return ANASTASIS_DB_STORE_STATUS_HARD_ERROR;
+ }
+ /* get the current version and hash of the latest recovery document
+ for this account */
+ {
+ struct GNUNET_HashCode dh;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint32 ("version",
+ version),
+ GNUNET_PQ_result_spec_auto_from_type ("recovery_data_hash",
+ &dh),
+ GNUNET_PQ_result_spec_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "latest_recovery_version_select",
+ params,
+ rs);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ rollback (pg);
+ return ANASTASIS_DB_STORE_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ goto retry;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ *version = 1;
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* had an existing recovery_data, is it identical? */
+ if (0 == GNUNET_memcmp (&dh,
+ recovery_data_hash))
+ {
+ /* Yes. Previous identical recovery data exists */
+ rollback (pg);
+ return ANASTASIS_DB_STORE_STATUS_NO_RESULTS;
+ }
+ (*version)++;
+ break;
+ default:
+ rollback (pg);
+ return qs;
+ }
+ }
+
+ /* First, check if account exists */
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "user_select",
+ params,
+ rs);
+ }
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ rollback (pg);
+ return ANASTASIS_DB_STORE_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ goto retry;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ rollback (pg);
+ return ANASTASIS_DB_STORE_STATUS_PAYMENT_REQUIRED;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* handle interesting case below */
+ break;
+ }
+
+ {
+ uint32_t postcounter;
+
+ /* lookup if the user has enough uploads left and decrement */
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_auto_from_type (payment_secret),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint32 ("post_counter",
+ &postcounter),
+ GNUNET_PQ_result_spec_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "postcounter_select",
+ params,
+ rs);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ rollback (pg);
+ return ANASTASIS_DB_STORE_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ goto retry;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ rollback (pg);
+ return ANASTASIS_DB_STORE_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ }
+
+ if (0 == postcounter)
+ {
+ rollback (pg);
+ return ANASTASIS_DB_STORE_STATUS_STORE_LIMIT_EXCEEDED;
+ }
+ /* Decrement the postcounter by one */
+ postcounter--;
+
+ /* Update the postcounter in the Database */
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint32 (&postcounter),
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_auto_from_type (payment_secret),
+ GNUNET_PQ_query_param_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "postcounter_update",
+ params);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ rollback (pg);
+ return ANASTASIS_DB_STORE_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ goto retry;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break (0);
+ rollback (pg);
+ return ANASTASIS_DB_STORE_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ default:
+ rollback (pg);
+ return qs;
+ }
+ }
+ }
+
+ /* finally, actually insert the recovery document */
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_uint32 (version),
+ GNUNET_PQ_query_param_auto_from_type (account_sig),
+ GNUNET_PQ_query_param_auto_from_type (recovery_data_hash),
+ GNUNET_PQ_query_param_fixed_size (recovery_data,
+ recovery_data_size),
+ GNUNET_PQ_query_param_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "recovery_document_insert",
+ params);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ rollback (pg);
+ return ANASTASIS_DB_STORE_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ goto retry;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break (0);
+ rollback (pg);
+ return ANASTASIS_DB_STORE_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ qs = commit_transaction (pg);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ goto retry;
+ if (qs < 0)
+ return ANASTASIS_DB_STORE_STATUS_HARD_ERROR;
+ return ANASTASIS_DB_STORE_STATUS_SUCCESS;
+ }
+ }
+retry:
+ rollback (pg);
+ }
+ return ANASTASIS_DB_STORE_STATUS_SOFT_ERROR;
+}
+
+
+/**
+ * Increment account lifetime.
+ *
+ * @param cls closure
+ * @param anastasis_pub which account received a payment
+ * @param payment_identifier proof of payment, must be unique and match pending payment
+ * @param lifetime for how long is the account now paid (increment)
+ * @param[out] paid_until set to the end of the lifetime after the operation
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_increment_lifetime (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *anastasis_pub,
+ const struct ANASTASIS_PaymentSecretP *payment_identifier,
+ struct GNUNET_TIME_Relative lifetime,
+ struct GNUNET_TIME_Absolute *paid_until)
+{
+ struct PostgresClosure *pg = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ for (unsigned int retries = 0; retries<MAX_RETRIES; retries++)
+ {
+ if (GNUNET_OK !=
+ begin_transaction (pg,
+ "increment lifetime"))
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (payment_identifier),
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_end
+ };
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "recdoc_payment_done",
+ params);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ rollback (pg);
+ *paid_until = GNUNET_TIME_UNIT_ZERO_ABS;
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ goto retry;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* continued below */
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* continued below */
+ break;
+ }
+ }
+
+ {
+ enum GNUNET_DB_QueryStatus qs2;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_TIME_Absolute expiration;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_absolute_time ("expiration_date",
+ &expiration),
+ GNUNET_PQ_result_spec_end
+ };
+
+ qs2 = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "user_select",
+ params,
+ rs);
+ switch (qs2)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ rollback (pg);
+ return qs2;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ goto retry;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ /* inconsistent, cannot have recdoc payment but no user!? */
+ GNUNET_break (0);
+ rollback (pg);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ else
+ {
+ /* user does not exist, create new one */
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_absolute_time (&expiration),
+ GNUNET_PQ_query_param_end
+ };
+
+ expiration = GNUNET_TIME_relative_to_absolute (lifetime);
+ GNUNET_break (GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us !=
+ expiration.abs_value_us);
+ *paid_until = expiration;
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "user_insert",
+ params);
+ }
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ /* existing rec doc payment, return expiration */
+ *paid_until = expiration;
+ rollback (pg);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Payment existed, lifetime of account %s unchanged at %s\n",
+ TALER_B2S (anastasis_pub),
+ GNUNET_STRINGS_absolute_time_to_string (*paid_until));
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+ else
+ {
+ /* user exists, update expiration_date */
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_absolute_time (&expiration),
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_end
+ };
+
+ expiration = GNUNET_TIME_absolute_add (expiration,
+ lifetime);
+ GNUNET_break (GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us !=
+ expiration.abs_value_us);
+ *paid_until = expiration;
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "user_update",
+ params);
+ }
+ break;
+ }
+ }
+
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ goto retry;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break (0);
+ rollback (pg);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ qs = commit_transaction (pg);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ goto retry;
+ if (qs < 0)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Incremented lifetime of account %s to %s\n",
+ TALER_B2S (anastasis_pub),
+ GNUNET_STRINGS_absolute_time_to_string (*paid_until));
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+retry:
+ rollback (pg);
+ }
+ return GNUNET_DB_STATUS_SOFT_ERROR;
+}
+
+
+/**
+ * Update account lifetime to the maximum of the current
+ * value and @a eol.
+ *
+ * @param cls closure
+ * @param account_pub which account received a payment
+ * @param payment_identifier proof of payment, must be unique and match pending payment
+ * @param eol for how long is the account now paid (absolute)
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_update_lifetime (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *anastasis_pub,
+ const struct ANASTASIS_PaymentSecretP *payment_identifier,
+ struct GNUNET_TIME_Absolute eol)
+{
+ struct PostgresClosure *pg = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ for (unsigned int retries = 0; retries<MAX_RETRIES; retries++)
+ {
+ if (GNUNET_OK !=
+ begin_transaction (pg,
+ "update lifetime"))
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (payment_identifier),
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_end
+ };
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "recdoc_payment_done",
+ params);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ goto retry;
+ if (0 >= qs)
+ {
+ /* same payment made before, or unknown, or error
+ => no further action! */
+ rollback (pg);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Payment existed, lifetime of account %s unchanged\n",
+ TALER_B2S (anastasis_pub));
+ return qs;
+ }
+ }
+
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_TIME_Absolute expiration;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_absolute_time ("expiration_date",
+ &expiration),
+ GNUNET_PQ_result_spec_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "user_select",
+ params,
+ rs);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ goto retry;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ {
+ /* user does not exist, create new one */
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_absolute_time (&eol),
+ GNUNET_PQ_query_param_end
+ };
+
+ GNUNET_break (GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us !=
+ eol.abs_value_us);
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "user_insert",
+ params);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Created new account %s with expiration %s\n",
+ TALER_B2S (anastasis_pub),
+ GNUNET_STRINGS_absolute_time_to_string (eol));
+ }
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ {
+ /* user exists, update expiration_date */
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_absolute_time (&expiration),
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_end
+ };
+
+ expiration = GNUNET_TIME_absolute_max (expiration,
+ eol);
+ GNUNET_break (GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us !=
+ expiration.abs_value_us);
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "user_update",
+ params);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Updated account %s to new expiration %s\n",
+ TALER_B2S (anastasis_pub),
+ GNUNET_STRINGS_absolute_time_to_string (expiration));
+ }
+ break;
+ }
+ }
+
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ goto retry;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break (0);
+ rollback (pg);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ qs = commit_transaction (pg);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ goto retry;
+ if (qs < 0)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+retry:
+ rollback (pg);
+ }
+ return GNUNET_DB_STATUS_SOFT_ERROR;
+}
+
+
+/**
+ * Store payment. Used to begin a payment, not indicative
+ * that the payment actually was made. (That is done
+ * when we increment the account's lifetime.)
+ *
+ * @param cls closure
+ * @param anastasis_pub anastasis's public key
+ * @param post_counter how many uploads does @a amount pay for
+ * @param payment_secret payment secret which the user must provide with every upload
+ * @param amount how much we asked for
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_record_recdoc_payment (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *anastasis_pub,
+ uint32_t post_counter,
+ const struct ANASTASIS_PaymentSecretP *payment_secret,
+ const struct TALER_Amount *amount)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+ struct GNUNET_TIME_Absolute expiration;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_uint32 (&post_counter),
+ TALER_PQ_query_param_amount (amount),
+ GNUNET_PQ_query_param_auto_from_type (payment_secret),
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ postgres_preflight (pg);
+
+ /* because of constraint at user_id, first we have to verify
+ if user exists, and if not, create one */
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_absolute_time ("expiration_date",
+ &expiration),
+ GNUNET_PQ_result_spec_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "user_select",
+ params,
+ rs);
+ }
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ {
+ /* create new user with short lifetime */
+ struct GNUNET_TIME_Absolute exp
+ = GNUNET_TIME_relative_to_absolute (TRANSIENT_LIFETIME);
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_absolute_time (&exp),
+ GNUNET_PQ_query_param_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "user_insert",
+ params);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* successful, continue below */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Created new account %s with transient life until %s\n",
+ TALER_B2S (anastasis_pub),
+ GNUNET_STRINGS_absolute_time_to_string (exp));
+ break;
+ }
+ }
+ /* continue below */
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* handle case below */
+ break;
+ }
+
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "recdoc_payment_insert",
+ params);
+}
+
+
+/**
+ * Record truth upload payment was made.
+ *
+ * @param cls closure
+ * @param uuid the truth's UUID
+ * @param amount the amount that was paid
+ * @param duration how long is the truth paid for
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_record_truth_upload_payment (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid,
+ const struct TALER_Amount *amount,
+ struct GNUNET_TIME_Relative duration)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Absolute exp = GNUNET_TIME_relative_to_absolute (duration);
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (uuid),
+ TALER_PQ_query_param_amount (amount),
+ GNUNET_PQ_query_param_absolute_time (&exp),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "truth_payment_insert",
+ params);
+}
+
+
+/**
+ * Inquire whether truth upload payment was made.
+ *
+ * @param cls closure
+ * @param uuid the truth's UUID
+ * @param[out] paid_until set for how long this truth is paid for
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_check_truth_upload_paid (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid,
+ struct GNUNET_TIME_Absolute *paid_until)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (uuid),
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_absolute_time ("expiration",
+ paid_until),
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "truth_payment_select",
+ params,
+ rs);
+}
+
+
+/**
+ * Store payment for challenge.
+ *
+ * @param cls closure
+ * @param truth_key identifier of the challenge to pay
+ * @param payment_secret payment secret which the user must provide with every upload
+ * @param amount how much we asked for
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_record_challenge_payment (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ const struct ANASTASIS_PaymentSecretP *payment_secret,
+ const struct TALER_Amount *amount)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (truth_uuid),
+ TALER_PQ_query_param_amount (amount),
+ GNUNET_PQ_query_param_auto_from_type (payment_secret),
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "challenge_payment_insert",
+ params);
+}
+
+
+/**
+ * Store refund granted for challenge.
+ *
+ * @param cls closure
+ * @param truth_key identifier of the challenge to pay
+ * @param payment_secret payment secret which the user must provide with every upload
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_record_challenge_refund (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ const struct ANASTASIS_PaymentSecretP *payment_secret)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (payment_secret),
+ GNUNET_PQ_query_param_auto_from_type (truth_uuid),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "challenge_refund_update",
+ params);
+}
+
+
+/**
+ * Check payment identifier. Used to check if a payment identifier given by
+ * the user is valid (existing and paid).
+ *
+ * @param cls closure
+ * @param payment_secret payment secret which the user must provide with every upload
+ * @param[out] paid bool value to show if payment is paid
+ * @param[out] valid_counter bool value to show if post_counter is > 0
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_check_challenge_payment (
+ void *cls,
+ const struct ANASTASIS_PaymentSecretP *payment_secret,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ bool *paid)
+{
+ struct PostgresClosure *pg = cls;
+ uint8_t paid8;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (payment_secret),
+ GNUNET_PQ_query_param_auto_from_type (truth_uuid),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("paid",
+ &paid8),
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "challenge_payment_select",
+ params,
+ rs);
+ *paid = (0 != paid8);
+ return qs;
+}
+
+
+/**
+ * Check payment identifier. Used to check if a payment identifier given by
+ * the user is valid (existing and paid).
+ *
+ * @param cls closure
+ * @param payment_secret payment secret which the user must provide with every upload
+ * @param[out] paid bool value to show if payment is paid
+ * @param[out] valid_counter bool value to show if post_counter is > 0
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_check_payment_identifier (
+ void *cls,
+ const struct ANASTASIS_PaymentSecretP *payment_secret,
+ bool *paid,
+ bool *valid_counter)
+{
+ struct PostgresClosure *pg = cls;
+ uint32_t counter;
+ uint8_t paid8;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (payment_secret),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("paid",
+ &paid8),
+ GNUNET_PQ_result_spec_uint32 ("post_counter",
+ &counter),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "recdoc_payment_select",
+ params,
+ rs);
+
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ if (counter > 0)
+ *valid_counter = true;
+ else
+ *valid_counter = false;
+ *paid = (0 != paid8);
+ }
+ return qs;
+}
+
+
+/**
+ * Upload Truth, which contains the Truth and the KeyShare.
+ *
+ * @param cls closure
+ * @param truth_uuid the identifier for the Truth
+ * @param key_share_data contains information of an EncryptedKeyShare
+ * @param method name of method
+ * @param nonce nonce used to compute encryption key for encrypted_truth
+ * @param aes_gcm_tag authentication tag of encrypted_truth
+ * @param encrypted_truth contains the encrypted Truth which includes the ground truth i.e. H(challenge answer), phonenumber, SMS
+ * @param encrypted_truth_size the size of the Truth
+ * @param truth_expiration time till the according data will be stored
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_store_truth (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ const struct ANASTASIS_CRYPTO_EncryptedKeyShareP *key_share_data,
+ const char *mime_type,
+ const void *encrypted_truth,
+ size_t encrypted_truth_size,
+ const char *method,
+ struct GNUNET_TIME_Relative truth_expiration)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Absolute expiration = GNUNET_TIME_absolute_get ();
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (truth_uuid),
+ GNUNET_PQ_query_param_auto_from_type (key_share_data),
+ GNUNET_PQ_query_param_string (method),
+ GNUNET_PQ_query_param_fixed_size (encrypted_truth,
+ encrypted_truth_size),
+ GNUNET_PQ_query_param_string (mime_type),
+ TALER_PQ_query_param_absolute_time (&expiration),
+ GNUNET_PQ_query_param_end
+ };
+
+
+ expiration = GNUNET_TIME_absolute_add (expiration,
+ truth_expiration);
+ GNUNET_TIME_round_abs (&expiration);
+ check_connection (pg);
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "truth_insert",
+ params);
+}
+
+
+/**
+ * Get the encrypted truth to validate the challenge response
+ *
+ * @param cls closure
+ * @param truth_uuid the identifier for the Truth
+ * @param[out] truth contains the encrypted truth
+ * @param[out] truth_size size of the encrypted truth
+ * @param[out] truth_mime mime type of truth
+ * @param[out] method type of the challenge
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+postgres_get_escrow_challenge (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ void **truth,
+ size_t *truth_size,
+ char **truth_mime,
+ char **method)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (truth_uuid),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_variable_size ("encrypted_truth",
+ truth,
+ truth_size),
+ GNUNET_PQ_result_spec_string ("truth_mime",
+ truth_mime),
+ GNUNET_PQ_result_spec_string ("method_name",
+ method),
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "truth_select",
+ params,
+ rs);
+}
+
+
+/**
+ * Lookup (encrypted) key share by @a truth_uuid.
+ *
+ * @param cls closure
+ * @param truth_uuid the identifier for the Truth
+ * @param[out] key_share contains the encrypted Keyshare
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+postgres_get_key_share (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ struct ANASTASIS_CRYPTO_EncryptedKeyShareP *key_share)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (truth_uuid),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("key_share_data",
+ key_share),
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "key_share_select",
+ params,
+ rs);
+}
+
+
+/**
+ * Check if an account exists, and if so, return the
+ * current @a recovery_document_hash.
+ *
+ * @param cls closure
+ * @param anastasis_pub account identifier
+ * @param[out] paid_until until when is the account paid up?
+ * @param[out] recovery_data_hash set to hash of @a recovery document
+ * @param[out] version set to the recovery policy version
+ * @return transaction status
+ */
+enum ANASTASIS_DB_AccountStatus
+postgres_lookup_account (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *anastasis_pub,
+ struct GNUNET_TIME_Absolute *paid_until,
+ struct GNUNET_HashCode *recovery_data_hash,
+ uint32_t *version)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ postgres_preflight (pg);
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_absolute_time ("expiration_date",
+ paid_until),
+ GNUNET_PQ_result_spec_auto_from_type ("recovery_data_hash",
+ recovery_data_hash),
+ GNUNET_PQ_result_spec_uint32 ("version",
+ version),
+ GNUNET_PQ_result_spec_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "latest_recovery_version_select",
+ params,
+ rs);
+ }
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ return ANASTASIS_DB_ACCOUNT_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return ANASTASIS_DB_ACCOUNT_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ break; /* handle interesting case below */
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ return ANASTASIS_DB_ACCOUNT_STATUS_VALID_HASH_RETURNED;
+ }
+
+ /* check if account exists */
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_absolute_time ("expiration_date",
+ paid_until),
+ GNUNET_PQ_result_spec_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "user_select",
+ params,
+ rs);
+ }
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ return ANASTASIS_DB_ACCOUNT_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return ANASTASIS_DB_ACCOUNT_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* indicates: no account */
+ return ANASTASIS_DB_ACCOUNT_STATUS_PAYMENT_REQUIRED;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* indicates: no backup */
+ *version = UINT32_MAX;
+ memset (recovery_data_hash,
+ 0,
+ sizeof (*recovery_data_hash));
+ return ANASTASIS_DB_ACCOUNT_STATUS_NO_RESULTS;
+ default:
+ GNUNET_break (0);
+ return ANASTASIS_DB_ACCOUNT_STATUS_HARD_ERROR;
+ }
+}
+
+
+/**
+ * Fetch latest recovery document for user.
+ *
+ * @param cls closure
+ * @param anastasis_pub public key of the user's account
+ * @param account_sig signature
+ * @param recovery_data_hash hash of the current recovery data
+ * @param data_size size of data blob
+ * @param data blob which contains the recovery document
+ * @param version[OUT] set to the version number of the policy being returned
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+postgres_get_latest_recovery_document (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *anastasis_pub,
+ struct ANASTASIS_AccountSignatureP *account_sig,
+ struct GNUNET_HashCode *recovery_data_hash,
+ size_t *data_size,
+ void **data,
+ uint32_t *version)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint32 ("version",
+ version),
+ GNUNET_PQ_result_spec_auto_from_type ("account_sig",
+ account_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("recovery_data_hash",
+ recovery_data_hash),
+ GNUNET_PQ_result_spec_variable_size ("recovery_data",
+ data,
+ data_size),
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ postgres_preflight (pg);
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "latest_recoverydocument_select",
+ params,
+ rs);
+}
+
+
+/**
+ * Fetch recovery document for user according given version.
+ *
+ * @param cls closure
+ * @param anastasis_pub public key of the user's account
+ * @param version the version number of the policy the user requests
+ * @param[out] account_sig signature
+ * @param[out] recovery_data_hash hash of the current recovery data
+ * @param[out] data_size size of data blob
+ * @param[out] data blob which contains the recovery document
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+postgres_get_recovery_document (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *anastasis_pub,
+ uint32_t version,
+ struct ANASTASIS_AccountSignatureP *account_sig,
+ struct GNUNET_HashCode *recovery_data_hash,
+ size_t *data_size,
+ void **data)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_uint32 (&version),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("account_sig",
+ account_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("recovery_data_hash",
+ recovery_data_hash),
+ GNUNET_PQ_result_spec_variable_size ("recovery_data",
+ data,
+ data_size),
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "recoverydocument_select",
+ params,
+ rs);
+}
+
+
+/**
+ * Closure for check_valid_code().
+ */
+struct CheckValidityContext
+{
+ /**
+ * Code to check for.
+ */
+ const struct GNUNET_HashCode *hashed_code;
+
+ /**
+ * Truth we are processing.
+ */
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid;
+
+ /**
+ * Database context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Set to true if a code matching @e hashed_code was found.
+ */
+ bool valid;
+
+ /**
+ * Set to true if we had a database failure.
+ */
+ bool db_failure;
+
+};
+
+
+/**
+ * Helper function for #postgres_verify_challenge_code().
+ * To be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct CheckValidityContext *`
+ * @param result the postgres result
+ * @param num_result the number of results in @a result
+ */
+static void
+check_valid_code (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct CheckValidityContext *cvc = cls;
+ struct PostgresClosure *pg = cvc->pg;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ uint64_t server_code;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("code",
+ &server_code),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ cvc->db_failure = true;
+ return;
+ }
+ {
+ struct GNUNET_HashCode shashed_code;
+
+ ANASTASIS_hash_answer (server_code,
+ &shashed_code);
+ if (0 ==
+ GNUNET_memcmp (&shashed_code,
+ cvc->hashed_code))
+ {
+ cvc->valid = true;
+ }
+ else
+ {
+ /* count failures to prevent brute-force attacks */
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (cvc->truth_uuid),
+ GNUNET_PQ_query_param_uint64 (&server_code),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "challengecode_update_retry",
+ params);
+ if (qs <= 0)
+ {
+ GNUNET_break (0);
+ cvc->db_failure = true;
+ }
+ }
+ }
+ }
+}
+
+
+/**
+ * Verify the provided code with the code on the server.
+ * If the code matches the function will return with success, if the code
+ * does not match, the retry counter will be decreased by one.
+ *
+ * @param cls closure
+ * @param truth_pub identification of the challenge which the code corresponds to
+ * @param hashed_code code which the user provided and wants to verify
+ * @return code validity status
+ */
+enum ANASTASIS_DB_CodeStatus
+postgres_verify_challenge_code (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ const struct GNUNET_HashCode *hashed_code)
+{
+ struct PostgresClosure *pg = cls;
+ struct CheckValidityContext cvc = {
+ .truth_uuid = truth_uuid,
+ .hashed_code = hashed_code,
+ .pg = pg
+ };
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (truth_uuid),
+ TALER_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ GNUNET_TIME_round_abs (&now);
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "challengecode_select",
+ params,
+ &check_valid_code,
+ &cvc);
+ if ( (qs < 0) ||
+ (cvc.db_failure) )
+ return ANASTASIS_DB_CODE_STATUS_HARD_ERROR;
+ if (cvc.valid)
+ return ANASTASIS_DB_CODE_STATUS_VALID_CODE_STORED;
+ if (0 == qs)
+ return ANASTASIS_DB_CODE_STATUS_NO_RESULTS;
+ return ANASTASIS_DB_CODE_STATUS_CHALLENGE_CODE_MISMATCH;
+}
+
+
+/**
+ * Lookup pending payment for a certain challenge.
+ *
+ * @param cls closure
+ * @param truth_uuid identification of the challenge
+ * @param[out] payment_secret set to the challenge payment secret
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+postgres_lookup_challenge_payment (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ struct ANASTASIS_PaymentSecretP *payment_secret)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+ struct GNUNET_TIME_Absolute recent
+ = GNUNET_TIME_absolute_subtract (now,
+ ANASTASIS_CHALLENGE_OFFER_LIFETIME);
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (truth_uuid),
+ GNUNET_PQ_query_param_absolute_time (&recent),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("payment_identifier",
+ payment_secret),
+ GNUNET_PQ_result_spec_end
+ };
+
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "challenge_pending_payment_select",
+ params,
+ rs);
+}
+
+
+/**
+ * Update payment status of challenge
+ *
+ * @param cls closure
+ * @param truth_uuid which challenge received a payment
+ * @param payment_identifier proof of payment, must be unique and match pending payment
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+postgres_update_challenge_payment (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ const struct ANASTASIS_PaymentSecretP *payment_identifier)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (payment_identifier),
+ GNUNET_PQ_query_param_auto_from_type (truth_uuid),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "challenge_payment_done",
+ params);
+}
+
+
+/**
+ * Create a new challenge code for a given challenge identified by the challenge
+ * public key. The function will first check if there is already a valid code
+ * for this challenge present and won't insert a new one in this case.
+ *
+ * @param cls closure
+ * @param truth_uuid the identifier for the challenge
+ * @param rotation_period for how long is the code available
+ * @param validity_period for how long is the code available
+ * @param retry_counter amount of retries allowed
+ * @param[out] retransmission_date when to next retransmit
+ * @param[out] code set to the code which will be checked for later
+ * @return transaction status,
+ * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if we are out of valid tries,
+ * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if @a code is now in the DB
+ */
+enum GNUNET_DB_QueryStatus
+postgres_create_challenge_code (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ struct GNUNET_TIME_Relative rotation_period,
+ struct GNUNET_TIME_Relative validity_period,
+ unsigned int retry_counter,
+ struct GNUNET_TIME_Absolute *retransmission_date,
+ uint64_t *code)
+{
+ struct PostgresClosure *pg = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+ struct GNUNET_TIME_Absolute expiration_date;
+ struct GNUNET_TIME_Absolute ex_rot;
+
+ check_connection (pg);
+ GNUNET_TIME_round_abs (&now);
+ expiration_date = GNUNET_TIME_absolute_add (now,
+ validity_period);
+ ex_rot = GNUNET_TIME_absolute_subtract (now,
+ rotation_period);
+ for (unsigned int retries = 0; retries<MAX_RETRIES; retries++)
+ {
+ if (GNUNET_OK !=
+ begin_transaction (pg,
+ "create_challenge_code"))
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ {
+ uint32_t old_retry_counter;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (truth_uuid),
+ TALER_PQ_query_param_absolute_time (&now),
+ TALER_PQ_query_param_absolute_time (&ex_rot),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("code",
+ code),
+ GNUNET_PQ_result_spec_uint32 ("retry_counter",
+ &old_retry_counter),
+ GNUNET_PQ_result_spec_absolute_time ("retransmission_date",
+ retransmission_date),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "challengecode_select_meta",
+ params,
+ rs);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ goto retry;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* no active challenge, create fresh one (below) */
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ if (0 == old_retry_counter)
+ {
+ rollback (pg);
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+ rollback (pg);
+ return qs;
+ }
+ }
+
+ *code = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE,
+ INT64_MAX);
+ *retransmission_date = GNUNET_TIME_UNIT_ZERO_ABS;
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (truth_uuid),
+ GNUNET_PQ_query_param_uint64 (code),
+ TALER_PQ_query_param_absolute_time (&now),
+ TALER_PQ_query_param_absolute_time (&expiration_date),
+ GNUNET_PQ_query_param_uint32 (&retry_counter),
+ GNUNET_PQ_query_param_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "challengecode_insert",
+ params);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ rollback (pg);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ goto retry;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break (0);
+ rollback (pg);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ }
+ qs = commit_transaction (pg);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ goto retry;
+ if (qs < 0)
+ return qs;
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+retry:
+ rollback (pg);
+ }
+ return GNUNET_DB_STATUS_SOFT_ERROR;
+}
+
+
+/**
+ * Remember in the database that we successfully sent a challenge.
+ *
+ * @param cls closure
+ * @param payment_secret payment secret which the user must provide with every upload
+ * @param truth_uuid the identifier for the challenge
+ * @param code the challenge that was sent
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_mark_challenge_sent (
+ void *cls,
+ const struct ANASTASIS_PaymentSecretP *payment_secret,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ uint64_t code)
+{
+ struct PostgresClosure *pg = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ {
+ struct GNUNET_TIME_Absolute now;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (truth_uuid),
+ GNUNET_PQ_query_param_uint64 (&code),
+ TALER_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_end
+ };
+
+ now = GNUNET_TIME_absolute_get ();
+ GNUNET_TIME_round_abs (&now);
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "challengecode_mark_sent",
+ params);
+ if (qs <= 0)
+ return qs;
+ }
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (truth_uuid),
+ GNUNET_PQ_query_param_auto_from_type (payment_secret),
+ GNUNET_PQ_query_param_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "challengepayment_dec_counter",
+ params);
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; /* probably was free */
+ return qs;
+ }
+}
+
+
+/**
+ * Function called to remove all expired codes from the database.
+ * FIXME maybe implemented as part of postgres_gc() in the future.
+ *
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+postgres_challenge_gc (void *cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Absolute time_now = GNUNET_TIME_absolute_get ();
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_absolute_time (&time_now),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ postgres_preflight (pg);
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "gc_challengecodes",
+ params);
+}
+
+
+/**
+ * Initialize Postgres database subsystem.
+ *
+ * @param cls a configuration instance
+ * @return NULL on error, otherwise a `struct TALER_ANASTASISDB_Plugin`
+ */
+void *
+libanastasis_plugin_db_postgres_init (void *cls)
+{
+ struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+ struct PostgresClosure *pg;
+ struct ANASTASIS_DatabasePlugin *plugin;
+ struct GNUNET_PQ_PreparedStatement ps[] = {
+ GNUNET_PQ_make_prepare ("user_insert",
+ "INSERT INTO anastasis_user "
+ "(user_id"
+ ",expiration_date"
+ ") VALUES "
+ "($1, $2);",
+ 2),
+ GNUNET_PQ_make_prepare ("do_commit",
+ "COMMIT",
+ 0),
+ GNUNET_PQ_make_prepare ("user_select",
+ "SELECT"
+ " expiration_date "
+ "FROM anastasis_user"
+ " WHERE user_id=$1"
+ " FOR UPDATE;",
+ 1),
+ GNUNET_PQ_make_prepare ("user_update",
+ "UPDATE anastasis_user"
+ " SET "
+ " expiration_date=$1"
+ " WHERE user_id=$2;",
+ 2),
+ GNUNET_PQ_make_prepare ("recdoc_payment_insert",
+ "INSERT INTO anastasis_recdoc_payment "
+ "(user_id"
+ ",post_counter"
+ ",amount_val"
+ ",amount_frac"
+ ",payment_identifier"
+ ",creation_date"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6);",
+ 6),
+ GNUNET_PQ_make_prepare ("challenge_payment_insert",
+ "INSERT INTO anastasis_challenge_payment "
+ "(truth_uuid"
+ ",amount_val"
+ ",amount_frac"
+ ",payment_identifier"
+ ",creation_date"
+ ") VALUES "
+ "($1, $2, $3, $4, $5);",
+ 5),
+ GNUNET_PQ_make_prepare ("truth_payment_insert",
+ "INSERT INTO anastasis_truth_payment "
+ "(truth_uuid"
+ ",amount_val"
+ ",amount_frac"
+ ",expiration"
+ ") VALUES "
+ "($1, $2, $3, $4);",
+ 4),
+ GNUNET_PQ_make_prepare ("recdoc_payment_done",
+ "UPDATE anastasis_recdoc_payment "
+ "SET"
+ " paid=TRUE "
+ "WHERE"
+ " payment_identifier=$1"
+ " AND"
+ " user_id=$2"
+ " AND"
+ " paid=FALSE;",
+ 2),
+ GNUNET_PQ_make_prepare ("challenge_refund_update",
+ "UPDATE anastasis_challenge_payment "
+ "SET"
+ " refunded=TRUE "
+ "WHERE"
+ " payment_identifier=$1"
+ " AND"
+ " paid=TRUE"
+ " AND"
+ " truth_uuid=$2;",
+ 2),
+ GNUNET_PQ_make_prepare ("challenge_payment_done",
+ "UPDATE anastasis_challenge_payment "
+ "SET"
+ " paid=TRUE "
+ "WHERE"
+ " payment_identifier=$1"
+ " AND"
+ " refunded=FALSE"
+ " AND"
+ " truth_uuid=$2"
+ " AND"
+ " paid=FALSE;",
+ 2),
+ GNUNET_PQ_make_prepare ("recdoc_payment_select",
+ "SELECT"
+ " creation_date"
+ ",post_counter"
+ ",amount_val"
+ ",amount_frac"
+ ",paid"
+ " FROM anastasis_recdoc_payment"
+ " WHERE payment_identifier=$1;",
+ 1),
+ GNUNET_PQ_make_prepare ("truth_payment_select",
+ "SELECT"
+ " expiration"
+ " FROM anastasis_truth_payment"
+ " WHERE truth_uuid=$1"
+ " AND expiration>$2;",
+ 2),
+ GNUNET_PQ_make_prepare ("challenge_payment_select",
+ "SELECT"
+ " creation_date"
+ ",amount_val"
+ ",amount_frac"
+ ",paid"
+ " FROM anastasis_challenge_payment"
+ " WHERE payment_identifier=$1"
+ " AND truth_uuid=$2"
+ " AND refunded=FALSE"
+ " AND counter>0;",
+ 1),
+ GNUNET_PQ_make_prepare ("challenge_pending_payment_select",
+ "SELECT"
+ " creation_date"
+ ",payment_identifier"
+ ",amount_val"
+ ",amount_frac"
+ " FROM anastasis_challenge_payment"
+ " WHERE"
+ " paid=FALSE"
+ " AND"
+ " refunded=FALSE"
+ " AND"
+ " truth_uuid=$1"
+ " AND"
+ " creation_date > $2;",
+ 1),
+ GNUNET_PQ_make_prepare ("recdoc_payments_select",
+ "SELECT"
+ " user_id"
+ ",payment_identifier"
+ ",amount_val"
+ ",amount_frac"
+ " FROM anastasis_recdoc_payment"
+ " WHERE paid=FALSE;",
+ 0),
+ GNUNET_PQ_make_prepare ("gc_accounts",
+ "DELETE FROM anastasis_user "
+ "WHERE"
+ " expiration_date < $1;",
+ 1),
+ GNUNET_PQ_make_prepare ("gc_recdoc_pending_payments",
+ "DELETE FROM anastasis_recdoc_payment "
+ "WHERE"
+ " paid=FALSE"
+ " AND"
+ " creation_date < $1;",
+ 1),
+ GNUNET_PQ_make_prepare ("gc_challenge_pending_payments",
+ "DELETE FROM anastasis_challenge_payment "
+ "WHERE"
+ " (paid=FALSE"
+ " OR"
+ " refunded=TRUE)"
+ " AND"
+ " creation_date < $1;",
+ 1),
+ GNUNET_PQ_make_prepare ("truth_insert",
+ "INSERT INTO anastasis_truth "
+ "(truth_uuid"
+ ",key_share_data"
+ ",method_name"
+ ",encrypted_truth"
+ ",truth_mime"
+ ",expiration"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6);",
+ 6),
+ GNUNET_PQ_make_prepare ("recovery_document_insert",
+ "INSERT INTO anastasis_recoverydocument "
+ "(user_id"
+ ",version"
+ ",account_sig"
+ ",recovery_data_hash"
+ ",recovery_data"
+ ") VALUES "
+ "($1, $2, $3, $4, $5);",
+ 5),
+ GNUNET_PQ_make_prepare ("truth_select",
+ "SELECT "
+ " method_name"
+ ",encrypted_truth"
+ ",truth_mime"
+ " FROM anastasis_truth"
+ " WHERE truth_uuid =$1;",
+ 1),
+ GNUNET_PQ_make_prepare ("latest_recoverydocument_select",
+ "SELECT "
+ " version"
+ ",account_sig"
+ ",recovery_data_hash"
+ ",recovery_data"
+ " FROM anastasis_recoverydocument"
+ " WHERE user_id =$1 "
+ " ORDER BY version DESC"
+ " LIMIT 1;",
+ 1),
+ GNUNET_PQ_make_prepare ("latest_recovery_version_select",
+ "SELECT"
+ " version"
+ ",recovery_data_hash"
+ ",expiration_date"
+ " FROM anastasis_recoverydocument"
+ " JOIN anastasis_user USING (user_id)"
+ " WHERE user_id=$1"
+ " ORDER BY version DESC"
+ " LIMIT 1;",
+ 1),
+ GNUNET_PQ_make_prepare ("recoverydocument_select",
+ "SELECT "
+ " account_sig"
+ ",recovery_data_hash"
+ ",recovery_data"
+ " FROM anastasis_recoverydocument"
+ " WHERE user_id=$1"
+ " AND version=$2;",
+ 2),
+ GNUNET_PQ_make_prepare ("postcounter_select",
+ "SELECT"
+ " post_counter"
+ " FROM anastasis_recdoc_payment"
+ " WHERE user_id=$1"
+ " AND payment_identifier=$2;",
+ 2),
+ GNUNET_PQ_make_prepare ("postcounter_update",
+ "UPDATE "
+ "anastasis_recdoc_payment "
+ "SET "
+ "post_counter=$1 "
+ "WHERE user_id =$2 "
+ "AND payment_identifier=$3;",
+ 3),
+ GNUNET_PQ_make_prepare ("key_share_select",
+ "SELECT "
+ "key_share_data "
+ "FROM "
+ "anastasis_truth "
+ "WHERE truth_uuid =$1;",
+ 1),
+ GNUNET_PQ_make_prepare ("challengecode_insert",
+ "INSERT INTO anastasis_challengecode "
+ "(truth_uuid"
+ ",code"
+ ",creation_date"
+ ",expiration_date"
+ ",retry_counter"
+ ") VALUES "
+ "($1, $2, $3, $4, $5);",
+ 5),
+ GNUNET_PQ_make_prepare ("challengecode_select",
+ "SELECT "
+ " code"
+ " FROM anastasis_challengecode"
+ " WHERE truth_uuid=$1"
+ " AND expiration_date > $2"
+ " AND retry_counter > 0;",
+ 2),
+ GNUNET_PQ_make_prepare ("challengecode_select_meta",
+ "SELECT "
+ " code"
+ ",retry_counter"
+ ",retransmission_date"
+ " FROM anastasis_challengecode"
+ " WHERE truth_uuid=$1"
+ " AND expiration_date > $2"
+ " AND creation_date > $3"
+ " ORDER BY creation_date DESC"
+ " LIMIT 1;",
+ 2),
+ GNUNET_PQ_make_prepare ("challengecode_update_retry",
+ "UPDATE anastasis_challengecode"
+ " SET retry_counter=retry_counter - 1"
+ " WHERE truth_uuid=$1"
+ " AND code=$2"
+ " AND retry_counter > 0;",
+ 1),
+ GNUNET_PQ_make_prepare ("challengepayment_dec_counter",
+ "UPDATE anastasis_challenge_payment"
+ " SET counter=counter - 1"
+ " WHERE truth_uuid=$1"
+ " AND payment_identifier=$2"
+ " AND counter > 0;",
+ 2),
+ GNUNET_PQ_make_prepare ("challengecode_mark_sent",
+ "UPDATE anastasis_challengecode"
+ " SET retransmission_date=$3"
+ " WHERE truth_uuid=$1"
+ " AND code=$2"
+ " AND creation_date IN"
+ " (SELECT creation_date"
+ " FROM anastasis_challengecode"
+ " WHERE truth_uuid=$1"
+ " AND code=$2"
+ " ORDER BY creation_date DESC"
+ " LIMIT 1);",
+ 3),
+ GNUNET_PQ_make_prepare ("gc_challengecodes",
+ "DELETE FROM anastasis_challengecode "
+ "WHERE "
+ "expiration_date < $1;",
+ 1),
+ GNUNET_PQ_PREPARED_STATEMENT_END
+ };
+
+ pg = GNUNET_new (struct PostgresClosure);
+ pg->cfg = cfg;
+ pg->conn = GNUNET_PQ_connect_with_cfg (cfg,
+ "stasis-postgres",
+ "stasis-",
+ NULL,
+ ps);
+ if (NULL == pg->conn)
+ {
+ GNUNET_free (pg);
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "taler",
+ "CURRENCY",
+ &pg->currency))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "taler",
+ "CURRENCY");
+ GNUNET_PQ_disconnect (pg->conn);
+ GNUNET_free (pg);
+ return NULL;
+ }
+ plugin = GNUNET_new (struct ANASTASIS_DatabasePlugin);
+ plugin->cls = pg;
+ plugin->drop_tables = &postgres_drop_tables;
+ plugin->gc = &postgres_gc;
+ plugin->preflight = &postgres_preflight;
+ plugin->rollback = &rollback;
+ plugin->commit = &commit_transaction;
+ plugin->store_recovery_document = &postgres_store_recovery_document;
+ plugin->record_recdoc_payment = &postgres_record_recdoc_payment;
+ plugin->store_truth = &postgres_store_truth;
+ plugin->get_escrow_challenge = &postgres_get_escrow_challenge;
+ plugin->get_key_share = &postgres_get_key_share;
+ plugin->get_latest_recovery_document = &postgres_get_latest_recovery_document;
+ plugin->get_recovery_document = &postgres_get_recovery_document;
+ plugin->lookup_account = &postgres_lookup_account;
+ plugin->check_payment_identifier = &postgres_check_payment_identifier;
+ plugin->increment_lifetime = &postgres_increment_lifetime;
+ plugin->update_lifetime = &postgres_update_lifetime;
+ plugin->start = &begin_transaction;
+ plugin->check_connection = &check_connection;
+ plugin->verify_challenge_code = &postgres_verify_challenge_code;
+ plugin->create_challenge_code = &postgres_create_challenge_code;
+ plugin->mark_challenge_sent = &postgres_mark_challenge_sent;
+ plugin->challenge_gc = &postgres_challenge_gc;
+ plugin->record_truth_upload_payment = &postgres_record_truth_upload_payment;
+ plugin->check_truth_upload_paid = &postgres_check_truth_upload_paid;
+ plugin->record_challenge_payment = &postgres_record_challenge_payment;
+ plugin->record_challenge_refund = &postgres_record_challenge_refund;
+ plugin->check_challenge_payment = &postgres_check_challenge_payment;
+ plugin->lookup_challenge_payment = &postgres_lookup_challenge_payment;
+ plugin->update_challenge_payment = &postgres_update_challenge_payment;
+ return plugin;
+}
+
+
+/**
+ * Shutdown Postgres database subsystem.
+ *
+ * @param cls a `struct ANASTASIS_DB_STATUS_Plugin`
+ * @return NULL (always)
+ */
+void *
+libanastasis_plugin_db_postgres_done (void *cls)
+{
+ struct ANASTASIS_DatabasePlugin *plugin = cls;
+ struct PostgresClosure *pg = plugin->cls;
+
+ GNUNET_PQ_disconnect (pg->conn);
+ GNUNET_free (pg->currency);
+ GNUNET_free (pg);
+ GNUNET_free (plugin);
+ return NULL;
+}
+
+
+/* end of plugin_anastasisdb_postgres.c */
diff --git a/src/stasis/stasis-0000.sql b/src/stasis/stasis-0000.sql
new file mode 100644
index 0000000..116f409
--- /dev/null
+++ b/src/stasis/stasis-0000.sql
@@ -0,0 +1,293 @@
+-- LICENSE AND COPYRIGHT
+--
+-- Copyright (C) 2010 Hubert depesz Lubaczewski
+--
+-- This program is distributed under the (Revised) BSD License:
+-- L<http://www.opensource.org/licenses/bsd-license.php>
+--
+-- Redistribution and use in source and binary forms, with or without
+-- modification, are permitted provided that the following conditions
+-- are met:
+--
+-- * Redistributions of source code must retain the above copyright
+-- notice, this list of conditions and the following disclaimer.
+--
+-- * Redistributions in binary form must reproduce the above copyright
+-- notice, this list of conditions and the following disclaimer in the
+-- documentation and/or other materials provided with the distribution.
+--
+-- * Neither the name of Hubert depesz Lubaczewski's Organization
+-- nor the names of its contributors may be used to endorse or
+-- promote products derived from this software without specific
+-- prior written permission.
+--
+-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+-- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+-- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+-- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+-- CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+-- OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+-- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+--
+-- Code origin: https://gitlab.com/depesz/Versioning/blob/master/install.versioning.sql
+--
+--
+-- # NAME
+--
+-- **Versioning** - simplistic take on tracking and applying changes to databases.
+--
+-- # DESCRIPTION
+--
+-- This project strives to provide simple way to manage changes to
+-- database.
+--
+-- Instead of making changes on development server, then finding
+-- differences between production and development, deciding which ones
+-- should be installed on production, and finding a way to install them -
+-- you start with writing diffs themselves!
+--
+-- # INSTALLATION
+--
+-- To install versioning simply run install.versioning.sql in your database
+-- (all of them: production, stage, test, devel, ...).
+--
+-- # USAGE
+--
+-- In your files with patches to database, put whole logic in single
+-- transaction, and use \_v.\* functions - usually \_v.register_patch() at
+-- least to make sure everything is OK.
+--
+-- For example. Let's assume you have patch files:
+--
+-- ## 0001.sql:
+--
+-- ```
+-- create table users (id serial primary key, username text);
+-- ```
+--
+-- ## 0002.sql:
+--
+-- ```
+-- insert into users (username) values ('depesz');
+-- ```
+-- To change it to use versioning you would change the files, to this
+-- state:
+--
+-- 0000.sql:
+--
+-- ```
+-- BEGIN;
+-- select _v.register_patch('000-base', NULL, NULL);
+-- create table users (id serial primary key, username text);
+-- COMMIT;
+-- ```
+--
+-- ## 0002.sql:
+--
+-- ```
+-- BEGIN;
+-- select _v.register_patch('001-users', ARRAY['000-base'], NULL);
+-- insert into users (username) values ('depesz');
+-- COMMIT;
+-- ```
+--
+-- This will make sure that patch 001-users can only be applied after
+-- 000-base.
+--
+-- # AVAILABLE FUNCTIONS
+--
+-- ## \_v.register_patch( TEXT )
+--
+-- Registers named patch, or dies if it is already registered.
+--
+-- Returns integer which is id of patch in \_v.patches table - only if it
+-- succeeded.
+--
+-- ## \_v.register_patch( TEXT, TEXT[] )
+--
+-- Same as \_v.register_patch( TEXT ), but checks is all given patches (given as
+-- array in second argument) are already registered.
+--
+-- ## \_v.register_patch( TEXT, TEXT[], TEXT[] )
+--
+-- Same as \_v.register_patch( TEXT, TEXT[] ), but also checks if there are no conflicts with preexisting patches.
+--
+-- Third argument is array of names of patches that conflict with current one. So
+-- if any of them is installed - register_patch will error out.
+--
+-- ## \_v.unregister_patch( TEXT )
+--
+-- Removes information about given patch from the versioning data.
+--
+-- It doesn't remove objects that were created by this patch - just removes
+-- metainformation.
+--
+-- ## \_v.assert_user_is_superuser()
+--
+-- Make sure that current patch is being loaded by superuser.
+--
+-- If it's not - it will raise exception, and break transaction.
+--
+-- ## \_v.assert_user_is_not_superuser()
+--
+-- Make sure that current patch is not being loaded by superuser.
+--
+-- If it is - it will raise exception, and break transaction.
+--
+-- ## \_v.assert_user_is_one_of(TEXT, TEXT, ... )
+--
+-- Make sure that current patch is being loaded by one of listed users.
+--
+-- If ```current_user``` is not listed as one of arguments - function will raise
+-- exception and break the transaction.
+
+BEGIN;
+
+-- This file adds versioning support to database it will be loaded to.
+-- It requires that PL/pgSQL is already loaded - will raise exception otherwise.
+-- All versioning "stuff" (tables, functions) is in "_v" schema.
+
+-- All functions are defined as 'RETURNS SETOF INT4' to be able to make them to RETURN literally nothing (0 rows).
+-- >> RETURNS VOID<< IS similar, but it still outputs "empty line" in psql when calling.
+CREATE SCHEMA IF NOT EXISTS _v;
+COMMENT ON SCHEMA _v IS 'Schema for versioning data and functionality.';
+
+CREATE TABLE IF NOT EXISTS _v.patches (
+ patch_name TEXT PRIMARY KEY,
+ applied_tsz TIMESTAMPTZ NOT NULL DEFAULT now(),
+ applied_by TEXT NOT NULL,
+ requires TEXT[],
+ conflicts TEXT[]
+);
+COMMENT ON TABLE _v.patches IS 'Contains information about what patches are currently applied on database.';
+COMMENT ON COLUMN _v.patches.patch_name IS 'Name of patch, has to be unique for every patch.';
+COMMENT ON COLUMN _v.patches.applied_tsz IS 'When the patch was applied.';
+COMMENT ON COLUMN _v.patches.applied_by IS 'Who applied this patch (PostgreSQL username)';
+COMMENT ON COLUMN _v.patches.requires IS 'List of patches that are required for given patch.';
+COMMENT ON COLUMN _v.patches.conflicts IS 'List of patches that conflict with given patch.';
+
+CREATE OR REPLACE FUNCTION _v.register_patch( IN in_patch_name TEXT, IN in_requirements TEXT[], in_conflicts TEXT[], OUT versioning INT4 ) RETURNS setof INT4 AS $$
+DECLARE
+ t_text TEXT;
+ t_text_a TEXT[];
+ i INT4;
+BEGIN
+ -- Thanks to this we know only one patch will be applied at a time
+ LOCK TABLE _v.patches IN EXCLUSIVE MODE;
+
+ SELECT patch_name INTO t_text FROM _v.patches WHERE patch_name = in_patch_name;
+ IF FOUND THEN
+ RAISE EXCEPTION 'Patch % is already applied!', in_patch_name;
+ END IF;
+
+ t_text_a := ARRAY( SELECT patch_name FROM _v.patches WHERE patch_name = any( in_conflicts ) );
+ IF array_upper( t_text_a, 1 ) IS NOT NULL THEN
+ RAISE EXCEPTION 'Versioning patches conflict. Conflicting patche(s) installed: %.', array_to_string( t_text_a, ', ' );
+ END IF;
+
+ IF array_upper( in_requirements, 1 ) IS NOT NULL THEN
+ t_text_a := '{}';
+ FOR i IN array_lower( in_requirements, 1 ) .. array_upper( in_requirements, 1 ) LOOP
+ SELECT patch_name INTO t_text FROM _v.patches WHERE patch_name = in_requirements[i];
+ IF NOT FOUND THEN
+ t_text_a := t_text_a || in_requirements[i];
+ END IF;
+ END LOOP;
+ IF array_upper( t_text_a, 1 ) IS NOT NULL THEN
+ RAISE EXCEPTION 'Missing prerequisite(s): %.', array_to_string( t_text_a, ', ' );
+ END IF;
+ END IF;
+
+ INSERT INTO _v.patches (patch_name, applied_tsz, applied_by, requires, conflicts ) VALUES ( in_patch_name, now(), current_user, coalesce( in_requirements, '{}' ), coalesce( in_conflicts, '{}' ) );
+ RETURN;
+END;
+$$ language plpgsql;
+COMMENT ON FUNCTION _v.register_patch( TEXT, TEXT[], TEXT[] ) IS 'Function to register patches in database. Raises exception if there are conflicts, prerequisites are not installed or the migration has already been installed.';
+
+CREATE OR REPLACE FUNCTION _v.register_patch( TEXT, TEXT[] ) RETURNS setof INT4 AS $$
+ SELECT _v.register_patch( $1, $2, NULL );
+$$ language sql;
+COMMENT ON FUNCTION _v.register_patch( TEXT, TEXT[] ) IS 'Wrapper to allow registration of patches without conflicts.';
+CREATE OR REPLACE FUNCTION _v.register_patch( TEXT ) RETURNS setof INT4 AS $$
+ SELECT _v.register_patch( $1, NULL, NULL );
+$$ language sql;
+COMMENT ON FUNCTION _v.register_patch( TEXT ) IS 'Wrapper to allow registration of patches without requirements and conflicts.';
+
+CREATE OR REPLACE FUNCTION _v.unregister_patch( IN in_patch_name TEXT, OUT versioning INT4 ) RETURNS setof INT4 AS $$
+DECLARE
+ i INT4;
+ t_text_a TEXT[];
+BEGIN
+ -- Thanks to this we know only one patch will be applied at a time
+ LOCK TABLE _v.patches IN EXCLUSIVE MODE;
+
+ t_text_a := ARRAY( SELECT patch_name FROM _v.patches WHERE in_patch_name = ANY( requires ) );
+ IF array_upper( t_text_a, 1 ) IS NOT NULL THEN
+ RAISE EXCEPTION 'Cannot uninstall %, as it is required by: %.', in_patch_name, array_to_string( t_text_a, ', ' );
+ END IF;
+
+ DELETE FROM _v.patches WHERE patch_name = in_patch_name;
+ GET DIAGNOSTICS i = ROW_COUNT;
+ IF i < 1 THEN
+ RAISE EXCEPTION 'Patch % is not installed, so it can''t be uninstalled!', in_patch_name;
+ END IF;
+
+ RETURN;
+END;
+$$ language plpgsql;
+COMMENT ON FUNCTION _v.unregister_patch( TEXT ) IS 'Function to unregister patches in database. Dies if the patch is not registered, or if unregistering it would break dependencies.';
+
+CREATE OR REPLACE FUNCTION _v.assert_patch_is_applied( IN in_patch_name TEXT ) RETURNS TEXT as $$
+DECLARE
+ t_text TEXT;
+BEGIN
+ SELECT patch_name INTO t_text FROM _v.patches WHERE patch_name = in_patch_name;
+ IF NOT FOUND THEN
+ RAISE EXCEPTION 'Patch % is not applied!', in_patch_name;
+ END IF;
+ RETURN format('Patch %s is applied.', in_patch_name);
+END;
+$$ language plpgsql;
+COMMENT ON FUNCTION _v.assert_patch_is_applied( TEXT ) IS 'Function that can be used to make sure that patch has been applied.';
+
+CREATE OR REPLACE FUNCTION _v.assert_user_is_superuser() RETURNS TEXT as $$
+DECLARE
+ v_super bool;
+BEGIN
+ SELECT usesuper INTO v_super FROM pg_user WHERE usename = current_user;
+ IF v_super THEN
+ RETURN 'assert_user_is_superuser: OK';
+ END IF;
+ RAISE EXCEPTION 'Current user is not superuser - cannot continue.';
+END;
+$$ language plpgsql;
+COMMENT ON FUNCTION _v.assert_user_is_superuser() IS 'Function that can be used to make sure that patch is being applied using superuser account.';
+
+CREATE OR REPLACE FUNCTION _v.assert_user_is_not_superuser() RETURNS TEXT as $$
+DECLARE
+ v_super bool;
+BEGIN
+ SELECT usesuper INTO v_super FROM pg_user WHERE usename = current_user;
+ IF v_super THEN
+ RAISE EXCEPTION 'Current user is superuser - cannot continue.';
+ END IF;
+ RETURN 'assert_user_is_not_superuser: OK';
+END;
+$$ language plpgsql;
+COMMENT ON FUNCTION _v.assert_user_is_not_superuser() IS 'Function that can be used to make sure that patch is being applied using normal (not superuser) account.';
+
+CREATE OR REPLACE FUNCTION _v.assert_user_is_one_of(VARIADIC p_acceptable_users TEXT[] ) RETURNS TEXT as $$
+DECLARE
+BEGIN
+ IF current_user = any( p_acceptable_users ) THEN
+ RETURN 'assert_user_is_one_of: OK';
+ END IF;
+ RAISE EXCEPTION 'User is not one of: % - cannot continue.', p_acceptable_users;
+END;
+$$ language plpgsql;
+COMMENT ON FUNCTION _v.assert_user_is_one_of(TEXT[]) IS 'Function that can be used to make sure that patch is being applied by one of defined users.';
+
+COMMIT;
diff --git a/src/stasis/stasis-0001.sql b/src/stasis/stasis-0001.sql
new file mode 100644
index 0000000..beb886d
--- /dev/null
+++ b/src/stasis/stasis-0001.sql
@@ -0,0 +1,194 @@
+--
+-- This file is part of Anastasis
+-- Copyright (C) 2020, 2021 Anastasis SARL SA
+--
+-- ANASTASIS 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.
+--
+-- ANASTASIS 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
+-- ANASTASIS; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+-- Everything in one big transaction
+BEGIN;
+
+-- Check patch versioning is in place.
+SELECT _v.register_patch('stasis-0001', NULL, NULL);
+
+
+CREATE TABLE IF NOT EXISTS anastasis_truth_payment
+ (truth_uuid BYTEA PRIMARY KEY CHECK(LENGTH(truth_uuid)=32),
+ amount_val INT8 NOT NULL,
+ amount_frac INT4 NOT NULL,
+ expiration INT8 NOT NULL);
+COMMENT ON TABLE anastasis_truth_payment
+ IS 'Records about payments for truth uploads';
+COMMENT ON COLUMN anastasis_truth_payment.truth_uuid
+ IS 'Identifier of the truth';
+COMMENT ON COLUMN anastasis_truth_payment.amount_val
+ IS 'Amount we were paid';
+COMMENT ON COLUMN anastasis_truth_payment.amount_frac
+ IS 'Amount we were paid fraction';
+COMMENT ON COLUMN anastasis_truth_payment.expiration
+ IS 'At which date will the truth payment expire';
+
+
+CREATE TABLE IF NOT EXISTS anastasis_truth
+ (truth_uuid BYTEA PRIMARY KEY CHECK(LENGTH(truth_uuid)=32),
+ key_share_data BYTEA CHECK(LENGTH(key_share_data)=80) NOT NULL,
+ method_name VARCHAR NOT NULL,
+ encrypted_truth BYTEA NOT NULL,
+ truth_mime VARCHAR NOT NULL,
+ expiration INT8 NOT NULL);
+COMMENT ON TABLE anastasis_truth
+ IS 'Truth data is needed to authenticate clients during recovery';
+COMMENT ON COLUMN anastasis_truth.truth_uuid
+ IS 'The truth UUID uniquely identifies this truth record. Not a foreign key as we may offer storing truth for free.';
+COMMENT ON COLUMN anastasis_truth.key_share_data
+ IS 'Stores the encrypted key share used to recover the key (nonce, tag and keyshare)';
+COMMENT ON COLUMN anastasis_truth.method_name
+ IS 'Defines the authentication method (SMS, E-Mail, Question..)';
+COMMENT ON COLUMN anastasis_truth.encrypted_truth
+ IS 'Stores the encrypted authentication data';
+COMMENT ON COLUMN anastasis_truth.truth_mime
+ IS 'Defines the mime type of the stored authentcation data';
+COMMENT ON COLUMN anastasis_truth.expiration
+ IS 'At which date will the truth record expire';
+
+
+CREATE TABLE IF NOT EXISTS anastasis_user
+ (user_id BYTEA PRIMARY KEY CHECK(LENGTH(user_id)=32),
+ expiration_date INT8 NOT NULL);
+COMMENT ON TABLE anastasis_user
+ IS 'Saves a user which is using Anastasis';
+COMMENT ON COLUMN anastasis_user.user_id
+ IS 'Identifier of the user account';
+COMMENT ON COLUMN anastasis_user.expiration_date
+ IS 'At which date will the user record expire';
+
+
+CREATE TABLE IF NOT EXISTS anastasis_recdoc_payment
+ (payment_id BIGSERIAL PRIMARY KEY,
+ user_id BYTEA NOT NULL REFERENCES anastasis_user(user_id),
+ post_counter INT4 NOT NULL DEFAULT 0 CHECK(post_counter >= 0),
+ amount_val INT8 NOT NULL,
+ amount_frac INT4 NOT NULL,
+ payment_identifier BYTEA NOT NULL CHECK(LENGTH(payment_identifier)=32),
+ creation_date INT8 NOT NULL,
+ paid BOOLEAN NOT NULL DEFAULT FALSE);
+COMMENT ON TABLE anastasis_recdoc_payment
+ IS 'Records a payment for a recovery document';
+COMMENT ON COLUMN anastasis_recdoc_payment.payment_id
+ IS 'Serial number which identifies the payment';
+COMMENT ON COLUMN anastasis_recdoc_payment.user_id
+ IS 'Link to the corresponding user who paid';
+COMMENT ON COLUMN anastasis_recdoc_payment.post_counter
+ IS 'For how many posts does the user pay';
+COMMENT ON COLUMN anastasis_recdoc_payment.amount_val
+ IS 'Amount we were paid';
+COMMENT ON COLUMN anastasis_recdoc_payment.amount_frac
+ IS 'Amount we were paid fraction';
+COMMENT ON COLUMN anastasis_recdoc_payment.payment_identifier
+ IS 'Payment identifier which the user has to provide';
+COMMENT ON COLUMN anastasis_recdoc_payment.creation_date
+ IS 'Creation date of the payment';
+COMMENT ON COLUMN anastasis_recdoc_payment.paid
+ IS 'Is the payment finished';
+
+
+CREATE TABLE IF NOT EXISTS anastasis_challenge_payment
+ (payment_id BIGSERIAL PRIMARY KEY,
+ truth_uuid BYTEA CHECK(LENGTH(truth_uuid)=32) NOT NULL,
+ amount_val INT8 NOT NULL,
+ amount_frac INT4 NOT NULL,
+ payment_identifier BYTEA NOT NULL CHECK(LENGTH(payment_identifier)=32),
+ creation_date INT8 NOT NULL,
+ counter INT4 NOT NULL DEFAULT 3,
+ paid BOOLEAN NOT NULL DEFAULT FALSE,
+ refunded BOOLEAN NOT NULL DEFAULT FALSE
+ );
+COMMENT ON TABLE anastasis_recdoc_payment
+ IS 'Records a payment for a challenge';
+COMMENT ON COLUMN anastasis_challenge_payment.payment_id
+ IS 'Serial number which identifies the payment';
+COMMENT ON COLUMN anastasis_challenge_payment.truth_uuid
+ IS 'Link to the corresponding challenge which is paid';
+COMMENT ON COLUMN anastasis_challenge_payment.amount_val
+ IS 'Amount we were paid';
+COMMENT ON COLUMN anastasis_challenge_payment.amount_frac
+ IS 'Amount we were paid fraction';
+COMMENT ON COLUMN anastasis_challenge_payment.payment_identifier
+ IS 'Payment identifier which the user has to provide';
+COMMENT ON COLUMN anastasis_challenge_payment.counter
+ IS 'How many more times will we issue the challenge for the given payment';
+COMMENT ON COLUMN anastasis_challenge_payment.creation_date
+ IS 'Creation date of the payment';
+COMMENT ON COLUMN anastasis_challenge_payment.paid
+ IS 'Is the payment finished';
+COMMENT ON COLUMN anastasis_challenge_payment.refunded
+ IS 'Was the payment refunded';
+
+
+CREATE TABLE IF NOT EXISTS anastasis_recoverydocument
+ (user_id BYTEA NOT NULL REFERENCES anastasis_user(user_id),
+ version INT4 NOT NULL,
+ account_sig BYTEA NOT NULL CHECK(LENGTH(account_sig)=64),
+ recovery_data_hash BYTEA NOT NULL CHECK(length(recovery_data_hash)=64),
+ recovery_data BYTEA NOT NULL,
+ PRIMARY KEY (user_id, version));
+COMMENT ON TABLE anastasis_recoverydocument
+ IS 'Stores a recovery document which contains the policy and the encrypted core secret';
+COMMENT ON COLUMN anastasis_recoverydocument.user_id
+ IS 'Link to the owner of this recovery document';
+COMMENT ON COLUMN anastasis_recoverydocument.version
+ IS 'The version of this recovery document';
+COMMENT ON COLUMN anastasis_recoverydocument.account_sig
+ IS 'Signature of the recovery document';
+COMMENT ON COLUMN anastasis_recoverydocument.recovery_data_hash
+ IS 'Hash of the recovery document to prevent unnecessary uploads';
+COMMENT ON COLUMN anastasis_recoverydocument.recovery_data
+ IS 'Contains the encrypted policy and core secret';
+
+
+CREATE TABLE IF NOT EXISTS anastasis_challengecode
+ (truth_uuid BYTEA CHECK(LENGTH(truth_uuid)=32) NOT NULL,
+ code INT8 NOT NULL,
+ creation_date INT8 NOT NULL,
+ expiration_date INT8 NOT NULL,
+ retransmission_date INT8 NOT NULL DEFAULT 0,
+ retry_counter INT4 NOT NULL);
+COMMENT ON TABLE anastasis_challengecode
+ IS 'Stores a code which is checked for the authentication by SMS, E-Mail..';
+COMMENT ON COLUMN anastasis_challengecode.truth_uuid
+ IS 'Link to the corresponding challenge which is solved';
+COMMENT ON COLUMN anastasis_challengecode.code
+ IS 'The pin code which is sent to the user and verified';
+COMMENT ON COLUMN anastasis_challengecode.creation_date
+ IS 'Creation date of the code';
+COMMENT ON COLUMN anastasis_challengecode.retransmission_date
+ IS 'When did we last transmit the challenge to the user';
+COMMENT ON COLUMN anastasis_challengecode.expiration_date
+ IS 'When will the code expire';
+COMMENT ON COLUMN anastasis_challengecode.retry_counter
+ IS 'How many tries are left for this code must be > 0';
+
+CREATE INDEX IF NOT EXISTS anastasis_challengecode_uuid_index
+ ON anastasis_challengecode
+ (truth_uuid,expiration_date);
+COMMENT ON INDEX anastasis_challengecode_uuid_index
+ IS 'for challenge lookup';
+
+CREATE INDEX IF NOT EXISTS anastasis_challengecode_expiration_index
+ ON anastasis_challengecode
+ (truth_uuid,expiration_date);
+COMMENT ON INDEX anastasis_challengecode_expiration_index
+ IS 'for challenge garbage collection';
+
+
+-- Complete transaction
+COMMIT;
diff --git a/src/stasis/stasis-postgres.conf b/src/stasis/stasis-postgres.conf
new file mode 100644
index 0000000..0d9b209
--- /dev/null
+++ b/src/stasis/stasis-postgres.conf
@@ -0,0 +1,6 @@
+[stasis-postgres]
+CONFIG = "postgres:///anastasis"
+
+# Where are the SQL files to setup our tables?
+# Important: this MUST end with a "/"!
+SQL_DIR = $DATADIR/sql/
diff --git a/src/stasis/test_anastasis_db.c b/src/stasis/test_anastasis_db.c
new file mode 100644
index 0000000..5e21530
--- /dev/null
+++ b/src/stasis/test_anastasis_db.c
@@ -0,0 +1,344 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 2021 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file anastasis/test_anastasis_db.c
+ * @brief testcase for anastasis postgres db plugin
+ * @author Marcello Stanisci
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "anastasis_database_lib.h"
+#include "anastasis_util_lib.h"
+#include <gnunet/gnunet_signatures.h>
+
+
+#define FAILIF(cond) \
+ do { \
+ if (! (cond)) { break;} \
+ GNUNET_break (0); \
+ goto drop; \
+ } while (0)
+
+#define RND_BLK(ptr) \
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, ptr, sizeof (*ptr))
+
+/**
+ * Global return value for the test. Initially -1, set to 0 upon
+ * completion. Other values indicate some kind of error.
+ */
+static int result;
+
+/**
+ * Handle to the plugin we are testing.
+ */
+static struct ANASTASIS_DatabasePlugin *plugin;
+
+
+/**
+ * Main function that will be run by the scheduler.
+ *
+ * @param cls closure with config
+ */
+static void
+run (void *cls)
+{
+ struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+ struct TALER_Amount amount;
+ struct ANASTASIS_PaymentSecretP paymentSecretP;
+ struct ANASTASIS_CRYPTO_AccountPublicKeyP accountPubP;
+ struct ANASTASIS_AccountSignatureP accountSig;
+ struct ANASTASIS_AccountSignatureP res_account_sig;
+ struct GNUNET_HashCode recoveryDataHash;
+ struct GNUNET_HashCode res_recovery_data_hash;
+ struct GNUNET_HashCode r;
+ struct GNUNET_TIME_Relative rel_time;
+ struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid;
+ struct ANASTASIS_CRYPTO_EncryptedKeyShareP key_share;
+ unsigned int post_counter;
+ char *mime_type;
+ char *method;
+ uint32_t docVersion;
+ uint32_t res_version;
+ size_t recoverydatasize;
+ void *res_recovery_data = NULL;
+ struct ANASTASIS_CRYPTO_EncryptedKeyShareP res_key_share;
+ bool paid;
+ bool valid_counter;
+ uint32_t recversion = 1;
+ unsigned char aes_gcm_tag[16];
+ const char *recovery_data = "RECOVERY_DATA";
+ uint64_t challenge_code = 1234;
+ struct GNUNET_HashCode c_hash;
+ struct ANASTASIS_UploadSignaturePS usp = {
+ .purpose.purpose = htonl (GNUNET_SIGNATURE_PURPOSE_TEST),
+ .purpose.size = htonl (sizeof (usp))
+ };
+
+ if (NULL == (plugin = ANASTASIS_DB_plugin_load (cfg)))
+ {
+ result = 77;
+ return;
+ }
+ if (GNUNET_OK != plugin->drop_tables (plugin->cls))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Dropping tables failed\n");
+ result = 77;
+ return;
+ }
+ ANASTASIS_DB_plugin_unload (plugin);
+ if (NULL == (plugin = ANASTASIS_DB_plugin_load (cfg)))
+ {
+ result = 77;
+ return;
+ }
+
+ GNUNET_CRYPTO_hash (recovery_data,
+ strlen (recovery_data),
+ &recoveryDataHash);
+ RND_BLK (&paymentSecretP);
+ RND_BLK (&aes_gcm_tag);
+ post_counter = 2;
+ mime_type = "Picture";
+ method = "Method";
+ TALER_string_to_amount ("EUR:30",&amount);
+
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ &truth_uuid,
+ sizeof (truth_uuid));
+ rel_time = GNUNET_TIME_UNIT_MONTHS;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("EUR:1",
+ &amount));
+
+ memset (&key_share, 1, sizeof (key_share));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->store_truth (plugin->cls,
+ &truth_uuid,
+ &key_share,
+ mime_type,
+ "encrypted_truth",
+ strlen ("encrypted_truth"),
+ method,
+ rel_time));
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->check_payment_identifier (plugin->cls,
+ &paymentSecretP,
+ &paid,
+ &valid_counter));
+
+ memset (&accountPubP, 2, sizeof (accountPubP));
+ memset (&accountSig, 3, sizeof (accountSig));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->record_recdoc_payment (plugin->cls,
+ &accountPubP,
+ post_counter,
+ &paymentSecretP,
+ &amount));
+ {
+ struct GNUNET_TIME_Absolute res_time;
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->increment_lifetime (plugin->cls,
+ &accountPubP,
+ &paymentSecretP,
+ rel_time,
+ &res_time));
+ }
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->check_payment_identifier (plugin->cls,
+ &paymentSecretP,
+ &paid,
+ &valid_counter));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->check_challenge_payment (plugin->cls,
+ &paymentSecretP,
+ &truth_uuid,
+ &paid));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->record_challenge_payment (plugin->cls,
+ &truth_uuid,
+ &paymentSecretP,
+ &amount));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->update_challenge_payment (plugin->cls,
+ &truth_uuid,
+ &paymentSecretP));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->check_challenge_payment (plugin->cls,
+ &paymentSecretP,
+ &truth_uuid,
+ &paid));
+ FAILIF (! paid);
+ FAILIF (ANASTASIS_DB_STORE_STATUS_SUCCESS !=
+ plugin->store_recovery_document (plugin->cls,
+ &accountPubP,
+ &accountSig,
+ &recoveryDataHash,
+ recovery_data,
+ strlen (recovery_data),
+ &paymentSecretP,
+ &docVersion));
+ {
+ uint32_t vrs;
+ struct GNUNET_TIME_Absolute exp;
+
+ FAILIF (ANASTASIS_DB_ACCOUNT_STATUS_VALID_HASH_RETURNED !=
+ plugin->lookup_account (plugin->cls,
+ &accountPubP,
+ &exp,
+ &r,
+ &vrs));
+ }
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->get_key_share (plugin->cls,
+ &truth_uuid,
+ &res_key_share));
+ FAILIF (0 !=
+ GNUNET_memcmp (&res_key_share,
+ &key_share));
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->get_recovery_document (plugin->cls,
+ &accountPubP,
+ recversion,
+ &res_account_sig,
+ &res_recovery_data_hash,
+ &recoverydatasize,
+ &res_recovery_data));
+ FAILIF (0 != memcmp (res_recovery_data,
+ recovery_data,
+ strlen (recovery_data)));
+ GNUNET_free (res_recovery_data);
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->get_latest_recovery_document (plugin->cls,
+ &accountPubP,
+ &res_account_sig,
+ &res_recovery_data_hash,
+ &recoverydatasize,
+ &res_recovery_data,
+ &res_version));
+ FAILIF (0 != memcmp (res_recovery_data,
+ recovery_data,
+ strlen (recovery_data)));
+ GNUNET_free (res_recovery_data);
+
+ {
+ struct GNUNET_TIME_Absolute rt;
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->create_challenge_code (plugin->cls,
+ &truth_uuid,
+ GNUNET_TIME_UNIT_HOURS,
+ GNUNET_TIME_UNIT_DAYS,
+ 3, /* retry counter */
+ &rt,
+ &challenge_code));
+ FAILIF (0 != rt.abs_value_us);
+ }
+ {
+ struct GNUNET_TIME_Absolute rt;
+ uint64_t c2;
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->create_challenge_code (plugin->cls,
+ &truth_uuid,
+ GNUNET_TIME_UNIT_HOURS,
+ GNUNET_TIME_UNIT_DAYS,
+ 3, /* retry counter */
+ &rt,
+ &c2));
+ FAILIF (c2 != challenge_code);
+ }
+ ANASTASIS_hash_answer (123,
+ &c_hash);
+ FAILIF (ANASTASIS_DB_CODE_STATUS_CHALLENGE_CODE_MISMATCH !=
+ plugin->verify_challenge_code (plugin->cls,
+ &truth_uuid,
+ &c_hash));
+
+ ANASTASIS_hash_answer (challenge_code,
+ &c_hash);
+ FAILIF (ANASTASIS_DB_CODE_STATUS_VALID_CODE_STORED !=
+ plugin->verify_challenge_code (plugin->cls,
+ &truth_uuid,
+ &c_hash));
+
+ if (-1 == result)
+ result = 0;
+
+drop:
+ GNUNET_break (GNUNET_OK ==
+ plugin->drop_tables (plugin->cls));
+ ANASTASIS_DB_plugin_unload (plugin);
+ if (NULL != plugin)
+ {
+ plugin = NULL;
+ }
+}
+
+
+int
+main (int argc,
+ char *const argv[])
+{
+ const char *plugin_name;
+ char *config_filename;
+ char *testname;
+ struct GNUNET_CONFIGURATION_Handle *cfg;
+
+ result = -1;
+ if (NULL == (plugin_name = strrchr (argv[0], (int) '-')))
+ {
+ GNUNET_break (0);
+ return -1;
+ }
+ /* FIRST get the libtalerutil initialization out
+ of the way. Then throw that one away, and force
+ the SYNC defaults to be used! */
+ (void) TALER_project_data_default ();
+ GNUNET_OS_init (ANASTASIS_project_data_default ());
+ GNUNET_log_setup (argv[0], "DEBUG", NULL);
+ plugin_name++;
+ GNUNET_asprintf (&testname,
+ "%s",
+ plugin_name);
+ GNUNET_asprintf (&config_filename,
+ "test_anastasis_db_%s.conf",
+ testname);
+ cfg = GNUNET_CONFIGURATION_create ();
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_load (cfg,
+ config_filename))
+ {
+ GNUNET_break (0);
+ GNUNET_free (config_filename);
+ GNUNET_free (testname);
+ return 2;
+ }
+ GNUNET_SCHEDULER_run (&run,
+ cfg);
+ GNUNET_CONFIGURATION_destroy (cfg);
+ GNUNET_free (config_filename);
+ GNUNET_free (testname);
+ return result;
+}
+
+
+/* end of test_anastasis_db.c */
diff --git a/src/stasis/test_anastasis_db_postgres.conf b/src/stasis/test_anastasis_db_postgres.conf
new file mode 100644
index 0000000..8971a62
--- /dev/null
+++ b/src/stasis/test_anastasis_db_postgres.conf
@@ -0,0 +1,10 @@
+[anastasis]
+#The DB plugin to use
+DB = postgres
+
+[taler]
+CURRENCY = EUR
+
+[stasis-postgres]
+#The connection string the plugin has to use for connecting to the database
+CONFIG = postgres:///anastasischeck
diff --git a/src/testing/.gitignore b/src/testing/.gitignore
new file mode 100644
index 0000000..a6eb294
--- /dev/null
+++ b/src/testing/.gitignore
@@ -0,0 +1,3 @@
+test_anastasis
+test_anastasisrest_api
+test_anastasis_api_home/.local/share/taler/crypto-*
diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am
new file mode 100644
index 0000000..8fc710b
--- /dev/null
+++ b/src/testing/Makefile.am
@@ -0,0 +1,91 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include -I$(top_srcdir)/src/backend -I$(top_srcdir)/src/lib
+
+if USE_COVERAGE
+ AM_CFLAGS = --coverage -O0
+ XLIB = -lgcov
+endif
+
+lib_LTLIBRARIES = \
+ libanastasistesting.la
+
+libanastasistesting_la_LDFLAGS = \
+ -version-info 0:0:0 \
+ -no-undefined
+libanastasistesting_la_SOURCES = \
+ testing_api_cmd_policy_store.c \
+ testing_api_cmd_truth_store.c \
+ testing_api_cmd_policy_lookup.c \
+ testing_api_cmd_keyshare_lookup.c \
+ testing_api_cmd_config.c \
+ testing_api_helpers.c \
+ testing_api_trait_account_pub.c \
+ testing_api_trait_account_priv.c \
+ testing_api_trait_eks.c \
+ testing_api_trait_payment_secret.c \
+ testing_api_trait_truth_key.c \
+ testing_api_trait_truth_uuid.c \
+ testing_api_trait_hash.c \
+ testing_api_trait_salt.c \
+ testing_api_trait_code.c \
+ testing_cmd_truth_upload.c \
+ testing_cmd_policy_create.c \
+ testing_cmd_secret_share.c \
+ testing_cmd_recover_secret.c \
+ testing_cmd_challenge_answer.c \
+ testing_trait_truth.c \
+ testing_trait_policy.c \
+ testing_trait_core_secret.c \
+ testing_trait_challenge.c
+libanastasistesting_la_LIBADD = \
+ $(top_builddir)/src/restclient/libanastasisrest.la \
+ $(top_builddir)/src/lib/libanastasis.la \
+ $(top_builddir)/src/util/libanastasisutil.la \
+ -ltalerexchange \
+ -ltalermerchant \
+ -ltalerjson \
+ -ltalerutil \
+ -lgnunetcurl \
+ -lgnunetjson \
+ -lgnunetutil \
+ -ljansson \
+ -luuid \
+ -ltalertesting \
+ $(XLIB)
+
+
+check_PROGRAMS = \
+ test_anastasisrest_api \
+ test_anastasis
+
+AM_TESTS_ENVIRONMENT=export ANASTASIS_PREFIX=$${ANASTASIS_PREFIX:-@libdir@};export PATH=$${ANASTASIS_PREFIX:-@prefix@}/bin:$$PATH;unset XDG_DATA_HOME;unset XDG_CONFIG_HOME;
+TESTS = \
+ $(check_PROGRAMS)
+
+test_anastasisrest_api_SOURCES = \
+ test_anastasis_api.c
+test_anastasisrest_api_LDADD = \
+ libanastasistesting.la \
+ -ltalermerchanttesting \
+ -ltalertesting \
+ -lgnunetutil \
+ $(XLIB)
+
+test_anastasis_SOURCES = \
+ test_anastasis.c
+test_anastasis_LDADD = \
+ libanastasistesting.la \
+ -ltalermerchanttesting \
+ -ltalertesting \
+ -ltalerexchange \
+ -lgnunetutil \
+ $(XLIB)
+
+EXTRA_DIST = \
+ test_anastasis_api.conf \
+ test_anastasis_api_home/.config/taler/exchange/account-2.json \
+ test_anastasis_api_home/.local/share/taler/exchange/offline-keys/master.priv \
+ sms_authentication.sh
+
+MOSTLYCLEANFILES = \
+ test_anastasis_api_home/.local/share/taler/exchange/offline-keys/secm_tofus.pub
diff --git a/src/testing/sms_authentication.sh b/src/testing/sms_authentication.sh
new file mode 100755
index 0000000..0c2c851
--- /dev/null
+++ b/src/testing/sms_authentication.sh
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+# Note: to use this script, you must set
+# ANASTASIS_SMS_API_KEY and
+# ANASTASIS_SMS_API_SECRET environment variables.
+curl -X "POST" "https://rest.nexmo.com/sms/json" \
+ -d "from=Vonage APIs" \
+ -d "text=$1" \
+ -d "to=$2" \
+ -d "api_key=$ANASTASIS_SMS_API_KEY" \
+ -d "api_secret=$ANASTASIS_SMS_API_SECRET"
diff --git a/src/testing/test_anastasis.c b/src/testing/test_anastasis.c
new file mode 100644
index 0000000..2ff7cb0
--- /dev/null
+++ b/src/testing/test_anastasis.c
@@ -0,0 +1,464 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 2021 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/test_anastasis.c
+ * @brief testcase to test anastasis
+ * @author Christian Grothoff
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ */
+#include "platform.h"
+#include "anastasis_testing_lib.h"
+#include <taler/taler_merchant_testing_lib.h>
+
+
+/**
+ * Configuration file we use. One (big) configuration is used
+ * for the various components for this test.
+ */
+#define CONFIG_FILE "test_anastasis_api.conf"
+
+/**
+ * Exchange base URL. Could also be taken from config.
+ */
+#define EXCHANGE_URL "http://localhost:8081/"
+
+/**
+ * Account number of the exchange at the bank.
+ */
+#define EXCHANGE_ACCOUNT_NAME "2"
+
+/**
+ * Account number of some user.
+ */
+#define USER_ACCOUNT_NAME "62"
+
+/**
+ * Account number used by the merchant
+ */
+#define MERCHANT_ACCOUNT_NAME "3"
+
+/**
+ * Configuration of the bank.
+ */
+static struct TALER_TESTING_BankConfiguration bc;
+
+/**
+ * Configuration of the exchange.
+ */
+static struct TALER_TESTING_ExchangeConfiguration ec;
+
+/**
+ * Payto URI of the customer (payer).
+ */
+static char *payer_payto;
+
+/**
+ * Payto URI of the exchange (escrow account).
+ */
+static char *exchange_payto;
+
+/**
+ * Payto URI of the merchant (receiver).
+ */
+static char *merchant_payto;
+
+/**
+ * Merchant base URL.
+ */
+static char *merchant_url;
+
+/**
+ * Anastasis base URL.
+ */
+static char *anastasis_url;
+
+/**
+ * Name of the file for exchanging the secret.
+ */
+static char *file_secret;
+
+/**
+ * Merchant process.
+ */
+static struct GNUNET_OS_Process *merchantd;
+
+/**
+ * Anastasis process.
+ */
+static struct GNUNET_OS_Process *anastasisd;
+
+/**
+ * Identity to use for testing.
+ */
+static json_t *id_data;
+
+
+/**
+ * Execute the taler-exchange-wirewatch command with
+ * our configuration file.
+ *
+ * @param label label to use for the command.
+ */
+static struct TALER_TESTING_Command
+cmd_exec_wirewatch (char *label)
+{
+ return TALER_TESTING_cmd_exec_wirewatch (label,
+ CONFIG_FILE);
+}
+
+
+/**
+ * Run wire transfer of funds from some user's account to the
+ * exchange.
+ *
+ * @param label label to use for the command.
+ * @param amount amount to transfer, i.e. "EUR:1"
+ * @param url exchange_url
+ */
+static struct TALER_TESTING_Command
+cmd_transfer_to_exchange (const char *label,
+ const char *amount)
+{
+ return TALER_TESTING_cmd_admin_add_incoming (label,
+ amount,
+ &bc.exchange_auth,
+ payer_payto);
+}
+
+
+/**
+ * Main function that will tell the interpreter what commands to
+ * run.
+ *
+ * @param cls closure
+ */
+static void
+run (void *cls,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct TALER_TESTING_Command pay[] = {
+ /**
+ * Move money to the exchange's bank account.
+ */
+ cmd_transfer_to_exchange ("create-reserve-1",
+ "EUR:10.02"),
+ /**
+ * Make a reserve exist, according to the previous
+ * transfer.
+ */
+ cmd_exec_wirewatch ("wirewatch-1"),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",
+ "create-reserve-1",
+ "EUR:5",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2",
+ "create-reserve-1",
+ "EUR:5",
+ MHD_HTTP_OK),
+ /**
+ * Check the reserve is depleted.
+ */
+ TALER_TESTING_cmd_status ("withdraw-status-1",
+ "create-reserve-1",
+ "EUR:0",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_end ()
+ };
+
+ struct TALER_TESTING_Command anastasis[] = {
+ ANASTASIS_TESTING_cmd_config ("salt-request-1",
+ anastasis_url,
+ MHD_HTTP_OK),
+ ANASTASIS_TESTING_cmd_truth_upload_question ("truth-create-1",
+ anastasis_url,
+ id_data,
+ "answer the question",
+ "text/plain",
+ "SomeTruth1",
+ MHD_HTTP_NO_CONTENT,
+ ANASTASIS_TESTING_TSO_NONE,
+ "salt-request-1"),
+ ANASTASIS_TESTING_cmd_truth_upload_question ("truth-create-2",
+ anastasis_url,
+ id_data,
+ "answer the question",
+ "text/plain",
+ "SomeTruth2",
+ MHD_HTTP_NO_CONTENT,
+ ANASTASIS_TESTING_TSO_NONE,
+ "salt-request-1"),
+ ANASTASIS_TESTING_cmd_truth_upload ("truth-create-3",
+ anastasis_url,
+ id_data,
+ "file",
+ "read the file",
+ "text/plain",
+ file_secret,
+ strlen (file_secret),
+ MHD_HTTP_NO_CONTENT,
+ ANASTASIS_TESTING_TSO_NONE,
+ "salt-request-1"),
+ ANASTASIS_TESTING_cmd_policy_create ("policy-create-1",
+ "truth-create-1",
+ "truth-create-2",
+ NULL),
+ ANASTASIS_TESTING_cmd_policy_create ("policy-create-2",
+ "truth-create-1",
+ "truth-create-3",
+ NULL),
+ ANASTASIS_TESTING_cmd_policy_create ("policy-create-3",
+ "truth-create-2",
+ "truth-create-3",
+ NULL),
+ ANASTASIS_TESTING_cmd_secret_share ("secret-share-1",
+ anastasis_url,
+ "salt-request-1",
+ NULL,
+ id_data,
+ "core secret",
+ strlen ("core secret"),
+ ANASTASIS_SHARE_STATUS_PAYMENT_REQUIRED,
+ ANASTASIS_TESTING_SSO_NONE,
+ "policy-create-1",
+ "policy-create-2",
+ "policy-create-3",
+ NULL),
+ /* what would we have to pay? */
+ TALER_TESTING_cmd_merchant_claim_order ("fetch-proposal",
+ merchant_url,
+ MHD_HTTP_OK,
+ "secret-share-1",
+ NULL),
+ /* make the payment */
+ TALER_TESTING_cmd_merchant_pay_order ("pay-account",
+ merchant_url,
+ MHD_HTTP_OK,
+ "fetch-proposal",
+ "withdraw-coin-1",
+ "EUR:5",
+ "EUR:4.99", /* must match ANNUAL_FEE in config! */
+ NULL),
+ ANASTASIS_TESTING_cmd_secret_share ("secret-share-2",
+ anastasis_url,
+ "salt-request-1",
+ "secret-share-1",
+ id_data,
+ "core secret",
+ strlen ("core secret"),
+ ANASTASIS_SHARE_STATUS_SUCCESS,
+ ANASTASIS_TESTING_SSO_NONE,
+ "policy-create-1",
+ "policy-create-2",
+ "policy-create-3",
+ NULL),
+ ANASTASIS_TESTING_cmd_recover_secret ("recover-secret-1",
+ anastasis_url,
+ id_data,
+ 0, /* version */
+ ANASTASIS_TESTING_RSO_NONE,
+ "salt-request-1",
+ "secret-share-2"),
+ ANASTASIS_TESTING_cmd_challenge_answer ("challenge-answer-1",
+ NULL, /* payment ref */
+ "recover-secret-1", /* challenge ref */
+ 0, /* challenge index */
+ "SomeTruth1",
+ 0, /* mode */
+ ANASTASIS_CHALLENGE_STATUS_SOLVED),
+#if 0
+ ANASTASIS_TESTING_cmd_challenge_answer ("challenge-answer-2",
+ NULL, /* payment ref */
+ "recover-secret-1",
+ 1, /* challenge index */
+ "SomeTruth2",
+ 0, /* mode */
+ ANASTASIS_CHALLENGE_STATUS_SOLVED),
+#endif
+ ANASTASIS_TESTING_cmd_challenge_start ("challenge-start-3-pay",
+ NULL, /* payment ref */
+ "recover-secret-1",
+ 2, /* challenge index */
+ ANASTASIS_CHALLENGE_STATUS_PAYMENT_REQUIRED),
+ TALER_TESTING_cmd_merchant_claim_order ("fetch-challenge-pay-proposal",
+ merchant_url,
+ MHD_HTTP_OK,
+ "challenge-start-3-pay",
+ NULL),
+ TALER_TESTING_cmd_merchant_pay_order ("pay-file-challenge",
+ merchant_url,
+ MHD_HTTP_OK,
+ "fetch-challenge-pay-proposal",
+ "withdraw-coin-2",
+ "EUR:1",
+ "EUR:1", /* must match COST in config! */
+ NULL),
+ ANASTASIS_TESTING_cmd_challenge_start ("challenge-start-3-paid",
+ "challenge-start-3-pay", /* payment ref */
+ "recover-secret-1",
+ 2, /* challenge index */
+ ANASTASIS_CHALLENGE_STATUS_INSTRUCTIONS),
+ ANASTASIS_TESTING_cmd_challenge_answer ("challenge-answer-3",
+ "challenge-start-3-pay", /* payment ref */
+ "recover-secret-1",
+ 2, /* challenge index */
+ "challenge-start-3-paid", /* answer */
+ 1, /* mode */
+ ANASTASIS_CHALLENGE_STATUS_SOLVED),
+ ANASTASIS_TESTING_cmd_recover_secret_finish ("recover-finish-1",
+ "recover-secret-1",
+ GNUNET_TIME_UNIT_SECONDS),
+ TALER_TESTING_cmd_end ()
+ };
+
+ struct TALER_TESTING_Command commands[] = {
+ /* general setup */
+ TALER_TESTING_cmd_auditor_add ("add-auditor-OK",
+ MHD_HTTP_NO_CONTENT,
+ false),
+ TALER_TESTING_cmd_wire_add ("add-wire-account",
+ "payto://x-taler-bank/localhost/2",
+ MHD_HTTP_NO_CONTENT,
+ false),
+ TALER_TESTING_cmd_exec_offline_sign_keys ("offline-sign-future-keys",
+ CONFIG_FILE),
+ TALER_TESTING_cmd_exec_offline_sign_fees ("offline-sign-fees",
+ CONFIG_FILE,
+ "EUR:0.01",
+ "EUR:0.01"),
+ TALER_TESTING_cmd_check_keys_pull_all_keys ("refetch /keys",
+ 1),
+ TALER_TESTING_cmd_merchant_post_instances ("instance-create-default",
+ merchant_url,
+ "default",
+ merchant_payto,
+ "EUR",
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_batch ("pay",
+ pay),
+ TALER_TESTING_cmd_batch ("anastasis",
+ anastasis),
+ TALER_TESTING_cmd_end ()
+ };
+
+ TALER_TESTING_run_with_fakebank (is,
+ commands,
+ bc.exchange_auth.wire_gateway_url);
+}
+
+
+int
+main (int argc,
+ char *const *argv)
+{
+ unsigned int ret;
+ /* These environment variables get in the way... */
+ unsetenv ("XDG_DATA_HOME");
+ unsetenv ("XDG_CONFIG_HOME");
+
+ GNUNET_log_setup ("test-anastasis",
+ "DEBUG",
+ NULL);
+ if (GNUNET_OK !=
+ TALER_TESTING_prepare_fakebank (CONFIG_FILE,
+ "exchange-account-exchange",
+ &bc))
+ return 77;
+ {
+ char dir[] = "/tmp/test-anastasis-file-XXXXXX";
+
+ if (NULL == mkdtemp (dir))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "mkdtemp",
+ dir);
+ return 77;
+ }
+ GNUNET_asprintf (&file_secret,
+ "%s/.secret",
+ dir);
+ }
+ id_data = ANASTASIS_TESTING_make_id_data_example (
+ "MaxMuster123456789");
+ payer_payto = ("payto://x-taler-bank/localhost/" USER_ACCOUNT_NAME);
+ exchange_payto = ("payto://x-taler-bank/localhost/" EXCHANGE_ACCOUNT_NAME);
+ merchant_payto = ("payto://x-taler-bank/localhost/" MERCHANT_ACCOUNT_NAME);
+ if (NULL ==
+ (merchant_url = TALER_TESTING_prepare_merchant (CONFIG_FILE)))
+ return 77;
+ TALER_TESTING_cleanup_files (CONFIG_FILE);
+
+ if (NULL ==
+ (anastasis_url = ANASTASIS_TESTING_prepare_anastasis (CONFIG_FILE)))
+ return 77;
+ TALER_TESTING_cleanup_files (CONFIG_FILE);
+
+ switch (TALER_TESTING_prepare_exchange (CONFIG_FILE,
+ GNUNET_YES,
+ &ec))
+ {
+ case GNUNET_SYSERR:
+ GNUNET_break (0);
+ return 1;
+ case GNUNET_NO:
+ return 77;
+ case GNUNET_OK:
+ if (NULL == (merchantd =
+ TALER_TESTING_run_merchant (CONFIG_FILE,
+ merchant_url)))
+ {
+ GNUNET_break (0);
+ return 1;
+ }
+ if (NULL == (anastasisd =
+ ANASTASIS_TESTING_run_anastasis (CONFIG_FILE,
+ anastasis_url)))
+ {
+ GNUNET_break (0);
+ GNUNET_OS_process_kill (merchantd,
+ SIGTERM);
+ GNUNET_OS_process_wait (merchantd);
+ GNUNET_OS_process_destroy (merchantd);
+
+ return 1;
+ }
+ ret = TALER_TESTING_setup_with_exchange (&run,
+ NULL,
+ CONFIG_FILE);
+
+ GNUNET_OS_process_kill (merchantd,
+ SIGTERM);
+ GNUNET_OS_process_kill (anastasisd,
+ SIGTERM);
+ GNUNET_OS_process_wait (merchantd);
+ GNUNET_OS_process_wait (anastasisd);
+ GNUNET_OS_process_destroy (merchantd);
+ GNUNET_OS_process_destroy (anastasisd);
+ GNUNET_free (merchant_url);
+ GNUNET_free (anastasis_url);
+
+ if (GNUNET_OK != ret)
+ return 1;
+ break;
+ default:
+ GNUNET_break (0);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* end of test_anastasis.c */
diff --git a/src/testing/test_anastasis_api.c b/src/testing/test_anastasis_api.c
new file mode 100644
index 0000000..db18b41
--- /dev/null
+++ b/src/testing/test_anastasis_api.c
@@ -0,0 +1,421 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 2021 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/test_anastasis_api.c
+ * @brief testcase to test anastasis' HTTP API interface
+ * @author Christian Grothoff
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ */
+#include "platform.h"
+#include "anastasis_testing_lib.h"
+#include <taler/taler_merchant_testing_lib.h>
+
+
+/**
+ * Configuration file we use. One (big) configuration is used
+ * for the various components for this test.
+ */
+#define CONFIG_FILE "test_anastasis_api.conf"
+
+/**
+ * Exchange base URL. Could also be taken from config.
+ */
+#define EXCHANGE_URL "http://localhost:8081/"
+
+/**
+ * Account number of the exchange at the bank.
+ */
+#define EXCHANGE_ACCOUNT_NAME "2"
+
+/**
+ * Account number of some user.
+ */
+#define USER_ACCOUNT_NAME "62"
+
+/**
+ * Account number used by the merchant
+ */
+#define MERCHANT_ACCOUNT_NAME "3"
+
+/**
+ * Configuration of the bank.
+ */
+static struct TALER_TESTING_BankConfiguration bc;
+
+/**
+ * Configuration of the exchange.
+ */
+static struct TALER_TESTING_ExchangeConfiguration ec;
+
+/**
+ * Payto URI of the customer (payer).
+ */
+static char *payer_payto;
+
+/**
+ * Payto URI of the exchange (escrow account).
+ */
+static char *exchange_payto;
+
+/**
+ * Payto URI of the merchant (receiver).
+ */
+static char *merchant_payto;
+
+/**
+ * Merchant base URL.
+ */
+static char *merchant_url;
+
+/**
+ * Anastasis base URL.
+ */
+static char *anastasis_url;
+
+/**
+ * Merchant process.
+ */
+static struct GNUNET_OS_Process *merchantd;
+
+/**
+ * Anastasis process.
+ */
+static struct GNUNET_OS_Process *anastasisd;
+
+/**
+ * Name of the file for exchanging the secret.
+ */
+static char *file_secret;
+
+/**
+ * Execute the taler-exchange-wirewatch command with our configuration
+ * file.
+ *
+ * @param label label to use for the command.
+ */
+static struct TALER_TESTING_Command
+cmd_exec_wirewatch (char *label)
+{
+ return TALER_TESTING_cmd_exec_wirewatch (label,
+ CONFIG_FILE);
+}
+
+
+/**
+ * Run wire transfer of funds from some user's account to the
+ * exchange.
+ *
+ * @param label label to use for the command.
+ * @param amount amount to transfer, i.e. "EUR:1"
+ * @param url exchange_url
+ */
+static struct TALER_TESTING_Command
+cmd_transfer_to_exchange (const char *label,
+ const char *amount)
+{
+ return TALER_TESTING_cmd_admin_add_incoming (label,
+ amount,
+ &bc.exchange_auth,
+ payer_payto);
+}
+
+
+/**
+ * Main function that will tell the interpreter what commands to run.
+ *
+ * @param cls closure
+ */
+static void
+run (void *cls,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct TALER_TESTING_Command withdraw[] = {
+ cmd_transfer_to_exchange ("create-reserve-1",
+ "EUR:10.02"),
+ cmd_exec_wirewatch ("wirewatch-1"),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",
+ "create-reserve-1",
+ "EUR:5",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2",
+ "create-reserve-1",
+ "EUR:5",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_status ("withdraw-status-1",
+ "create-reserve-1",
+ "EUR:0",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_end ()
+ };
+
+ struct TALER_TESTING_Command policy[] = {
+ ANASTASIS_TESTING_cmd_policy_store ("policy-store-1",
+ anastasis_url,
+ NULL /* prev upload */,
+ MHD_HTTP_PAYMENT_REQUIRED,
+ ANASTASIS_TESTING_PSO_NONE,
+ "Test-1",
+ strlen ("Test-1")),
+ /* what would we have to pay? */
+ TALER_TESTING_cmd_merchant_claim_order ("fetch-proposal",
+ merchant_url,
+ MHD_HTTP_OK,
+ "policy-store-1",
+ NULL),
+ /* make the payment */
+ TALER_TESTING_cmd_merchant_pay_order ("pay-account",
+ merchant_url,
+ MHD_HTTP_OK,
+ "fetch-proposal",
+ "withdraw-coin-1",
+ "EUR:5",
+ "EUR:4.99", /* must match ANNUAL_FEE in config! */
+ NULL),
+ ANASTASIS_TESTING_cmd_policy_store ("policy-store-2",
+ anastasis_url,
+ "policy-store-1",
+ MHD_HTTP_NO_CONTENT,
+ ANASTASIS_TESTING_PSO_NONE,
+ "Test-1",
+ strlen ("Test-1")),
+ ANASTASIS_TESTING_cmd_policy_lookup ("policy-lookup-1",
+ anastasis_url,
+ MHD_HTTP_OK,
+ "policy-store-2"),
+ TALER_TESTING_cmd_end ()
+ };
+
+ struct TALER_TESTING_Command truth[] = {
+ ANASTASIS_TESTING_cmd_truth_question (
+ "truth-store-1",
+ anastasis_url,
+ NULL,
+ "The-Answer",
+ ANASTASIS_TESTING_TSO_NONE,
+ MHD_HTTP_NO_CONTENT),
+ ANASTASIS_TESTING_cmd_keyshare_lookup (
+ "keyshare-lookup-1",
+ anastasis_url,
+ "The-Answer",
+ NULL, /* payment ref */
+ "truth-store-1",
+ 0,
+ ANASTASIS_KSD_SUCCESS),
+ ANASTASIS_TESTING_cmd_truth_store (
+ "truth-store-2",
+ anastasis_url,
+ NULL,
+ "file",
+ "text/plain",
+ strlen (file_secret),
+ file_secret,
+ ANASTASIS_TESTING_TSO_NONE,
+ MHD_HTTP_NO_CONTENT),
+ ANASTASIS_TESTING_cmd_keyshare_lookup (
+ "challenge-fail-1",
+ anastasis_url,
+ "Wrong-Answer",
+ NULL,
+ "truth-store-1",
+ 0,
+ ANASTASIS_KSD_INVALID_ANSWER),
+ ANASTASIS_TESTING_cmd_keyshare_lookup (
+ "file-challenge-run-1",
+ anastasis_url,
+ NULL, /* no answer */
+ NULL, /* payment ref */
+ "truth-store-2", /* upload ref */
+ 0,
+ ANASTASIS_KSD_PAYMENT_REQUIRED),
+ /* what would we have to pay? */
+ TALER_TESTING_cmd_merchant_claim_order ("fetch-proposal-2",
+ merchant_url,
+ MHD_HTTP_OK,
+ "file-challenge-run-1",
+ NULL),
+ /* make the payment */
+ TALER_TESTING_cmd_merchant_pay_order ("pay-account-2",
+ merchant_url,
+ MHD_HTTP_OK,
+ "fetch-proposal-2",
+ "withdraw-coin-2",
+ "EUR:1.01",
+ "EUR:1",
+ NULL),
+
+ ANASTASIS_TESTING_cmd_keyshare_lookup (
+ "file-challenge-run-2",
+ anastasis_url,
+ NULL, /* no answer */
+ "file-challenge-run-1", /* payment ref */
+ "truth-store-2",
+ 0,
+ ANASTASIS_KSD_INVALID_ANSWER),
+ ANASTASIS_TESTING_cmd_keyshare_lookup (
+ "file-challenge-run-3",
+ anastasis_url,
+ "file-challenge-run-2", /* answer */
+ "file-challenge-run-1", /* payment ref */
+ "truth-store-2",
+ 1,
+ ANASTASIS_KSD_SUCCESS),
+ TALER_TESTING_cmd_end ()
+ };
+
+ struct TALER_TESTING_Command commands[] = {
+ /* general setup */
+ TALER_TESTING_cmd_auditor_add ("add-auditor-OK",
+ MHD_HTTP_NO_CONTENT,
+ false),
+ TALER_TESTING_cmd_wire_add ("add-wire-account",
+ "payto://x-taler-bank/localhost/2",
+ MHD_HTTP_NO_CONTENT,
+ false),
+ TALER_TESTING_cmd_exec_offline_sign_keys ("offline-sign-future-keys",
+ CONFIG_FILE),
+ TALER_TESTING_cmd_exec_offline_sign_fees ("offline-sign-fees",
+ CONFIG_FILE,
+ "EUR:0.01",
+ "EUR:0.01"),
+ TALER_TESTING_cmd_check_keys_pull_all_keys ("refetch /keys",
+ 1),
+ TALER_TESTING_cmd_merchant_post_instances ("instance-create-default",
+ merchant_url,
+ "default",
+ merchant_payto,
+ "EUR",
+ MHD_HTTP_NO_CONTENT),
+ ANASTASIS_TESTING_cmd_config ("salt-request-1",
+ anastasis_url,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_batch ("withdraw",
+ withdraw),
+ TALER_TESTING_cmd_batch ("policy",
+ policy),
+ TALER_TESTING_cmd_batch ("truth",
+ truth),
+ TALER_TESTING_cmd_end ()
+ };
+
+ TALER_TESTING_run_with_fakebank (is,
+ commands,
+ bc.exchange_auth.wire_gateway_url);
+}
+
+
+int
+main (int argc,
+ char *const *argv)
+{
+ int ret;
+
+ /* These environment variables get in the way... */
+ unsetenv ("XDG_DATA_HOME");
+ unsetenv ("XDG_CONFIG_HOME");
+ GNUNET_log_setup ("test-anastasis-api",
+ "DEBUG",
+ NULL);
+ if (GNUNET_OK !=
+ TALER_TESTING_prepare_fakebank (CONFIG_FILE,
+ "exchange-account-exchange",
+ &bc))
+ return 77;
+ {
+ char dir[] = "/tmp/test-anastasis-file-XXXXXX";
+
+ if (NULL == mkdtemp (dir))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "mkdtemp",
+ dir);
+ return 77;
+ }
+ GNUNET_asprintf (&file_secret,
+ "%s/.secret",
+ dir);
+ }
+ payer_payto = ("payto://x-taler-bank/localhost/" USER_ACCOUNT_NAME);
+ exchange_payto = ("payto://x-taler-bank/localhost/" EXCHANGE_ACCOUNT_NAME);
+ merchant_payto = ("payto://x-taler-bank/localhost/" MERCHANT_ACCOUNT_NAME);
+ if (NULL ==
+ (merchant_url = TALER_TESTING_prepare_merchant (CONFIG_FILE)))
+ return 77;
+ TALER_TESTING_cleanup_files (CONFIG_FILE);
+
+ if (NULL ==
+ (anastasis_url = ANASTASIS_TESTING_prepare_anastasis (CONFIG_FILE)))
+ return 77;
+ TALER_TESTING_cleanup_files (CONFIG_FILE);
+
+ switch (TALER_TESTING_prepare_exchange (CONFIG_FILE,
+ GNUNET_YES,
+ &ec))
+ {
+ case GNUNET_SYSERR:
+ GNUNET_break (0);
+ return 1;
+ case GNUNET_NO:
+ return 77;
+ case GNUNET_OK:
+ if (NULL == (merchantd =
+ TALER_TESTING_run_merchant (CONFIG_FILE,
+ merchant_url)))
+ {
+ GNUNET_break (0);
+ return 1;
+ }
+ if (NULL == (anastasisd =
+ ANASTASIS_TESTING_run_anastasis (CONFIG_FILE,
+ anastasis_url)))
+ {
+ GNUNET_break (0);
+ GNUNET_OS_process_kill (merchantd,
+ SIGTERM);
+ GNUNET_OS_process_wait (merchantd);
+ GNUNET_OS_process_destroy (merchantd);
+ return 1;
+ }
+ ret = TALER_TESTING_setup_with_exchange (&run,
+ NULL,
+ CONFIG_FILE);
+ GNUNET_OS_process_kill (merchantd,
+ SIGTERM);
+ GNUNET_OS_process_kill (anastasisd,
+ SIGTERM);
+ GNUNET_OS_process_wait (merchantd);
+ GNUNET_OS_process_wait (anastasisd);
+ GNUNET_OS_process_destroy (merchantd);
+ GNUNET_OS_process_destroy (anastasisd);
+ GNUNET_free (merchant_url);
+ GNUNET_free (anastasis_url);
+
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Test failed in interpreter\n");
+ return 1;
+ }
+ break;
+ default:
+ GNUNET_break (0);
+ return 1;
+ }
+ return 0;
+}
+
+
+/* end of test_anastasis_api.c */
diff --git a/src/testing/test_anastasis_api.conf b/src/testing/test_anastasis_api.conf
new file mode 100644
index 0000000..bde7ee1
--- /dev/null
+++ b/src/testing/test_anastasis_api.conf
@@ -0,0 +1,264 @@
+# This file is in the public domain.
+#
+[PATHS]
+# Persistent data storage for the testcase
+TALER_TEST_HOME = test_anastasis_api_home/
+TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/${USER:-}/taler-system-runtime/
+
+# Persistent data storage
+TALER_DATA_HOME = $TALER_TEST_HOME/.local/share/taler/
+
+# Configuration files
+TALER_CONFIG_HOME = $TALER_TEST_HOME/.config/taler/
+
+# Cached data, no big deal if lost
+TALER_CACHE_HOME = $TALER_TEST_HOME/.cache/taler/
+
+[taler]
+# What currency do we use?
+#currency = EUR
+currency = EUR
+#CURRENCY_ROUND_UNIT = EUR:0.01
+#CURRENCY_ROUND_UNIT = EUR:0.01
+
+[taler-helper-crypto-rsa]
+# Reduce from 1 year to speed up test
+LOOKAHEAD_SIGN = 12 days
+
+[taler-helper-crypto-eddsa]
+# Reduce from 1 year to speed up test
+LOOKAHEAD_SIGN = 12 days
+# Reduce from 12 weeks to ensure we have multiple
+DURATION = 7 days
+
+
+[bank]
+HTTP_PORT = 8082
+#BASE_URL = https://bank.test.taler.net/
+
+##########################################
+# Configuration for Anastasis #
+##########################################
+
+[anastasis]
+PORT = 8086
+
+DB = postgres
+
+BUSINESS_NAME = "Checker's Test Inc."
+
+# Upload limit
+UPLOAD_LIMIT_MB = 1
+
+ANNUAL_POLICY_UPLOAD_LIMIT = 64
+
+INSURANCE = EUR:0
+
+SERVER_SALT = salty
+
+# Base URL of anastasis.
+# BASE_URL = http://localhost:8086/
+
+# Where does our payment backend run? Must match PORT under [merchant]
+PAYMENT_BACKEND_URL = http://localhost:8080/
+
+# Annual fee we charge.
+#ANNUAL_FEE = EUR:4.99
+ANNUAL_FEE = EUR:4.99
+
+TRUTH_UPLOAD_FEE = EUR:0.0
+
+# Authentication costs
+[authorization-question]
+# Cost of authentication by question
+COST = EUR:0
+
+[authorization-file]
+# Cost of authentication by file (only for testing purposes)
+COST = EUR:1
+
+[authorization-email]
+# Cost of authentication by E-Mail
+COST = EUR:0
+
+[authorization-sms]
+# Cost of authentication by SMS
+COST = EUR:0
+
+# Command which is executed for the sms authentication
+COMMAND = ./sms_authentication.sh
+
+
+
+
+# This specifies which database the postgres backend uses.
+[stasis-postgres]
+CONFIG = postgres:///anastasischeck
+
+##########################################
+# Configuration for the merchant backend #
+##########################################
+
+[merchant]
+
+# Which port do we run the backend on? (HTTP server)
+PORT = 8080
+
+# How quickly do we want the exchange to send us our money?
+# Used only if the frontend does not specify a value.
+WIRE_TRANSFER_DELAY = 0 s
+
+# Which plugin (backend) do we use for the DB.
+DB = postgres
+
+# Default choice for maximum wire fee.
+DEFAULT_MAX_WIRE_FEE = EUR:0.10
+
+# Default choice for maximum deposit fee.
+DEFAULT_MAX_DEPOSIT_FEE = EUR:0.10
+
+
+# This specifies which database the postgres backend uses.
+[merchantdb-postgres]
+CONFIG = postgres:///talercheck
+
+# Sections starting with "exchange-" specify trusted exchanges
+# (by the merchant)
+[merchant-exchange-default]
+MASTER_KEY = T1VVFQZZARQ1CMF4BN58EE7SKTW5AV2BS18S87ZEGYS4S29J6DNG
+EXCHANGE_BASE_URL = http://localhost:8081/
+#MASTER_KEY = DY95EXAHQ2BKM2WK9YHZHYG1R7PPMMJPY14FNGP662DAKE35AKQG
+#EXCHANGE_BASE_URL = https://exchange.test.taler.net/
+#CURRENCY = EUR
+CURRENCY = EUR
+
+# only fixes skips.
+[auditor]
+BASE_URL = http://the.auditor/
+#BASE_URL = https://auditor.test.taler.net/
+#AUDITOR_KEY = DSDASDXAMDAARMNAD53ZA4AFAHA2QADAMAHHASWDAWXN84SDAA11
+# If currency does not match [TALER] section, the auditor
+# will be ignored!
+CURRENCY = EUR
+
+# Where do we store the auditor's private key?
+AUDITOR_PRIV_FILE = ${TALER_DATA_HOME}/auditor/offline-keys/auditor.priv
+
+# Auditors must be in sections "auditor-", the rest of the section
+# name could be anything.
+[auditor-ezb]
+# Informal name of the auditor. Just for the user.
+NAME = European Central Bank
+
+# URL of the auditor (especially for in the future, when the
+# auditor offers an automated issue reporting system).
+# Not really used today.
+URL = http://taler.ezb.eu/
+
+# This is the important bit: the signing key of the auditor.
+PUBLIC_KEY = 9QXF7XY7E9VPV47B5Z806NDFSX2VJ79SVHHD29QEQ3BG31ANHZ60
+
+# Which currency is this auditor trusted for?
+CURRENCY = EUR
+
+
+###################################################
+# Configuration for the exchange for the testcase #
+###################################################
+
+[exchange]
+# How to access our database
+DB = postgres
+
+# HTTP port the exchange listens to
+PORT = 8081
+
+# how long are the signatures with the signkey valid?
+SIGNKEY_LEGAL_DURATION = 2 years
+
+# Our public key
+MASTER_PUBLIC_KEY = T1VVFQZZARQ1CMF4BN58EE7SKTW5AV2BS18S87ZEGYS4S29J6DNG
+
+# Base URL of the exchange.
+BASE_URL = "http://localhost:8081/"
+#BASE_URL = https://exchange.test.taler.net/
+
+KEYDIR = ${TALER_DATA_HOME}/exchange/live-keys/
+
+REVOCATION_DIR = ${TALER_DATA_HOME}/exchange/revocations/
+
+
+# Network configuration for the normal API/service HTTP server
+# serve via tcp socket (on PORT)
+SERVE = tcp
+
+[exchange-offline]
+
+# Where do we store the offline master private key of the exchange?
+MASTER_PRIV_FILE = ${TALER_DATA_HOME}/exchange/offline-keys/master.priv
+
+# Where do we store the TOFU key material?
+SECM_TOFU_FILE = ${TALER_DATA_HOME}/exchange/offline-keys/secm_tofus.pub
+
+
+[exchangedb-postgres]
+CONFIG = "postgres:///talercheck"
+
+[auditordb-postgres]
+CONFIG = "postgres:///talercheck"
+
+# Account of the EXCHANGE
+[exchange-account-exchange]
+# What is the exchange's bank account (with the "Taler Bank" demo system)?
+PAYTO_URI = "payto://x-taler-bank/localhost:8082/2"
+
+WIRE_GATEWAY_URL = "http://localhost:8082/2/"
+WIRE_GATEWAY_AUTH_METHOD = NONE
+
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
+
+
+[coin_eur_ct_1]
+value = EUR:0.01
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.00
+fee_deposit = EUR:0.00
+fee_refresh = EUR:0.01
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+
+[coin_eur_ct_10]
+value = EUR:0.10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+
+[coin_eur_1]
+value = EUR:1
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+
+[coin_eur_5]
+value = EUR:5
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
diff --git a/src/testing/test_anastasis_api_home/.config/taler/exchange/account-2.json b/src/testing/test_anastasis_api_home/.config/taler/exchange/account-2.json
new file mode 100644
index 0000000..f798275
--- /dev/null
+++ b/src/testing/test_anastasis_api_home/.config/taler/exchange/account-2.json
@@ -0,0 +1,3 @@
+{
+ "payto_uri": "payto://x-taler-bank/localhost:8082/2",
+ "master_sig": "AM32QB4RYMWK548PE63PJXJMWSA001TFFWTZZPSSD8HQ8JE4D5V5X8WTSYSX59ANF4YRTRMF5Q4Q12CE2KTA8KQ03CM11YDTK75SJ20"}
diff --git a/src/testing/test_anastasis_api_home/.config/taler/merchant/account-3.json b/src/testing/test_anastasis_api_home/.config/taler/merchant/account-3.json
new file mode 100644
index 0000000..016e133
--- /dev/null
+++ b/src/testing/test_anastasis_api_home/.config/taler/merchant/account-3.json
@@ -0,0 +1 @@
+{"salt":"ZSHKZ4DRFMFRFBMB10BX8M1GD16AZNG639AY8QMW26XBGECQRS1H7H8CQ4WETHT66PG99CYBVGFRKJ7ZENEDJJ4E0X0JHRY1QKPQ3W0","payto_uri":"payto://x-taler-bank/localhost:8082/3"}
diff --git a/src/testing/test_anastasis_api_home/.config/taler/merchant/default.priv b/src/testing/test_anastasis_api_home/.config/taler/merchant/default.priv
new file mode 100644
index 0000000..3ed8c04
--- /dev/null
+++ b/src/testing/test_anastasis_api_home/.config/taler/merchant/default.priv
@@ -0,0 +1 @@
+<!ÔÇ»³¶l¶Ú&…|ÇÆËSb¿¹0)hó8ö \ No newline at end of file
diff --git a/src/testing/test_anastasis_api_home/.config/taler/merchant/dtip.priv b/src/testing/test_anastasis_api_home/.config/taler/merchant/dtip.priv
new file mode 100644
index 0000000..a72136d
--- /dev/null
+++ b/src/testing/test_anastasis_api_home/.config/taler/merchant/dtip.priv
@@ -0,0 +1 @@
+æÍëÕð*¾šiWªLØ(ß:ZOtG…s41æ \ No newline at end of file
diff --git a/src/testing/test_anastasis_api_home/.config/taler/merchant/nulltip.priv b/src/testing/test_anastasis_api_home/.config/taler/merchant/nulltip.priv
new file mode 100644
index 0000000..fe56b97
--- /dev/null
+++ b/src/testing/test_anastasis_api_home/.config/taler/merchant/nulltip.priv
@@ -0,0 +1 @@
+ç§l9h‡[èYÌÊ›‘ô"h1Àø—–žsÿÊB;¤Ð \ No newline at end of file
diff --git a/src/testing/test_anastasis_api_home/.config/taler/merchant/reserve/dtip.priv b/src/testing/test_anastasis_api_home/.config/taler/merchant/reserve/dtip.priv
new file mode 100644
index 0000000..a5f982f
--- /dev/null
+++ b/src/testing/test_anastasis_api_home/.config/taler/merchant/reserve/dtip.priv
@@ -0,0 +1 @@
+ò^Sz™êƒ5?éÌ<e?Ì[ÔY•ãs¨Âœ¿ûC$ \ No newline at end of file
diff --git a/src/testing/test_anastasis_api_home/.config/taler/merchant/reserve/nulltip.priv b/src/testing/test_anastasis_api_home/.config/taler/merchant/reserve/nulltip.priv
new file mode 100644
index 0000000..8b9f09c
--- /dev/null
+++ b/src/testing/test_anastasis_api_home/.config/taler/merchant/reserve/nulltip.priv
@@ -0,0 +1 @@
+I4Z‹‹JxžuRFSþEJßãÙÚÍf7?0W \ No newline at end of file
diff --git a/src/testing/test_anastasis_api_home/.config/taler/merchant/reserve/tip.priv b/src/testing/test_anastasis_api_home/.config/taler/merchant/reserve/tip.priv
new file mode 100644
index 0000000..16fecd6
--- /dev/null
+++ b/src/testing/test_anastasis_api_home/.config/taler/merchant/reserve/tip.priv
@@ -0,0 +1 @@
+òœÓËÌ­Œò?SwfJ¯")Dù²¶whšiôxK \ No newline at end of file
diff --git a/src/testing/test_anastasis_api_home/.config/taler/merchant/tip.priv b/src/testing/test_anastasis_api_home/.config/taler/merchant/tip.priv
new file mode 100644
index 0000000..951d893
--- /dev/null
+++ b/src/testing/test_anastasis_api_home/.config/taler/merchant/tip.priv
Binary files differ
diff --git a/src/testing/test_anastasis_api_home/.config/taler/merchant/tor.priv b/src/testing/test_anastasis_api_home/.config/taler/merchant/tor.priv
new file mode 100644
index 0000000..007b02c
--- /dev/null
+++ b/src/testing/test_anastasis_api_home/.config/taler/merchant/tor.priv
Binary files differ
diff --git a/src/testing/test_anastasis_api_home/.config/taler/test.json b/src/testing/test_anastasis_api_home/.config/taler/test.json
new file mode 100644
index 0000000..74cdc92
--- /dev/null
+++ b/src/testing/test_anastasis_api_home/.config/taler/test.json
@@ -0,0 +1,8 @@
+{
+ "name": "The exchange",
+ "account_number": 3,
+ "bank_url": "http://localhost:8083/",
+ "salt": "6259MV4W9V8D2A75RSGGPKYHQRXRPQZ33EBG263JZRJ6SA5HK0RRKHV70TNA1RVRG77M57CCFVSK2B0EJN3SR8S21F0ZX2MR9DNVG50",
+ "type": "test",
+ "sig": "8C3D3J816S29AA2AJ7P9TS6W13KFNFS2RCVYJEWRBNHRRMTTRAWKY7WA1N3G54E4K3XAC2HN6JDHS42TWR5315J34JHHCKV618K221G"
+}
diff --git a/src/testing/test_anastasis_api_home/.local/share/taler/exchange/offline-keys/master.priv b/src/testing/test_anastasis_api_home/.local/share/taler/exchange/offline-keys/master.priv
new file mode 100644
index 0000000..c20942d
--- /dev/null
+++ b/src/testing/test_anastasis_api_home/.local/share/taler/exchange/offline-keys/master.priv
@@ -0,0 +1 @@
+åÊk;d³_Uû}£A.wÔ"!Gûçv_m "_ò \ No newline at end of file
diff --git a/src/testing/test_anastasis_api_home/.local/share/taler/merchant/merchant.priv b/src/testing/test_anastasis_api_home/.local/share/taler/merchant/merchant.priv
new file mode 100644
index 0000000..fd6e5f7
--- /dev/null
+++ b/src/testing/test_anastasis_api_home/.local/share/taler/merchant/merchant.priv
@@ -0,0 +1 @@
+¶ù,åY%–FF<ßþR˜‰9ϳ5„¬v\þš×k4«6 \ No newline at end of file
diff --git a/src/testing/testing_api_cmd_config.c b/src/testing/testing_api_cmd_config.c
new file mode 100644
index 0000000..8906261
--- /dev/null
+++ b/src/testing/testing_api_cmd_config.c
@@ -0,0 +1,206 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2019, 2021 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/testing_api_cmd_config.c
+ * @brief command to obtain the configuration of an anastasis backend service.
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ */
+
+#include "platform.h"
+#include "anastasis_testing_lib.h"
+#include <taler/taler_util.h>
+#include <taler/taler_testing_lib.h>
+
+
+/**
+ * State for a "config" CMD.
+ */
+struct ConfigState
+{
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * URL of the anastasis backend.
+ */
+ const char *anastasis_url;
+
+ /**
+ * Expected status code.
+ */
+ unsigned int http_status;
+
+ /**
+ * The /config GET operation handle.
+ */
+ struct ANASTASIS_ConfigOperation *so;
+
+ /**
+ * The salt value from server.
+ */
+ struct ANASTASIS_CRYPTO_ProviderSaltP salt;
+};
+
+
+/**
+ * Function called with the results of a #ANASTASIS_config().
+ *
+ * @param cls closure
+ * @param http_status HTTP status of the request
+ * @param config config from the server
+ */
+static void
+config_cb (void *cls,
+ unsigned int http_status,
+ const struct ANASTASIS_Config *config)
+{
+ struct ConfigState *ss = cls;
+
+ ss->so = NULL;
+ if (http_status != ss->http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u to command %s in %s:%u\n",
+ http_status,
+ ss->is->commands[ss->is->ip].label,
+ __FILE__,
+ __LINE__);
+ TALER_TESTING_interpreter_fail (ss->is);
+ return;
+ }
+ if (NULL == config)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Config is NULL, command %s in %s:%u\n",
+ ss->is->commands[ss->is->ip].label,
+ __FILE__,
+ __LINE__);
+ TALER_TESTING_interpreter_fail (ss->is);
+ return;
+ }
+ ss->salt = config->salt;
+ TALER_TESTING_interpreter_next (ss->is);
+}
+
+
+/**
+ * Run a "config" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command currently being run.
+ * @param is interpreter state.
+ */
+static void
+config_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct ConfigState *ss = cls;
+
+ ss->is = is;
+ ss->so = ANASTASIS_get_config (is->ctx,
+ ss->anastasis_url,
+ &config_cb,
+ ss);
+ if (NULL == ss->so)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ss->is);
+ return;
+ }
+}
+
+
+/**
+ * Free the state of a "config" CMD, and possibly
+ * cancel it if it did not complete.
+ *
+ * @param cls closure.
+ * @param cmd command being freed.
+ */
+static void
+config_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct ConfigState *ss = cls;
+
+ if (NULL != ss->so)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Command '%s' did not complete (config)\n",
+ cmd->label);
+ ANASTASIS_config_cancel (ss->so);
+ ss->so = NULL;
+ }
+ GNUNET_free (ss);
+}
+
+
+/**
+ * Offer internal data to other commands.
+ *
+ * @param cls closure
+ * @param ret[out] result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to extract.
+ * @return #GNUNET_OK on success
+ */
+static int
+config_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct ConfigState *ss = cls;
+
+ struct TALER_TESTING_Trait traits[] = {
+ ANASTASIS_TESTING_make_trait_salt (0,
+ &ss->salt),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+struct TALER_TESTING_Command
+ANASTASIS_TESTING_cmd_config (const char *label,
+ const char *anastasis_url,
+ unsigned int http_status)
+{
+ struct ConfigState *ss;
+
+ ss = GNUNET_new (struct ConfigState);
+ ss->http_status = http_status;
+ ss->anastasis_url = anastasis_url;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ss,
+ .label = label,
+ .run = &config_run,
+ .cleanup = &config_cleanup,
+ .traits = &config_traits
+ };
+
+ return cmd;
+ }
+}
diff --git a/src/testing/testing_api_cmd_keyshare_lookup.c b/src/testing/testing_api_cmd_keyshare_lookup.c
new file mode 100644
index 0000000..895d321
--- /dev/null
+++ b/src/testing/testing_api_cmd_keyshare_lookup.c
@@ -0,0 +1,465 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/anastasis_api_keyshare_lookup.c
+ * @brief Testing of Implementation of the /truth GET
+ * @author Christian Grothoff
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ */
+
+#include "platform.h"
+#include "anastasis_testing_lib.h"
+#include <taler/taler_util.h>
+#include <taler/taler_testing_lib.h>
+#include <taler/taler_merchant_service.h>
+
+
+/**
+ * State for a "keyshare lookup" CMD.
+ */
+struct KeyShareLookupState
+{
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * URL of the anastasis backend.
+ */
+ const char *anastasis_url;
+
+ /**
+ * Expected status code.
+ */
+ enum ANASTASIS_KeyShareDownloadStatus expected_ksdd;
+
+ /**
+ * The /truth GET operation handle.
+ */
+ struct ANASTASIS_KeyShareLookupOperation *kslo;
+
+ /**
+ * answer to a challenge
+ */
+ const char *answer;
+
+ /**
+ * Reference to upload command we expect to lookup.
+ */
+ const char *upload_reference;
+
+ /**
+ * Reference to upload command we expect to lookup.
+ */
+ const char *payment_reference;
+
+ /**
+ * Payment secret requested by the service, if any.
+ */
+ struct ANASTASIS_PaymentSecretP payment_secret_response;
+
+ /**
+ * Taler-URI with payment request, if any.
+ */
+ char *pay_uri;
+
+ /**
+ * Order ID for payment request, if any.
+ */
+ char *order_id;
+
+ /**
+ * Redirect-URI for challenge, if any.
+ */
+ char *redirect_uri;
+
+ /**
+ * "code" returned by service, if any.
+ */
+ char *code;
+
+ /**
+ * "instructions" for how to solve the challenge as returned by service, if any.
+ */
+ char *instructions;
+
+ /**
+ * Name of the file where the service will write the challenge, if method is "file".
+ * Otherwise NULL.
+ */
+ char *filename;
+
+ /**
+ * Mode for the lookup(0 = question, 1 = code based)
+ */
+ int lookup_mode;
+
+};
+
+
+static void
+keyshare_lookup_cb (void *cls,
+ const struct ANASTASIS_KeyShareDownloadDetails *dd)
+{
+ struct KeyShareLookupState *ksls = cls;
+
+ ksls->kslo = NULL;
+ if (dd->status != ksls->expected_ksdd)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u to command %s in %s:%u\n",
+ dd->status,
+ ksls->is->commands[ksls->is->ip].label,
+ __FILE__,
+ __LINE__);
+ TALER_TESTING_interpreter_fail (ksls->is);
+ return;
+ }
+ switch (dd->status)
+ {
+ case ANASTASIS_KSD_SUCCESS:
+ break;
+ case ANASTASIS_KSD_PAYMENT_REQUIRED:
+ ksls->pay_uri = GNUNET_strdup (dd->details.payment_required.taler_pay_uri);
+ ksls->payment_secret_response = dd->details.payment_required.payment_secret;
+ {
+ struct TALER_MERCHANT_PayUriData pd;
+
+ if (GNUNET_OK !=
+ TALER_MERCHANT_parse_pay_uri (ksls->pay_uri,
+ &pd))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ksls->is);
+ return;
+ }
+ ksls->order_id = GNUNET_strdup (pd.order_id);
+ TALER_MERCHANT_parse_pay_uri_free (&pd);
+ }
+
+ break;
+ case ANASTASIS_KSD_INVALID_ANSWER:
+ if (ksls->filename)
+ {
+ FILE *file;
+ char code[22];
+
+ file = fopen (ksls->filename,
+ "r");
+ if (NULL == file)
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "open",
+ ksls->filename);
+ TALER_TESTING_interpreter_fail (ksls->is);
+ return;
+ }
+ if (0 == fscanf (file,
+ "%21s",
+ code))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "fscanf",
+ ksls->filename);
+ GNUNET_break (0 == fclose (file));
+ TALER_TESTING_interpreter_fail (ksls->is);
+ return;
+ }
+ GNUNET_break (0 == fclose (file));
+ ksls->code = GNUNET_strdup (code);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Read code `%s'\n",
+ code);
+ }
+ else
+ {
+ ksls->instructions = GNUNET_strndup (
+ dd->details.open_challenge.body,
+ dd->details.open_challenge.body_size);
+ }
+ break;
+ case ANASTASIS_KSD_REDIRECT_FOR_AUTHENTICATION:
+ ksls->redirect_uri = GNUNET_strdup (dd->details.redirect_url);
+ break;
+ case ANASTASIS_KSD_SERVER_ERROR:
+ break;
+ case ANASTASIS_KSD_CLIENT_FAILURE:
+ break;
+ case ANASTASIS_KSD_TRUTH_UNKNOWN:
+ break;
+ case ANASTASIS_KSD_RATE_LIMIT_EXCEEDED:
+ break;
+ }
+ TALER_TESTING_interpreter_next (ksls->is);
+}
+
+
+static void
+keyshare_lookup_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct KeyShareLookupState *ksls = cls;
+ const struct ANASTASIS_CRYPTO_TruthKeyP *truth_key;
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid;
+ const struct ANASTASIS_PaymentSecretP *payment_secret;
+ const char *answer;
+
+ ksls->is = is;
+ if (NULL == ksls->upload_reference)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ksls->is);
+ return;
+ }
+ {
+ const struct TALER_TESTING_Command *upload_cmd;
+
+ upload_cmd = TALER_TESTING_interpreter_lookup_command (
+ is,
+ ksls->upload_reference);
+ if (NULL == upload_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ksls->is);
+ return;
+ }
+ {
+ const char *fn;
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_string (upload_cmd,
+ 0,
+ &fn))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ksls->is);
+ return;
+ }
+ if (NULL != fn)
+ ksls->filename = GNUNET_strdup (fn);
+ }
+ if (GNUNET_OK !=
+ ANASTASIS_TESTING_get_trait_truth_uuid (upload_cmd,
+ 0,
+ &truth_uuid))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ksls->is);
+ return;
+ }
+ if (NULL == truth_uuid)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ksls->is);
+ return;
+ }
+ if (GNUNET_OK !=
+ ANASTASIS_TESTING_get_trait_truth_key (upload_cmd,
+ 0,
+ &truth_key))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ksls->is);
+ return;
+ }
+ if (NULL == truth_key)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ksls->is);
+ return;
+ }
+ }
+
+ if (ksls->lookup_mode == 1)
+ {
+ const struct TALER_TESTING_Command *download_cmd;
+
+ download_cmd = TALER_TESTING_interpreter_lookup_command (is,
+ ksls->answer);
+ if (NULL == download_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ksls->is);
+ return;
+ }
+ if (GNUNET_OK !=
+ ANASTASIS_TESTING_get_trait_code (download_cmd,
+ 0,
+ &answer))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ksls->is);
+ return;
+ }
+ if (NULL == answer)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ksls->is);
+ return;
+ }
+ }
+ else
+ {
+ /* answer is the answer */
+ answer = ksls->answer;
+ }
+
+ if (NULL != ksls->payment_reference)
+ {
+ const struct TALER_TESTING_Command *payment_cmd;
+
+ payment_cmd = TALER_TESTING_interpreter_lookup_command
+ (is,
+ ksls->payment_reference);
+ if (GNUNET_OK !=
+ ANASTASIS_TESTING_get_trait_payment_secret (payment_cmd,
+ 0,
+ &payment_secret))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ksls->is);
+ return;
+ }
+ }
+ else
+ {
+ payment_secret = NULL;
+ }
+
+ {
+ struct GNUNET_HashCode h_answer;
+
+ if (NULL != answer)
+ GNUNET_CRYPTO_hash (answer,
+ strlen (answer),
+ &h_answer);
+ ksls->kslo = ANASTASIS_keyshare_lookup (is->ctx,
+ ksls->anastasis_url,
+ truth_uuid,
+ truth_key,
+ payment_secret,
+ GNUNET_TIME_UNIT_ZERO,
+ (NULL != answer)
+ ? &h_answer
+ : NULL,
+ &keyshare_lookup_cb,
+ ksls);
+ }
+ if (NULL == ksls->kslo)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ksls->is);
+ return;
+ }
+}
+
+
+static void
+keyshare_lookup_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct KeyShareLookupState *ksls = cls;
+
+ if (NULL != ksls->kslo)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Command '%s' did not complete (keyshare lookup)\n",
+ cmd->label);
+ ANASTASIS_keyshare_lookup_cancel (ksls->kslo);
+ ksls->kslo = NULL;
+ }
+ GNUNET_free (ksls->pay_uri);
+ GNUNET_free (ksls->order_id);
+ GNUNET_free (ksls->code);
+ GNUNET_free (ksls->instructions);
+ GNUNET_free (ksls->redirect_uri);
+ GNUNET_free (ksls);
+}
+
+
+/**
+ * Offer internal data to other commands.
+ *
+ * @param cls closure
+ * @param[out] ret result (could be anything)
+ * @param[out] trait name of the trait
+ * @param index index number of the object to extract.
+ * @return #GNUNET_OK on success
+ */
+static int
+keyshare_lookup_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct KeyShareLookupState *ksls = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ ANASTASIS_TESTING_make_trait_payment_secret (0,
+ &ksls->payment_secret_response),
+ TALER_TESTING_make_trait_url (TALER_TESTING_UT_TALER_URL,
+ ksls->pay_uri),
+ TALER_TESTING_make_trait_order_id (0,
+ ksls->order_id),
+ ANASTASIS_TESTING_make_trait_code (0,
+ ksls->code),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+struct TALER_TESTING_Command
+ANASTASIS_TESTING_cmd_keyshare_lookup (
+ const char *label,
+ const char *anastasis_url,
+ const char *answer,
+ const char *payment_ref,
+ const char *upload_ref,
+ int lookup_mode,
+ enum ANASTASIS_KeyShareDownloadStatus ksdd)
+{
+ struct KeyShareLookupState *ksls;
+
+ GNUNET_assert (NULL != upload_ref);
+ ksls = GNUNET_new (struct KeyShareLookupState);
+ ksls->expected_ksdd = ksdd;
+ ksls->anastasis_url = anastasis_url;
+ ksls->upload_reference = upload_ref;
+ ksls->payment_reference = payment_ref;
+ ksls->answer = answer;
+ ksls->lookup_mode = lookup_mode;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ksls,
+ .label = label,
+ .run = &keyshare_lookup_run,
+ .cleanup = &keyshare_lookup_cleanup,
+ .traits = &keyshare_lookup_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_keyshare_lookup.c */
diff --git a/src/testing/testing_api_cmd_policy_lookup.c b/src/testing/testing_api_cmd_policy_lookup.c
new file mode 100644
index 0000000..e97f746
--- /dev/null
+++ b/src/testing/testing_api_cmd_policy_lookup.c
@@ -0,0 +1,267 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/testing_api_cmd_policy_lookup.c
+ * @brief command to execute the anastasis backend service.
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ */
+
+#include "platform.h"
+#include "anastasis_testing_lib.h"
+#include <taler/taler_util.h>
+#include <taler/taler_testing_lib.h>
+
+
+/**
+ * State for a "policy lookup" CMD.
+ */
+struct PolicyLookupState
+{
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Eddsa Publickey.
+ */
+ struct ANASTASIS_CRYPTO_AccountPublicKeyP anastasis_pub;
+
+ /**
+ * Hash of the upload (all zeros if there was no upload).
+ */
+ const struct GNUNET_HashCode *upload_hash;
+
+ /**
+ * URL of the anastasis backend.
+ */
+ const char *anastasis_url;
+
+ /**
+ * Expected status code.
+ */
+ unsigned int http_status;
+
+ /**
+ * Reference to upload command we expect to lookup.
+ */
+ const char *upload_reference;
+
+ /**
+ * The /policy GET operation handle.
+ */
+ struct ANASTASIS_PolicyLookupOperation *plo;
+};
+
+
+/**
+ * Function called with the results of a #ANASTASIS_lookup().
+ *
+ * @param cls closure
+ * @param http_status HTTP status of the request
+ * @param ud details about the lookup operation
+ */
+static void
+policy_lookup_cb (void *cls,
+ unsigned int http_status,
+ const struct ANASTASIS_DownloadDetails *dd)
+{
+ struct PolicyLookupState *pls = cls;
+
+ pls->plo = NULL;
+ if (http_status != pls->http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u to command %s in %s:%u\n",
+ http_status,
+ pls->is->commands[pls->is->ip].label,
+ __FILE__,
+ __LINE__);
+ TALER_TESTING_interpreter_fail (pls->is);
+ return;
+ }
+ if (NULL != pls->upload_reference)
+ {
+ if ( (MHD_HTTP_OK == http_status) &&
+ (0 != GNUNET_memcmp (&dd->curr_policy_hash,
+ pls->upload_hash)) )
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (pls->is);
+ return;
+ }
+ }
+ TALER_TESTING_interpreter_next (pls->is);
+}
+
+
+/**
+ * Run a "policy lookup" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command currently being run.
+ * @param is interpreter state.
+ */
+static void
+policy_lookup_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct PolicyLookupState *pls = cls;
+
+ pls->is = is;
+ if (NULL != pls->upload_reference)
+ {
+ const struct TALER_TESTING_Command *upload_cmd;
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *anastasis_pub;
+
+ upload_cmd = TALER_TESTING_interpreter_lookup_command
+ (is,
+ pls->upload_reference);
+ if (NULL == upload_cmd)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (pls->is);
+ return;
+ }
+ if (GNUNET_OK !=
+ ANASTASIS_TESTING_get_trait_hash (upload_cmd,
+ ANASTASIS_TESTING_TRAIT_HASH_CURRENT,
+ &pls->upload_hash))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (pls->is);
+ return;
+ }
+ if (GNUNET_OK !=
+ ANASTASIS_TESTING_get_trait_account_pub (upload_cmd,
+ 0,
+ &anastasis_pub))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (pls->is);
+ return;
+ }
+ pls->anastasis_pub = *anastasis_pub;
+ }
+ pls->plo = ANASTASIS_policy_lookup (is->ctx,
+ pls->anastasis_url,
+ &pls->anastasis_pub,
+ &policy_lookup_cb,
+ pls);
+ if (NULL == pls->plo)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (pls->is);
+ return;
+ }
+}
+
+
+/**
+ * Free the state of a "policy lookup" CMD, and possibly
+ * cancel it if it did not complete.
+ *
+ * @param cls closure.
+ * @param cmd command being freed.
+ */
+static void
+policy_lookup_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct PolicyLookupState *pls = cls;
+
+ if (NULL != pls->plo)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Command '%s' did not complete (policy lookup)\n",
+ cmd->label);
+ ANASTASIS_policy_lookup_cancel (pls->plo);
+ pls->plo = NULL;
+ }
+ GNUNET_free (pls);
+}
+
+
+/**
+ * Make the "policy lookup" command.
+ *
+ * @param label command label
+ * @param anastasis_url base URL of the ANASTASIS serving
+ * the policy store request.
+ * @param http_status expected HTTP status.
+ * @param upload_ref reference to upload command
+ * @return the command
+ */
+struct TALER_TESTING_Command
+ANASTASIS_TESTING_cmd_policy_lookup (const char *label,
+ const char *anastasis_url,
+ unsigned int http_status,
+ const char *upload_ref)
+{
+ struct PolicyLookupState *pls;
+
+ GNUNET_assert (NULL != upload_ref);
+ pls = GNUNET_new (struct PolicyLookupState);
+ pls->http_status = http_status;
+ pls->anastasis_url = anastasis_url;
+ pls->upload_reference = upload_ref;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = pls,
+ .label = label,
+ .run = &policy_lookup_run,
+ .cleanup = &policy_lookup_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/**
+ * Make the "policy lookup" command for a non-existent upload.
+ *
+ * @param label command label
+ * @param anastasis_url base URL of the ANASTASIS serving
+ * the policy lookup request.
+ * @return the command
+ */
+struct TALER_TESTING_Command
+ANASTASIS_TESTING_cmd_policy_nx (const char *label,
+ const char *anastasis_url)
+{
+ struct PolicyLookupState *pls;
+ struct GNUNET_CRYPTO_EddsaPrivateKey priv;
+
+ pls = GNUNET_new (struct PolicyLookupState);
+ pls->http_status = MHD_HTTP_NOT_FOUND;
+ pls->anastasis_url = anastasis_url;
+ GNUNET_CRYPTO_eddsa_key_create (&priv);
+ GNUNET_CRYPTO_eddsa_key_get_public (&priv,
+ &pls->anastasis_pub.pub);
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = pls,
+ .label = label,
+ .run = &policy_lookup_run,
+ .cleanup = &policy_lookup_cleanup
+ };
+
+ return cmd;
+ }
+}
diff --git a/src/testing/testing_api_cmd_policy_store.c b/src/testing/testing_api_cmd_policy_store.c
new file mode 100644
index 0000000..a8f0a70
--- /dev/null
+++ b/src/testing/testing_api_cmd_policy_store.c
@@ -0,0 +1,397 @@
+/*
+ This file is part of ANASTASIS
+ Copyright (C) 2014-2019 Taler Systems SA
+
+ ANASTASIS 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.
+
+ ANASTASIS 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 ANASTASIS; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file lib/testing_api_cmd_policy_store.c
+ * @brief command to execute the anastasis backend service.
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ */
+
+#include "platform.h"
+#include "anastasis_testing_lib.h"
+#include <taler/taler_util.h>
+#include <taler/taler_testing_lib.h>
+#include <taler/taler_merchant_service.h>
+
+
+/**
+ * State for a "policy store" CMD.
+ */
+struct PolicyStoreState
+{
+ /**
+ * Claim token we got back, if any. Otherwise all zeros.
+ */
+ struct TALER_ClaimTokenP claim_token;
+
+ /**
+ * The policy data.
+ */
+ const void *recovery_data;
+
+ /**
+ * Number of bytes in @e recovery_data
+ */
+ size_t recovery_data_size;
+
+ /**
+ * Expected status code.
+ */
+ unsigned int http_status;
+
+ /**
+ * Eddsa Publickey.
+ */
+ struct ANASTASIS_CRYPTO_AccountPublicKeyP anastasis_pub;
+
+ /**
+ * Eddsa Privatekey.
+ */
+ struct ANASTASIS_CRYPTO_AccountPrivateKeyP anastasis_priv;
+
+ /**
+ * Hash of uploaded data, used to verify the response.
+ */
+ struct GNUNET_HashCode curr_hash;
+
+ /**
+ * The /policy POST operation handle.
+ */
+ struct ANASTASIS_PolicyStoreOperation *pso;
+
+ /**
+ * The nonce.
+ */
+ struct ANASTASIS_CRYPTO_NonceP nonce;
+
+ /**
+ * URL of the anastasis backend.
+ */
+ const char *anastasis_url;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Previous upload, or NULL for none. Used to calculate what THIS
+ * upload is based on.
+ */
+ const char *prev_upload;
+
+ /**
+ * Payment order ID we are to provide in the request, or zero.
+ */
+ struct ANASTASIS_PaymentSecretP payment_secret_request;
+
+ /**
+ * The order ID, for making the payment.
+ */
+ char *order_id;
+
+ /**
+ * Payment order ID we are to provide in the response, or zero.
+ */
+ struct ANASTASIS_PaymentSecretP payment_secret_response;
+
+ /**
+ * Options for how we are supposed to do the upload.
+ */
+ enum ANASTASIS_TESTING_PolicyStoreOption psopt;
+
+ /**
+ * True if @e payment_secret_request is initialized.
+ */
+ bool payment_secret_set;
+};
+
+/**
+ * Function called with the results of a #policy_store().
+ *
+ * @param cls closure
+ * @param http_status HTTP status of the request
+ * @param ud details about the upload operation
+ */
+static void
+policy_store_cb (void *cls,
+ const struct ANASTASIS_UploadDetails *ud)
+{
+ struct PolicyStoreState *pss = cls;
+
+ pss->pso = NULL;
+ if (ud->http_status != pss->http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u to command %s in %s:%u\n",
+ ud->http_status,
+ pss->is->commands[pss->is->ip].label,
+ __FILE__,
+ __LINE__);
+ TALER_TESTING_interpreter_fail (pss->is);
+ return;
+ }
+ switch (ud->us)
+ {
+ case ANASTASIS_US_SUCCESS:
+ if (0 != GNUNET_memcmp (&pss->curr_hash,
+ ud->details.success.curr_backup_hash))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (pss->is);
+ return;
+ }
+ break;
+ case ANASTASIS_US_PAYMENT_REQUIRED:
+ pss->payment_secret_response = ud->details.payment.ps;
+ {
+ struct TALER_MERCHANT_PayUriData pd;
+
+ if (GNUNET_OK !=
+ TALER_MERCHANT_parse_pay_uri (ud->details.payment.payment_request,
+ &pd))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (pss->is);
+ return;
+ }
+ pss->order_id = GNUNET_strdup (pd.order_id);
+ if (NULL != pd.claim_token)
+ pss->claim_token = *pd.claim_token;
+ TALER_MERCHANT_parse_pay_uri_free (&pd);
+ }
+ break;
+ case ANASTASIS_US_HTTP_ERROR:
+ break;
+ case ANASTASIS_US_CLIENT_ERROR:
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (pss->is);
+ return;
+ case ANASTASIS_US_SERVER_ERROR:
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (pss->is);
+ return;
+ default:
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (pss->is);
+ return;
+ }
+ TALER_TESTING_interpreter_next (pss->is);
+}
+
+
+/**
+ * Run a "policy store" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command currently being run.
+ * @param is interpreter state.
+ */
+static void
+policy_store_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct PolicyStoreState *pss = cls;
+
+ pss->is = is;
+ if (NULL != pss->prev_upload)
+ {
+ const struct TALER_TESTING_Command *ref;
+
+ ref = TALER_TESTING_interpreter_lookup_command (is,
+ pss->prev_upload);
+ if (NULL == ref)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (pss->is);
+ return;
+ }
+ {
+ const struct ANASTASIS_CRYPTO_AccountPrivateKeyP *priv;
+
+ if (GNUNET_OK !=
+ ANASTASIS_TESTING_get_trait_account_priv (ref,
+ 0,
+ &priv))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (pss->is);
+ return;
+ }
+ pss->anastasis_priv = *priv;
+ }
+ {
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *pub;
+
+ if (GNUNET_OK !=
+ ANASTASIS_TESTING_get_trait_account_pub (ref,
+ 0,
+ &pub))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (pss->is);
+ return;
+ }
+ pss->anastasis_pub = *pub;
+ }
+ {
+ const struct ANASTASIS_PaymentSecretP *ps;
+
+ if (GNUNET_OK !=
+ ANASTASIS_TESTING_get_trait_payment_secret (ref,
+ 0,
+ &ps))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (pss->is);
+ return;
+ }
+ pss->payment_secret_request = *ps;
+ pss->payment_secret_set = true;
+ }
+ }
+ else
+ {
+ GNUNET_CRYPTO_eddsa_key_create (&pss->anastasis_priv.priv);
+ GNUNET_CRYPTO_eddsa_key_get_public (&pss->anastasis_priv.priv,
+ &pss->anastasis_pub.pub);
+ }
+
+ GNUNET_CRYPTO_hash (pss->recovery_data,
+ pss->recovery_data_size,
+ &pss->curr_hash);
+ pss->pso = ANASTASIS_policy_store (
+ is->ctx,
+ pss->anastasis_url,
+ &pss->anastasis_priv,
+ pss->recovery_data,
+ pss->recovery_data_size,
+ (0 != (ANASTASIS_TESTING_PSO_REQUEST_PAYMENT & pss->psopt)),
+ pss->payment_secret_set ? &pss->payment_secret_request : NULL,
+ GNUNET_TIME_UNIT_ZERO,
+ &policy_store_cb,
+ pss);
+ if (NULL == pss->pso)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (pss->is);
+ return;
+ }
+}
+
+
+/**
+ * Free the state of a "policy store" CMD, and possibly
+ * cancel it if it did not complete.
+ *
+ * @param cls closure.
+ * @param cmd command being freed.
+ */
+static void
+policy_store_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct PolicyStoreState *pss = cls;
+
+ if (NULL != pss->pso)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Command '%s' did not complete (policy post)\n",
+ cmd->label);
+ ANASTASIS_policy_store_cancel (pss->pso);
+ pss->pso = NULL;
+ }
+ GNUNET_free (pss->order_id);
+ GNUNET_free (pss);
+}
+
+
+/**
+ * Offer internal data to other commands.
+ *
+ * @param cls closure
+ * @param[out] ret result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to extract.
+ * @return #GNUNET_OK on success
+ */
+static int
+policy_store_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct PolicyStoreState *pss = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_claim_token (0,
+ &pss->claim_token),
+ TALER_TESTING_make_trait_order_id (0,
+ pss->order_id),
+ ANASTASIS_TESTING_make_trait_hash (0,
+ &pss->curr_hash),
+ ANASTASIS_TESTING_make_trait_account_pub (0,
+ &pss->anastasis_pub),
+ ANASTASIS_TESTING_make_trait_account_priv (0,
+ &pss->anastasis_priv),
+ ANASTASIS_TESTING_make_trait_payment_secret (0,
+ &pss->payment_secret_response),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+struct TALER_TESTING_Command
+ANASTASIS_TESTING_cmd_policy_store (
+ const char *label,
+ const char *anastasis_url,
+ const char *prev_upload,
+ unsigned int http_status,
+ enum ANASTASIS_TESTING_PolicyStoreOption pso,
+ const void *recovery_data,
+ size_t recovery_data_size)
+{
+ struct PolicyStoreState *pss;
+
+ pss = GNUNET_new (struct PolicyStoreState);
+ pss->recovery_data = recovery_data;
+ pss->recovery_data_size = recovery_data_size;
+ pss->http_status = http_status;
+ pss->psopt = pso;
+ pss->anastasis_url = anastasis_url;
+ pss->prev_upload = prev_upload;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = pss,
+ .label = label,
+ .run = &policy_store_run,
+ .cleanup = &policy_store_cleanup,
+ .traits = &policy_store_traits
+ };
+
+ return cmd;
+ }
+}
diff --git a/src/testing/testing_api_cmd_truth_store.c b/src/testing/testing_api_cmd_truth_store.c
new file mode 100644
index 0000000..0883406
--- /dev/null
+++ b/src/testing/testing_api_cmd_truth_store.c
@@ -0,0 +1,436 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/testing_api_cmd_truth_store.c
+ * @brief command to execute the anastasis backend service.
+ * @author Dennis Neufeld
+ */
+#include "platform.h"
+#include "anastasis_testing_lib.h"
+#include <taler/taler_util.h>
+#include <taler/taler_testing_lib.h>
+#include <taler/taler_merchant_service.h>
+
+/**
+ * State for a "truth store" CMD.
+ */
+struct TruthStoreState
+{
+ /**
+ * UUID of the uploaded truth
+ */
+ struct ANASTASIS_CRYPTO_TruthUUIDP uuid;
+
+ /**
+ * Key used to encrypt the @e truth_data on the server.
+ */
+ struct ANASTASIS_CRYPTO_TruthKeyP key;
+
+ /**
+ * "Encrypted" key share data we store at the server.
+ */
+ struct ANASTASIS_CRYPTO_EncryptedKeyShareP encrypted_keyshare;
+
+ /**
+ * The /truth POST operation handle.
+ */
+ struct ANASTASIS_TruthStoreOperation *tso;
+
+ /**
+ * URL of the anastasis backend.
+ */
+ const char *anastasis_url;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Previous upload, or NULL for none. Used to calculate what THIS
+ * upload is based on.
+ */
+ const char *prev_upload;
+
+ /**
+ * Authorization method / plugin name.
+ */
+ const char *method;
+
+ /**
+ * Mimetype of @e truth_data.
+ */
+ const char *mime_type;
+
+ /**
+ * Number of bytes in @e truth_data
+ */
+ size_t truth_data_size;
+
+ /**
+ * Data used by the authorization process.
+ */
+ void *truth_data;
+
+ /**
+ * Name of the file where the service will write the challenge, or NULL.
+ */
+ char *filename;
+
+ /**
+ * Expected status code.
+ */
+ unsigned int http_status;
+
+ /**
+ * Payment request we got back, or NULL.
+ */
+ char *pay_uri;
+
+ /**
+ * Payment order ID we got back, or all zeros.
+ */
+ struct ANASTASIS_PaymentSecretP payment_secret_response;
+
+ /**
+ * Options for how we are supposed to do the upload.
+ */
+ enum ANASTASIS_TESTING_TruthStoreOption tsopt;
+};
+
+/**
+ * Function called with the results of a #truth_store().
+ *
+ * @param cls closure
+ * @param ec ANASTASIS error code
+ * @param http_status HTTP status of the request
+ * @param ud details about the upload operation
+ */
+static void
+truth_store_cb (void *cls,
+ const struct ANASTASIS_UploadDetails *ud)
+{
+ struct TruthStoreState *tss = cls;
+
+ tss->tso = NULL;
+ if ( (NULL == ud) ||
+ (ud->http_status != tss->http_status) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u to command %s in %s:%u\n",
+ (NULL != ud) ? ud->http_status : 0,
+ tss->is->commands[tss->is->ip].label,
+ __FILE__,
+ __LINE__);
+ TALER_TESTING_interpreter_fail (tss->is);
+ return;
+ }
+ switch (ud->us)
+ {
+ case ANASTASIS_US_SUCCESS:
+ break;
+ case ANASTASIS_US_PAYMENT_REQUIRED:
+ tss->pay_uri = GNUNET_strdup (ud->details.payment.payment_request);
+ tss->payment_secret_response = ud->details.payment.ps;
+ break;
+ case ANASTASIS_US_CONFLICTING_TRUTH:
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (tss->is);
+ return;
+ case ANASTASIS_US_HTTP_ERROR:
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (tss->is);
+ return;
+ case ANASTASIS_US_CLIENT_ERROR:
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (tss->is);
+ return;
+ case ANASTASIS_US_SERVER_ERROR:
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (tss->is);
+ return;
+ default:
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (tss->is);
+ return;
+ }
+ TALER_TESTING_interpreter_next (tss->is);
+}
+
+
+/**
+ * Run a "truth store" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command currently being run.
+ * @param is interpreter state.
+ */
+static void
+truth_store_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct TruthStoreState *tss = cls;
+
+ tss->is = is;
+ if (NULL != tss->prev_upload)
+ {
+ const struct TALER_TESTING_Command *ref;
+
+ ref = TALER_TESTING_interpreter_lookup_command (is,
+ tss->prev_upload);
+ if (NULL == ref)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (tss->is);
+ return;
+ }
+
+ if (0 != (ANASTASIS_TESTING_TSO_REFERENCE_UUID & tss->tsopt))
+ {
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid;
+ const struct ANASTASIS_CRYPTO_EncryptedKeyShareP *eks;
+
+ if (GNUNET_OK !=
+ ANASTASIS_TESTING_get_trait_truth_uuid (ref,
+ 0,
+ &uuid))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (tss->is);
+ return;
+ }
+ tss->uuid = *uuid;
+ if (GNUNET_OK !=
+ ANASTASIS_TESTING_get_trait_eks (ref,
+ 0,
+ &eks))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (tss->is);
+ return;
+ }
+ tss->encrypted_keyshare = *eks;
+ }
+ }
+ else
+ {
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ &tss->uuid,
+ sizeof (struct ANASTASIS_CRYPTO_TruthUUIDP));
+ GNUNET_CRYPTO_random_block (
+ GNUNET_CRYPTO_QUALITY_WEAK,
+ &tss->encrypted_keyshare,
+ sizeof (struct ANASTASIS_CRYPTO_EncryptedKeyShareP));
+ }
+ GNUNET_CRYPTO_random_block (
+ GNUNET_CRYPTO_QUALITY_WEAK,
+ &tss->key,
+ sizeof (struct ANASTASIS_CRYPTO_TruthKeyP));
+
+ {
+ void *encrypted_truth;
+ size_t size_encrypted_truth;
+ struct ANASTASIS_CRYPTO_NonceP nonce;
+
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ &nonce,
+ sizeof (nonce));
+ ANASTASIS_CRYPTO_truth_encrypt (&nonce,
+ &tss->key,
+ tss->truth_data,
+ tss->truth_data_size,
+ &encrypted_truth,
+ &size_encrypted_truth);
+ {
+ void *t;
+ size_t t_size;
+
+ ANASTASIS_CRYPTO_truth_decrypt (&tss->key,
+ encrypted_truth,
+ size_encrypted_truth,
+ &t,
+ &t_size);
+ if ( (t_size != tss->truth_data_size) ||
+ (0 != memcmp (tss->truth_data,
+ t,
+ t_size)) )
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (tss->is);
+ return;
+ }
+ GNUNET_free (t);
+ }
+ tss->tso = ANASTASIS_truth_store (
+ is->ctx,
+ tss->anastasis_url,
+ &tss->uuid,
+ tss->method,
+ &tss->encrypted_keyshare,
+ tss->mime_type,
+ size_encrypted_truth,
+ encrypted_truth,
+ (0 != (ANASTASIS_TESTING_TSO_REQUEST_PAYMENT & tss->tsopt)),
+ GNUNET_TIME_UNIT_ZERO,
+ &truth_store_cb,
+ tss);
+ GNUNET_free (encrypted_truth);
+ }
+ if (NULL == tss->tso)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (tss->is);
+ return;
+ }
+}
+
+
+/**
+ * Free the state of a "truth store" CMD, and possibly
+ * cancel it if it did not complete.
+ *
+ * @param cls closure.
+ * @param cmd command being freed.
+ */
+static void
+truth_store_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct TruthStoreState *tss = cls;
+
+ if (NULL != tss->tso)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Command '%s' did not complete (truth post)\n",
+ cmd->label);
+ ANASTASIS_truth_store_cancel (tss->tso);
+ tss->tso = NULL;
+ }
+ GNUNET_free (tss->truth_data);
+ GNUNET_free (tss->pay_uri);
+ GNUNET_free (tss->filename);
+ GNUNET_free (tss);
+}
+
+
+/**
+ * Offer internal data to other commands.
+ *
+ * @param cls closure
+ * @param[out] ret result (could be anything)
+ * @param[out] trait name of the trait
+ * @param index index number of the object to extract.
+ * @return #GNUNET_OK on success
+ */
+static int
+truth_store_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct TruthStoreState *tss = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ ANASTASIS_TESTING_make_trait_truth_uuid (0,
+ &tss->uuid),
+ ANASTASIS_TESTING_make_trait_truth_key (0,
+ &tss->key),
+ ANASTASIS_TESTING_make_trait_eks (0,
+ &tss->encrypted_keyshare),
+ ANASTASIS_TESTING_make_trait_payment_secret (0,
+ &tss->payment_secret_response),
+ TALER_TESTING_make_trait_url (TALER_TESTING_UT_TALER_URL,
+ tss->pay_uri),
+ TALER_TESTING_make_trait_string (0,
+ tss->filename),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+struct TALER_TESTING_Command
+ANASTASIS_TESTING_cmd_truth_store (const char *label,
+ const char *anastasis_url,
+ const char *prev_upload,
+ const char *method,
+ const char *mime_type,
+ size_t truth_data_size,
+ const void *truth_data,
+ enum ANASTASIS_TESTING_TruthStoreOption tso,
+ unsigned int http_status)
+{
+ struct TruthStoreState *tss;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Storing %u bytes of truth\n",
+ (unsigned int) truth_data_size);
+ tss = GNUNET_new (struct TruthStoreState);
+ tss->http_status = http_status;
+ tss->tsopt = tso;
+ tss->anastasis_url = anastasis_url;
+ tss->prev_upload = prev_upload;
+ tss->method = method;
+ tss->mime_type = mime_type;
+ tss->truth_data = GNUNET_memdup (truth_data,
+ truth_data_size);
+ tss->truth_data_size = truth_data_size;
+ if (0 == strcasecmp (method,
+ "file"))
+ tss->filename = GNUNET_strndup (truth_data,
+ truth_data_size);
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = tss,
+ .label = label,
+ .run = &truth_store_run,
+ .cleanup = &truth_store_cleanup,
+ .traits = &truth_store_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+struct TALER_TESTING_Command
+ANASTASIS_TESTING_cmd_truth_question (
+ const char *label,
+ const char *anastasis_url,
+ const char *prev_upload,
+ const char *answer,
+ enum ANASTASIS_TESTING_TruthStoreOption tso,
+ unsigned int http_status)
+{
+ struct GNUNET_HashCode h;
+
+ GNUNET_CRYPTO_hash (answer,
+ strlen (answer),
+ &h);
+ return ANASTASIS_TESTING_cmd_truth_store (label,
+ anastasis_url,
+ prev_upload,
+ "question",
+ "binary/sha512",
+ sizeof (h),
+ &h,
+ tso,
+ http_status);
+}
diff --git a/src/testing/testing_api_helpers.c b/src/testing/testing_api_helpers.c
new file mode 100644
index 0000000..66e7032
--- /dev/null
+++ b/src/testing/testing_api_helpers.c
@@ -0,0 +1,173 @@
+/*
+ This file is part of ANASTASIS
+ Copyright (C) 2014-2021 Taler Systems SA
+
+ ANASTASIS 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.
+
+ ANASTASIS is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ ANASTASISABILITY 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 ANASTASIS; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file lib/testing_api_helpers.c
+ * @brief helper functions for test library.
+ * @author Christian Grothoff
+ * @author Marcello Stanisci
+ */
+
+#include "platform.h"
+#include "anastasis_testing_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+
+
+struct GNUNET_OS_Process *
+ANASTASIS_TESTING_run_anastasis (const char *config_filename,
+ const char *anastasis_url)
+{
+ struct GNUNET_OS_Process *anastasis_proc;
+ unsigned int iter;
+ char *wget_cmd;
+
+ anastasis_proc
+ = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
+ NULL, NULL, NULL,
+ "anastasis-httpd",
+ "anastasis-httpd",
+ "--log=INFO",
+ "-c", config_filename,
+ NULL);
+ if (NULL == anastasis_proc)
+ ANASTASIS_FAIL ();
+
+ GNUNET_asprintf (&wget_cmd,
+ "wget -q -t 1 -T 1"
+ " %s"
+ " -o /dev/null -O /dev/null",
+ anastasis_url);
+
+ /* give child time to start and bind against the socket */
+ fprintf (stderr,
+ "Waiting for `anastasis-httpd' to be ready\n");
+ iter = 0;
+ do
+ {
+ if (100 == iter)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to launch `anastasis-httpd' (or `wget')\n");
+ GNUNET_OS_process_kill (anastasis_proc,
+ SIGTERM);
+ GNUNET_OS_process_wait (anastasis_proc);
+ GNUNET_OS_process_destroy (anastasis_proc);
+ ANASTASIS_FAIL ();
+ }
+ {
+ struct timespec req = {
+ .tv_nsec = 10000
+ };
+
+ nanosleep (&req,
+ NULL);
+ }
+ iter++;
+ }
+ while (0 != system (wget_cmd));
+ GNUNET_free (wget_cmd);
+ fprintf (stderr,
+ "\n");
+ return anastasis_proc;
+}
+
+
+char *
+ANASTASIS_TESTING_prepare_anastasis (const char *config_filename)
+{
+ struct GNUNET_CONFIGURATION_Handle *cfg;
+ unsigned long long port;
+ struct GNUNET_OS_Process *dbinit_proc;
+ enum GNUNET_OS_ProcessStatusType type;
+ unsigned long code;
+ char *base_url;
+
+ cfg = GNUNET_CONFIGURATION_create ();
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_load (cfg,
+ config_filename))
+ ANASTASIS_FAIL ();
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_number (cfg,
+ "anastasis",
+ "PORT",
+ &port))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "anastasis",
+ "PORT");
+ GNUNET_CONFIGURATION_destroy (cfg);
+ return NULL;
+ }
+
+ GNUNET_CONFIGURATION_destroy (cfg);
+
+ if (GNUNET_OK !=
+ GNUNET_NETWORK_test_port_free (IPPROTO_TCP,
+ (uint16_t) port))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Required port %llu not available, skipping.\n",
+ port);
+ return NULL;
+ }
+
+ /* DB preparation */
+ if (NULL == (dbinit_proc = GNUNET_OS_start_process
+ (GNUNET_OS_INHERIT_STD_ALL,
+ NULL, NULL, NULL,
+ "anastasis-dbinit",
+ "anastasis-dbinit",
+ "-c", config_filename,
+ "-r",
+ NULL)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to run anastasis-dbinit. Check your PATH.\n");
+ return NULL;
+ }
+
+ if (GNUNET_SYSERR ==
+ GNUNET_OS_process_wait_status (dbinit_proc,
+ &type,
+ &code))
+ {
+ GNUNET_OS_process_destroy (dbinit_proc);
+ ANASTASIS_FAIL ();
+ }
+ if ( (type == GNUNET_OS_PROCESS_EXITED) &&
+ (0 != code) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to setup database\n");
+ return NULL;
+ }
+ if ( (type != GNUNET_OS_PROCESS_EXITED) ||
+ (0 != code) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected error running `anastasis-dbinit'!\n");
+ return NULL;
+ }
+ GNUNET_OS_process_destroy (dbinit_proc);
+ GNUNET_asprintf (&base_url,
+ "http://localhost:%llu/",
+ port);
+ return base_url;
+}
diff --git a/src/testing/testing_api_trait_account_priv.c b/src/testing/testing_api_trait_account_priv.c
new file mode 100644
index 0000000..4860e82
--- /dev/null
+++ b/src/testing/testing_api_trait_account_priv.c
@@ -0,0 +1,72 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2019 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 Privlic License for more details.
+
+ You should have received a copy of the GNU General Privlic
+ License along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/testing_api_trait_account_priv.c
+ * @brief traits to offer a account_priv
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "anastasis_testing_lib.h"
+
+#define ANASTASIS_TESTING_TRAIT_ACCOUNT_PRIV "anastasis-account_priv"
+
+
+/**
+ * Obtain an account private key from @a cmd.
+ *
+ * @param cmd command to extract the private key from.
+ * @param index the private key's index number.
+ * @param n[out] set to the private key coming from @a cmd.
+ * @return #GNUNET_OK on success.
+ */
+int
+ANASTASIS_TESTING_get_trait_account_priv
+ (const struct TALER_TESTING_Command *cmd,
+ unsigned int index,
+ const struct ANASTASIS_CRYPTO_AccountPrivateKeyP **priv)
+{
+ return cmd->traits (cmd->cls,
+ (const void **) priv,
+ ANASTASIS_TESTING_TRAIT_ACCOUNT_PRIV,
+ index);
+}
+
+
+/**
+ * Offer an account private key.
+ *
+ * @param index usually zero
+ * @param priv the account_priv to offer.
+ * @return #GNUNET_OK on success.
+ */
+struct TALER_TESTING_Trait
+ANASTASIS_TESTING_make_trait_account_priv
+ (unsigned int index,
+ const struct ANASTASIS_CRYPTO_AccountPrivateKeyP *priv)
+{
+ struct TALER_TESTING_Trait ret = {
+ .index = index,
+ .trait_name = ANASTASIS_TESTING_TRAIT_ACCOUNT_PRIV,
+ .ptr = (const void *) priv
+ };
+ return ret;
+}
+
+
+/* end of testing_api_trait_account_priv.c */
diff --git a/src/testing/testing_api_trait_account_pub.c b/src/testing/testing_api_trait_account_pub.c
new file mode 100644
index 0000000..5a3632e
--- /dev/null
+++ b/src/testing/testing_api_trait_account_pub.c
@@ -0,0 +1,72 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2019 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 lib/testing_api_trait_account_pub.c
+ * @brief traits to offer a account_pub
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "anastasis_testing_lib.h"
+
+#define ANASTASIS_TESTING_TRAIT_ACCOUNT_PUB "anastasis-account_pub"
+
+
+/**
+ * Obtain an account public key from @a cmd.
+ *
+ * @param cmd command to extract the public key from.
+ * @param index the public key's index number.
+ * @param n[out] set to the public key coming from @a cmd.
+ * @return #GNUNET_OK on success.
+ */
+int
+ANASTASIS_TESTING_get_trait_account_pub
+ (const struct TALER_TESTING_Command *cmd,
+ unsigned int index,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP **pub)
+{
+ return cmd->traits (cmd->cls,
+ (const void **) pub,
+ ANASTASIS_TESTING_TRAIT_ACCOUNT_PUB,
+ index);
+}
+
+
+/**
+ * Offer an account public key.
+ *
+ * @param index usually zero
+ * @param h the account_pub to offer.
+ * @return #GNUNET_OK on success.
+ */
+struct TALER_TESTING_Trait
+ANASTASIS_TESTING_make_trait_account_pub
+ (unsigned int index,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *h)
+{
+ struct TALER_TESTING_Trait ret = {
+ .index = index,
+ .trait_name = ANASTASIS_TESTING_TRAIT_ACCOUNT_PUB,
+ .ptr = (const void *) h
+ };
+ return ret;
+}
+
+
+/* end of testing_api_trait_account_pub.c */
diff --git a/src/testing/testing_api_trait_code.c b/src/testing/testing_api_trait_code.c
new file mode 100644
index 0000000..1a43cf8
--- /dev/null
+++ b/src/testing/testing_api_trait_code.c
@@ -0,0 +1,73 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2019 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 lib/testing_api_trait_string.c
+ * @brief traits to offers a code for a challenge
+ * @author Dominik Meister
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "anastasis_testing_lib.h"
+
+#define ANASTASIS_TESTING_TRAIT_CODE "anastasis-code"
+
+
+/**
+ * Obtain a code from @a cmd.
+ *
+ * @param cmd command to extract the number from.
+ * @param index the number's index number.
+ * @param n[out] set to the number coming from @a cmd.
+ * @return #GNUNET_OK on success.
+ */
+int
+ANASTASIS_TESTING_get_trait_code
+ (const struct TALER_TESTING_Command *cmd,
+ unsigned int index,
+ const char **code)
+{
+ return cmd->traits (cmd->cls,
+ (const void **) code,
+ ANASTASIS_TESTING_TRAIT_CODE,
+ index);
+}
+
+
+/**
+ * Offer a code.
+ *
+ * @param index the number's index number.
+ * @param code the code to offer.
+ * @return #GNUNET_OK on success.
+ */
+struct TALER_TESTING_Trait
+ANASTASIS_TESTING_make_trait_code
+ (unsigned int index,
+ const char *code)
+{
+ struct TALER_TESTING_Trait ret = {
+ .index = index,
+ .trait_name = ANASTASIS_TESTING_TRAIT_CODE,
+ .ptr = (const void *) code
+ };
+ return ret;
+}
+
+
+/* end of testing_api_trait_string.c */
diff --git a/src/testing/testing_api_trait_eks.c b/src/testing/testing_api_trait_eks.c
new file mode 100644
index 0000000..dc3f923
--- /dev/null
+++ b/src/testing/testing_api_trait_eks.c
@@ -0,0 +1,58 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2021 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 lib/testing_api_trait_eks.c
+ * @brief traits to offer a payment identifier
+ * @author Dennis Neufeld
+ */
+#include "platform.h"
+#include "anastasis_testing_lib.h"
+
+#define ANASTASIS_TESTING_TRAIT_EKS \
+ "anastasis-eks"
+
+
+int
+ANASTASIS_TESTING_get_trait_eks
+ (const struct TALER_TESTING_Command *cmd,
+ unsigned int index,
+ const struct ANASTASIS_CRYPTO_EncryptedKeyShareP **eks)
+{
+ return cmd->traits (cmd->cls,
+ (const void **) eks,
+ ANASTASIS_TESTING_TRAIT_EKS,
+ index);
+}
+
+
+struct TALER_TESTING_Trait
+ANASTASIS_TESTING_make_trait_eks
+ (unsigned int index,
+ const struct ANASTASIS_CRYPTO_EncryptedKeyShareP *eks)
+{
+ struct TALER_TESTING_Trait ret = {
+ .index = index,
+ .trait_name = ANASTASIS_TESTING_TRAIT_EKS,
+ .ptr = (const void *) eks
+ };
+ return ret;
+}
+
+
+/* end of testing_api_trait_eks.c */
diff --git a/src/testing/testing_api_trait_hash.c b/src/testing/testing_api_trait_hash.c
new file mode 100644
index 0000000..18be1ea
--- /dev/null
+++ b/src/testing/testing_api_trait_hash.c
@@ -0,0 +1,72 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2019 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 lib/testing_api_trait_hash.c
+ * @brief traits to offer a hash
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "anastasis_testing_lib.h"
+
+#define ANASTASIS_TESTING_TRAIT_HASH "anastasis-hash"
+
+
+/**
+ * Obtain a hash from @a cmd.
+ *
+ * @param cmd command to extract the number from.
+ * @param index the number's index number.
+ * @param n[out] set to the number coming from @a cmd.
+ * @return #GNUNET_OK on success.
+ */
+int
+ANASTASIS_TESTING_get_trait_hash
+ (const struct TALER_TESTING_Command *cmd,
+ unsigned int index,
+ const struct GNUNET_HashCode **h)
+{
+ return cmd->traits (cmd->cls,
+ (const void **) h,
+ ANASTASIS_TESTING_TRAIT_HASH,
+ index);
+}
+
+
+/**
+ * Offer a hash.
+ *
+ * @param index the number's index number.
+ * @param h the hash to offer.
+ * @return #GNUNET_OK on success.
+ */
+struct TALER_TESTING_Trait
+ANASTASIS_TESTING_make_trait_hash
+ (unsigned int index,
+ const struct GNUNET_HashCode *h)
+{
+ struct TALER_TESTING_Trait ret = {
+ .index = index,
+ .trait_name = ANASTASIS_TESTING_TRAIT_HASH,
+ .ptr = (const void *) h
+ };
+ return ret;
+}
+
+
+/* end of testing_api_trait_hash.c */
diff --git a/src/testing/testing_api_trait_payment_secret.c b/src/testing/testing_api_trait_payment_secret.c
new file mode 100644
index 0000000..6238879
--- /dev/null
+++ b/src/testing/testing_api_trait_payment_secret.c
@@ -0,0 +1,73 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2019 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 lib/testing_api_trait_payment_secret.c
+ * @brief traits to offer a payment identifier
+ * @author Dennis Neufeld
+ */
+#include "platform.h"
+#include "anastasis_testing_lib.h"
+
+#define ANASTASIS_TESTING_TRAIT_PAYMENT_SECRET \
+ "anastasis-payment_secret"
+
+
+/**
+ * Obtain an account public key from @a cmd.
+ *
+ * @param cmd command to extract the payment identifier from.
+ * @param index the payment identifier's index number.
+ * @param n[out] set to the payment identifier coming from @a cmd.
+ * @return #GNUNET_OK on success.
+ */
+int
+ANASTASIS_TESTING_get_trait_payment_secret
+ (const struct TALER_TESTING_Command *cmd,
+ unsigned int index,
+ const struct ANASTASIS_PaymentSecretP **payment_secret)
+{
+ return cmd->traits (cmd->cls,
+ (const void **) payment_secret,
+ ANASTASIS_TESTING_TRAIT_PAYMENT_SECRET,
+ index);
+}
+
+
+/**
+ * Offer a payment identifier.
+ *
+ * @param index usually zero
+ * @param h the payment identifier to offer.
+ * @return #GNUNET_OK on success.
+ */
+struct TALER_TESTING_Trait
+ANASTASIS_TESTING_make_trait_payment_secret
+ (unsigned int index,
+ const struct ANASTASIS_PaymentSecretP *h)
+{
+ struct TALER_TESTING_Trait ret = {
+ .index = index,
+ .trait_name = ANASTASIS_TESTING_TRAIT_PAYMENT_SECRET,
+ .ptr = (const void *) h
+ };
+ return ret;
+}
+
+
+/* end of testing_api_trait_payment_secret.c */
diff --git a/src/testing/testing_api_trait_salt.c b/src/testing/testing_api_trait_salt.c
new file mode 100644
index 0000000..116742f
--- /dev/null
+++ b/src/testing/testing_api_trait_salt.c
@@ -0,0 +1,74 @@
+/*
+ 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
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/testing_api_trait_salt.c
+ * @brief traits to offer a hash
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ */
+#include "platform.h"
+#include "anastasis_testing_lib.h"
+
+#define ANASTASIS_TESTING_TRAIT_SALT "anastasis-provider-salt"
+
+
+/**
+ * Obtain a salt from @a cmd.
+ *
+ * @param cmd command to extract the salt from.
+ * @param index the salt's index number.
+ * @param s[out] set to the salt coming from @a cmd.
+ * @return #GNUNET_OK on success.
+ */
+int
+ANASTASIS_TESTING_get_trait_salt
+ (const struct TALER_TESTING_Command *cmd,
+ unsigned int index,
+ const struct ANASTASIS_CRYPTO_ProviderSaltP **s)
+{
+ return cmd->traits (cmd->cls,
+ (const void **) s,
+ ANASTASIS_TESTING_TRAIT_SALT,
+ index);
+}
+
+
+/**
+ * Offer an salt.
+ *
+ * @param index the salt's index number.
+ * @param u the salt to offer.
+ * @return #GNUNET_OK on success.
+ */
+struct TALER_TESTING_Trait
+ANASTASIS_TESTING_make_trait_salt
+ (unsigned int index,
+ const struct ANASTASIS_CRYPTO_ProviderSaltP *s)
+{
+ struct TALER_TESTING_Trait ret = {
+ .index = index,
+ .trait_name = ANASTASIS_TESTING_TRAIT_SALT,
+ .ptr = (const void *) s
+ };
+ return ret;
+}
+
+
+/* end of testing_api_trait_salt.c */
diff --git a/src/testing/testing_api_trait_truth_key.c b/src/testing/testing_api_trait_truth_key.c
new file mode 100644
index 0000000..55094c1
--- /dev/null
+++ b/src/testing/testing_api_trait_truth_key.c
@@ -0,0 +1,58 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2019, 2021 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 lib/testing_api_trait_truth_key.c
+ * @brief traits to offer a payment identifier
+ * @author Dennis Neufeld
+ */
+#include "platform.h"
+#include "anastasis_testing_lib.h"
+
+#define ANASTASIS_TESTING_TRAIT_TRUTH_KEY \
+ "anastasis-truth_key"
+
+
+int
+ANASTASIS_TESTING_get_trait_truth_key
+ (const struct TALER_TESTING_Command *cmd,
+ unsigned int index,
+ const struct ANASTASIS_CRYPTO_TruthKeyP **truth_key)
+{
+ return cmd->traits (cmd->cls,
+ (const void **) truth_key,
+ ANASTASIS_TESTING_TRAIT_TRUTH_KEY,
+ index);
+}
+
+
+struct TALER_TESTING_Trait
+ANASTASIS_TESTING_make_trait_truth_key
+ (unsigned int index,
+ const struct ANASTASIS_CRYPTO_TruthKeyP *h)
+{
+ struct TALER_TESTING_Trait ret = {
+ .index = index,
+ .trait_name = ANASTASIS_TESTING_TRAIT_TRUTH_KEY,
+ .ptr = (const void *) h
+ };
+ return ret;
+}
+
+
+/* end of testing_api_trait_truth_key.c */
diff --git a/src/testing/testing_api_trait_truth_uuid.c b/src/testing/testing_api_trait_truth_uuid.c
new file mode 100644
index 0000000..38a7336
--- /dev/null
+++ b/src/testing/testing_api_trait_truth_uuid.c
@@ -0,0 +1,74 @@
+/*
+ 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
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/testing_api_trait_truth_pub.c
+ * @brief traits to offer a UUID for some truth
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ */
+#include "platform.h"
+#include "anastasis_testing_lib.h"
+
+#define ANASTASIS_TESTING_TRAIT_TRUTH_UUID "anastasis-truth-uuid"
+
+
+/**
+ * Obtain an public key from @a cmd.
+ *
+ * @param cmd command to extract the number from.
+ * @param index the number's index number.
+ * @param u[out] set to the number coming from @a cmd.
+ * @return #GNUNET_OK on success.
+ */
+int
+ANASTASIS_TESTING_get_trait_truth_uuid
+ (const struct TALER_TESTING_Command *cmd,
+ unsigned int index,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP **tpk)
+{
+ return cmd->traits (cmd->cls,
+ (const void **) tpk,
+ ANASTASIS_TESTING_TRAIT_TRUTH_UUID,
+ index);
+}
+
+
+/**
+ * Offer a truth public key.
+ *
+ * @param index the number's index number.
+ * @param tpk the public key to offer.
+ * @return #GNUNET_OK on success.
+ */
+struct TALER_TESTING_Trait
+ANASTASIS_TESTING_make_trait_truth_uuid
+ (unsigned int index,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *tpk)
+{
+ struct TALER_TESTING_Trait ret = {
+ .index = index,
+ .trait_name = ANASTASIS_TESTING_TRAIT_TRUTH_UUID,
+ .ptr = (const void *) tpk
+ };
+ return ret;
+}
+
+
+/* end of testing_api_trait_truth_pub.c */
diff --git a/src/testing/testing_cmd_challenge_answer.c b/src/testing/testing_cmd_challenge_answer.c
new file mode 100644
index 0000000..b243d61
--- /dev/null
+++ b/src/testing/testing_cmd_challenge_answer.c
@@ -0,0 +1,584 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/testing_cmd_challenge_answer.c
+ * @brief command to execute the anastasis recovery service
+ * @author Christian Grothoff
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ */
+
+#include "platform.h"
+#include "anastasis_testing_lib.h"
+#include <taler/taler_util.h>
+#include <taler/taler_testing_lib.h>
+#include <taler/taler_merchant_service.h>
+
+
+/**
+ * State for a "challenge answer" CMD.
+ */
+struct ChallengeState
+{
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Reference to the challenge we are solving
+ */
+ struct ANASTASIS_Challenge *c;
+
+ /**
+ * Answer to the challenge we are solving
+ */
+ const char *answer;
+
+ /**
+ * Reference to the recovery process
+ */
+ const char *challenge_ref;
+
+ /**
+ * Reference to the payment
+ */
+ const char *payment_ref;
+
+ /**
+ * "taler://pay/" URL we got back, if any. Otherwise NULL.
+ */
+ char *payment_uri;
+
+ /**
+ * Order ID extracted from @e payment_uri, or NULL.
+ */
+ char *order_id;
+
+ /**
+ * Payment order ID we are to provide in the request.
+ */
+ struct ANASTASIS_PaymentSecretP payment_order_req;
+
+ /**
+ * Expected status code.
+ */
+ enum ANASTASIS_ChallengeStatus expected_cs;
+
+ /**
+ * Index of the challenge we are solving
+ */
+ unsigned int challenge_index;
+
+ /**
+ * 0 for no plugin needed 1 for plugin needed to authenticate
+ */
+ unsigned int mode;
+
+ /**
+ * code we read in the file generated by the plugin
+ */
+ char code[22];
+
+};
+
+
+static void
+challenge_answer_cb (void *af_cls,
+ const struct ANASTASIS_ChallengeStartResponse *csr)
+{
+ struct ChallengeState *cs = af_cls;
+
+ cs->c = NULL;
+ if (csr->cs != cs->expected_cs)
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Expected status %u, got %u\n",
+ cs->expected_cs,
+ csr->cs);
+ TALER_TESTING_interpreter_fail (cs->is);
+ return;
+ }
+ switch (csr->cs)
+ {
+ case ANASTASIS_CHALLENGE_STATUS_SOLVED:
+ break;
+ case ANASTASIS_CHALLENGE_STATUS_INSTRUCTIONS:
+ {
+ FILE *file;
+ char *fn;
+
+ if (0 == strcasecmp (csr->details.open_challenge.content_type,
+ "application/json"))
+ {
+ const char *filename;
+ json_t *in;
+
+ in = json_loadb (csr->details.open_challenge.body,
+ csr->details.open_challenge.body_size,
+ JSON_REJECT_DUPLICATES,
+ NULL);
+ if (NULL == in)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (cs->is);
+ return;
+ }
+ filename = json_string_value (json_object_get (in,
+ "filename"));
+ if (NULL == filename)
+ {
+ GNUNET_break (0);
+ json_decref (in);
+ TALER_TESTING_interpreter_fail (cs->is);
+ return;
+ }
+ fn = GNUNET_strdup (filename);
+ json_decref (in);
+ }
+ else
+ {
+ fn = GNUNET_strndup (csr->details.open_challenge.body,
+ csr->details.open_challenge.body_size);
+ }
+ file = fopen (fn,
+ "r");
+ if (NULL == file)
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "open",
+ fn);
+ GNUNET_free (fn);
+ TALER_TESTING_interpreter_fail (cs->is);
+ return;
+ }
+ if (0 == fscanf (file,
+ "%21s",
+ cs->code))
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "fscanf",
+ fn);
+ TALER_TESTING_interpreter_fail (cs->is);
+ fclose (file);
+ GNUNET_free (fn);
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Read challenge answer `%s' from file `%s'\n",
+ cs->code,
+ fn);
+ TALER_TESTING_interpreter_next (cs->is);
+ GNUNET_break (0 == fclose (file));
+ GNUNET_free (fn);
+ return;
+ }
+ case ANASTASIS_CHALLENGE_STATUS_PAYMENT_REQUIRED:
+ if (0 != strncmp (csr->details.payment_required.taler_pay_uri,
+ "taler+http://pay/",
+ strlen ("taler+http://pay/")))
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid payment URI `%s'\n",
+ csr->details.payment_required.taler_pay_uri);
+ TALER_TESTING_interpreter_fail (cs->is);
+ return;
+ }
+ cs->payment_uri = GNUNET_strdup (
+ csr->details.payment_required.taler_pay_uri);
+ {
+ struct TALER_MERCHANT_PayUriData pud;
+
+ if (GNUNET_OK !=
+ TALER_MERCHANT_parse_pay_uri (cs->payment_uri,
+ &pud))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (cs->is);
+ return;
+ }
+ cs->order_id = GNUNET_strdup (pud.order_id);
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (cs->order_id,
+ strlen (cs->order_id),
+ &cs->payment_order_req,
+ sizeof (cs->payment_order_req)))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (cs->is);
+ return;
+ }
+ TALER_MERCHANT_parse_pay_uri_free (&pud);
+ }
+ TALER_TESTING_interpreter_next (cs->is);
+ return;
+ case ANASTASIS_CHALLENGE_STATUS_TRUTH_UNKNOWN:
+ break;
+ case ANASTASIS_CHALLENGE_STATUS_REDIRECT_FOR_AUTHENTICATION:
+ break;
+ case ANASTASIS_CHALLENGE_STATUS_SERVER_FAILURE:
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (cs->is);
+ return;
+ case ANASTASIS_CHALLENGE_STATUS_RATE_LIMIT_EXCEEDED:
+ break;
+ }
+ TALER_TESTING_interpreter_next (cs->is);
+}
+
+
+/**
+ * Run a "recover secret" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command currently being run.
+ * @param is interpreter state.
+ */
+static void
+challenge_answer_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct ChallengeState *cs = cls;
+ const struct ANASTASIS_Challenge *c;
+ const struct ANASTASIS_PaymentSecretP *ps;
+
+ cs->is = is;
+ if (NULL != cs->challenge_ref)
+ {
+ const struct TALER_TESTING_Command *ref;
+
+ ref = TALER_TESTING_interpreter_lookup_command (
+ is,
+ cs->challenge_ref);
+ if (NULL == ref)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (cs->is);
+ return;
+ }
+ if (GNUNET_OK !=
+ ANASTASIS_TESTING_get_trait_challenge (ref,
+ cs->challenge_index,
+ &c))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (cs->is);
+ return;
+ }
+ }
+
+ if (NULL != cs->payment_ref)
+ {
+ const struct TALER_TESTING_Command *ref;
+
+ ref = TALER_TESTING_interpreter_lookup_command (is,
+ cs->payment_ref);
+ if (NULL == ref)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (cs->is);
+ return;
+ }
+ if (GNUNET_OK !=
+ ANASTASIS_TESTING_get_trait_payment_secret (ref,
+ 0,
+ &ps))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (cs->is);
+ return;
+ }
+ }
+ else
+ {
+ ps = NULL;
+ }
+
+ cs->c = (struct ANASTASIS_Challenge *) c;
+
+ if (1 == cs->mode)
+ {
+ const struct TALER_TESTING_Command *ref;
+ const char *answer;
+ unsigned long long code;
+ char dummy;
+
+ ref = TALER_TESTING_interpreter_lookup_command (is,
+ cs->answer);
+ if (NULL == ref)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (cs->is);
+ return;
+ }
+ if (GNUNET_OK !=
+ ANASTASIS_TESTING_get_trait_code (ref,
+ 0,
+ &answer))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (cs->is);
+ return;
+ }
+ if (1 !=
+ sscanf (answer,
+ "%llu%c",
+ &code,
+ &dummy))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (cs->is);
+ return;
+ }
+ if (GNUNET_OK !=
+ ANASTASIS_challenge_answer2 (cs->c,
+ ps,
+ GNUNET_TIME_UNIT_ZERO,
+ code,
+ &challenge_answer_cb,
+ cs))
+ {
+ GNUNET_break (0);
+ cs->c = NULL;
+ TALER_TESTING_interpreter_fail (cs->is);
+ return;
+ }
+
+ }
+ else
+ {
+ if (GNUNET_OK !=
+ ANASTASIS_challenge_answer (cs->c,
+ ps,
+ GNUNET_TIME_UNIT_ZERO,
+ cs->answer,
+ &challenge_answer_cb,
+ cs))
+ {
+ GNUNET_break (0);
+ cs->c = NULL;
+ TALER_TESTING_interpreter_fail (cs->is);
+ return;
+ }
+ }
+}
+
+
+/**
+ * Run a "recover secret" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command currently being run.
+ * @param is interpreter state.
+ */
+static void
+challenge_start_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct ChallengeState *cs = cls;
+ const struct ANASTASIS_Challenge *c;
+ const struct TALER_TESTING_Command *ref;
+ const struct ANASTASIS_PaymentSecretP *ps;
+
+ cs->is = is;
+ ref = TALER_TESTING_interpreter_lookup_command (
+ is,
+ cs->challenge_ref);
+ if (NULL == ref)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (cs->is);
+ return;
+ }
+ if (GNUNET_OK !=
+ ANASTASIS_TESTING_get_trait_challenge (ref,
+ cs->challenge_index,
+ &c))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (cs->is);
+ return;
+ }
+ if (NULL != cs->payment_ref)
+ {
+ const struct TALER_TESTING_Command *ref;
+
+ ref = TALER_TESTING_interpreter_lookup_command (is,
+ cs->payment_ref);
+ if (NULL == ref)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (cs->is);
+ return;
+ }
+ if (GNUNET_OK !=
+ ANASTASIS_TESTING_get_trait_payment_secret (ref,
+ 0,
+ &ps))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (cs->is);
+ return;
+ }
+ }
+ else
+ {
+ ps = NULL;
+ }
+ if (GNUNET_OK !=
+ ANASTASIS_challenge_start ((struct ANASTASIS_Challenge *) c,
+ ps,
+ GNUNET_TIME_UNIT_ZERO,
+ NULL,
+ &challenge_answer_cb,
+ cs))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (cs->is);
+ return;
+ }
+}
+
+
+/**
+ * Free the state of a "recover secret" CMD, and possibly
+ * cancel it if it did not complete.
+ *
+ * @param cls closure.
+ * @param cmd command being freed.
+ */
+static void
+challenge_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct ChallengeState *cs = cls;
+
+ if (NULL != cs->c)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Command '%s' did not complete (challenge answer)\n",
+ cmd->label);
+ ANASTASIS_challenge_abort (cs->c);
+ cs->c = NULL;
+ }
+ GNUNET_free (cs->payment_uri);
+ GNUNET_free (cs->order_id);
+ GNUNET_free (cs);
+}
+
+
+/**
+ * Offer internal data to other commands.
+ *
+ * @param cls closure
+ * @param ret[out] result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to extract.
+ * @return #GNUNET_OK on success
+ */
+static int
+challenge_create_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct ChallengeState *cs = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ ANASTASIS_TESTING_make_trait_code (0,
+ cs->code),
+ ANASTASIS_TESTING_make_trait_payment_secret (0,
+ &cs->payment_order_req),
+ TALER_TESTING_make_trait_url (TALER_TESTING_UT_TALER_URL,
+ cs->payment_uri),
+ TALER_TESTING_make_trait_order_id (0,
+ cs->order_id),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+struct TALER_TESTING_Command
+ANASTASIS_TESTING_cmd_challenge_start (
+ const char *label,
+ const char *payment_ref,
+ const char *challenge_ref,
+ unsigned int challenge_index,
+ enum ANASTASIS_ChallengeStatus expected_cs)
+{
+ struct ChallengeState *cs;
+
+ cs = GNUNET_new (struct ChallengeState);
+ cs->expected_cs = expected_cs;
+ cs->challenge_ref = challenge_ref;
+ cs->payment_ref = payment_ref;
+ cs->challenge_index = challenge_index;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = cs,
+ .label = label,
+ .run = &challenge_start_run,
+ .cleanup = &challenge_cleanup,
+ .traits = &challenge_create_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+struct TALER_TESTING_Command
+ANASTASIS_TESTING_cmd_challenge_answer (
+ const char *label,
+ const char *payment_ref,
+ const char *challenge_ref,
+ unsigned int challenge_index,
+ const char *answer,
+ unsigned int mode,
+ enum ANASTASIS_ChallengeStatus expected_cs)
+{
+ struct ChallengeState *cs;
+
+ cs = GNUNET_new (struct ChallengeState);
+ cs->expected_cs = expected_cs;
+ cs->challenge_ref = challenge_ref;
+ cs->payment_ref = payment_ref;
+ cs->answer = answer;
+ cs->challenge_index = challenge_index;
+ cs->mode = mode;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = cs,
+ .label = label,
+ .run = &challenge_answer_run,
+ .cleanup = &challenge_cleanup,
+ .traits = &challenge_create_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_cmd_challenge_answer.c */
diff --git a/src/testing/testing_cmd_policy_create.c b/src/testing/testing_cmd_policy_create.c
new file mode 100644
index 0000000..fc9ed44
--- /dev/null
+++ b/src/testing/testing_cmd_policy_create.c
@@ -0,0 +1,208 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/testing_cmd_policy_create.c
+ * @brief command to execute the anastasis secret share service
+ * @author Christian Grothoff
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ */
+
+#include "platform.h"
+#include "anastasis_testing_lib.h"
+#include <taler/taler_util.h>
+#include <taler/taler_testing_lib.h>
+
+
+/**
+ * State for a "policy create" CMD.
+ */
+struct PolicyCreateState
+{
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Label of this command.
+ */
+ const char *label;
+
+ /**
+ * References to upload commands of previous truth uploads.
+ */
+ const char **cmd_label_array;
+
+ /**
+ * Length of array of command labels (cmd_label_array).
+ */
+ unsigned int cmd_label_array_length;
+
+ /**
+ * Policy object
+ */
+ struct ANASTASIS_Policy *policy;
+};
+
+
+/**
+ * Run a "policy create" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command currently being run.
+ * @param is interpreter state.
+ */
+static void
+policy_create_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct PolicyCreateState *pcs = cls;
+ const struct ANASTASIS_Truth *truths[pcs->cmd_label_array_length];
+
+ GNUNET_assert (pcs->cmd_label_array_length > 0);
+ GNUNET_assert (NULL != pcs->cmd_label_array);
+ pcs->is = is;
+ if (NULL != pcs->cmd_label_array)
+ {
+ for (unsigned int i = 0; i < pcs->cmd_label_array_length; i++)
+ {
+ const struct TALER_TESTING_Command *ref;
+ const struct ANASTASIS_Truth *truth;
+
+ ref = TALER_TESTING_interpreter_lookup_command (is,
+ pcs->cmd_label_array[i]);
+ if (NULL == ref)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (pcs->is);
+ return;
+ }
+ if (GNUNET_OK !=
+ ANASTASIS_TESTING_get_trait_truth (ref,
+ 0,
+ &truth))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (pcs->is);
+ return;
+ }
+ GNUNET_assert (NULL != truth);
+ truths[i] = truth;
+ }
+ }
+
+ pcs->policy = ANASTASIS_policy_create (truths,
+ pcs->cmd_label_array_length);
+
+ if (NULL == pcs->policy)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (pcs->is);
+ return;
+ }
+ TALER_TESTING_interpreter_next (pcs->is);
+}
+
+
+/**
+ * Free the state of a "policy create" CMD, and possibly
+ * cancel it if it did not complete.
+ *
+ * @param cls closure.
+ * @param cmd command being freed.
+ */
+static void
+policy_create_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct PolicyCreateState *pcs = cls;
+
+ GNUNET_free (pcs->cmd_label_array);
+ if (NULL != pcs->policy)
+ {
+ ANASTASIS_policy_destroy (pcs->policy);
+ pcs->policy = NULL;
+ }
+ GNUNET_free (pcs);
+}
+
+
+/**
+ * Offer internal data to other commands.
+ *
+ * @param cls closure
+ * @param ret[out] result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to extract.
+ * @return #GNUNET_OK on success
+ */
+static int
+policy_create_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct PolicyCreateState *pcs = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ ANASTASIS_TESTING_make_trait_policy (0,
+ pcs->policy),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+struct TALER_TESTING_Command
+ANASTASIS_TESTING_cmd_policy_create (const char *label,
+ ...)
+{
+ struct PolicyCreateState *pcs;
+ va_list ap;
+ const char *truth_upload_cmd;
+
+ pcs = GNUNET_new (struct PolicyCreateState);
+ pcs->label = label;
+
+ va_start (ap,
+ label);
+ while (NULL != (truth_upload_cmd = va_arg (ap, const char *)))
+ {
+ GNUNET_array_append (pcs->cmd_label_array,
+ pcs->cmd_label_array_length,
+ truth_upload_cmd);
+ }
+ va_end (ap);
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = pcs,
+ .label = label,
+ .run = &policy_create_run,
+ .cleanup = &policy_create_cleanup,
+ .traits = &policy_create_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_cmd_policy_create.c */
diff --git a/src/testing/testing_cmd_recover_secret.c b/src/testing/testing_cmd_recover_secret.c
new file mode 100644
index 0000000..a95bdd2
--- /dev/null
+++ b/src/testing/testing_cmd_recover_secret.c
@@ -0,0 +1,518 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 2021 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/testing_cmd_recover_secret.c
+ * @brief command to execute the anastasis recovery service
+ * @author Christian Grothoff
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ */
+#include "platform.h"
+#include "anastasis_testing_lib.h"
+#include <taler/taler_util.h>
+#include <taler/taler_testing_lib.h>
+
+
+/**
+ * State for a "recover secret" CMD.
+ */
+struct RecoverSecretState
+{
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * URL of the anastasis backend.
+ */
+ const char *anastasis_url;
+
+ /**
+ * The /policy GET operation handle.
+ */
+ struct ANASTASIS_Recovery *recovery;
+
+ /**
+ * Reference to download command we expect to lookup.
+ */
+ const char *download_reference;
+
+ /**
+ * Reference to download command we expect to lookup.
+ */
+ const char *core_secret_reference;
+
+ /**
+ * Options for how we are supposed to do the download.
+ */
+ enum ANASTASIS_TESTING_RecoverSecretOption rsopt;
+
+ /**
+ * Identification data from the user
+ */
+ json_t *id_data;
+
+ /**
+ * Salt to be used to derive the id
+ */
+ struct ANASTASIS_CRYPTO_ProviderSaltP *salt;
+
+ /**
+ * Recovery information from the lookup
+ */
+ struct ANASTASIS_RecoveryInformation *ri;
+
+ /**
+ * Coresecret to check if decryption worked
+ */
+ const void *core_secret;
+
+ /**
+ * Task scheduled to wait for recovery to complete.
+ */
+ struct GNUNET_SCHEDULER_Task *recovery_task;
+
+ /**
+ * version of the recovery document
+ */
+ unsigned int version;
+
+ /**
+ * #GNUNET_OK if the secret was recovered, #GNUNET_SYSERR if
+ * recovery failed (yielded wrong secret).
+ */
+ int recovered;
+};
+
+
+/**
+ * Callback which passes back the recovery document and its possible
+ * policies. Also passes back the version of the document for the user
+ * to check.
+ *
+ * @param cls closure for the callback
+ * @param ri recovery information struct which contains the policies
+ */
+static void
+policy_lookup_cb (void *cls,
+ const struct ANASTASIS_RecoveryInformation *ri)
+{
+ struct RecoverSecretState *rss = cls;
+
+ rss->ri = (struct ANASTASIS_RecoveryInformation *) ri;
+ if (NULL == ri)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (rss->is);
+ return;
+ }
+ TALER_TESTING_interpreter_next (rss->is);
+}
+
+
+/**
+ * This function is called whenever the recovery process ends.
+ * On success, the secret is returned in @a secret.
+ *
+ * @param cls closure
+ * @param ec error code
+ * @param secret contains the core secret which is passed to the user
+ * @param secret_size defines the size of the core secret
+ */
+static void
+core_secret_cb (void *cls,
+ enum ANASTASIS_RecoveryStatus rc,
+ const void *secret,
+ size_t secret_size)
+{
+ struct RecoverSecretState *rss = cls;
+
+ rss->recovery = NULL;
+ if (ANASTASIS_RS_SUCCESS != rc)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Recovery failed with status %d\n",
+ rc);
+ TALER_TESTING_interpreter_fail (rss->is);
+ return;
+ }
+ if (0 != memcmp (secret,
+ rss->core_secret,
+ secret_size))
+ {
+ GNUNET_break (0);
+ rss->recovered = GNUNET_SYSERR;
+ if (NULL != rss->recovery_task)
+ {
+ GNUNET_SCHEDULER_cancel (rss->recovery_task);
+ rss->recovery_task = NULL;
+ TALER_TESTING_interpreter_fail (rss->is);
+ }
+ return;
+ }
+ rss->recovered = GNUNET_OK;
+ if (NULL != rss->recovery_task)
+ {
+ GNUNET_SCHEDULER_cancel (rss->recovery_task);
+ rss->recovery_task = NULL;
+ TALER_TESTING_interpreter_next (rss->is);
+ }
+}
+
+
+/**
+ * Run a "recover secret" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command currently being run.
+ * @param is interpreter state.
+ */
+static void
+recover_secret_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct RecoverSecretState *rss = cls;
+ const struct TALER_TESTING_Command *ref;
+ const struct ANASTASIS_CRYPTO_ProviderSaltP *salt;
+ rss->is = is;
+
+ if (NULL != rss->download_reference)
+ {
+ ref = TALER_TESTING_interpreter_lookup_command
+ (is,
+ rss->download_reference);
+ if (NULL == ref)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (rss->is);
+ return;
+ }
+ if (GNUNET_OK !=
+ ANASTASIS_TESTING_get_trait_salt (ref,
+ 0,
+ &salt))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (rss->is);
+ return;
+ }
+ }
+ if (NULL != rss->core_secret_reference)
+ {
+ ref = TALER_TESTING_interpreter_lookup_command
+ (is,
+ rss->core_secret_reference);
+ if (NULL == ref)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (rss->is);
+ return;
+ }
+ if (GNUNET_OK !=
+ ANASTASIS_TESTING_get_trait_core_secret (ref,
+ 0,
+ &rss->core_secret))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (rss->is);
+ return;
+ }
+ }
+ rss->recovery = ANASTASIS_recovery_begin (is->ctx,
+ rss->id_data,
+ rss->version,
+ rss->anastasis_url,
+ salt,
+ &policy_lookup_cb,
+ rss,
+ &core_secret_cb,
+ rss);
+ if (NULL == rss->recovery)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (rss->is);
+ return;
+ }
+}
+
+
+/**
+ * Task to run the abort routine on the given @a cls object
+ * after the stack has fully unwound.
+ *
+ * @param cls a `struct ANASTASIS_Recovery *`
+ */
+static void
+delayed_abort (void *cls)
+{
+ struct ANASTASIS_Recovery *recovery = cls;
+
+ ANASTASIS_recovery_abort (recovery);
+}
+
+
+/**
+ * Free the state of a "recover secret" CMD, and possibly
+ * cancel it if it did not complete.
+ *
+ * @param cls closure
+ * @param cmd command being freed.
+ */
+static void
+recover_secret_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct RecoverSecretState *rss = cls;
+
+ if (NULL != rss->recovery)
+ {
+ /* must run first, or at least before #core_secret_cb */
+ (void) GNUNET_SCHEDULER_add_with_priority (
+ GNUNET_SCHEDULER_PRIORITY_SHUTDOWN,
+ &delayed_abort,
+ rss->recovery);
+ rss->recovery = NULL;
+ }
+ if (NULL != rss->recovery_task)
+ {
+ GNUNET_SCHEDULER_cancel (rss->recovery_task);
+ rss->recovery_task = NULL;
+ }
+ json_decref (rss->id_data);
+ GNUNET_free (rss);
+}
+
+
+/**
+ * Offer internal data to other commands.
+ *
+ * @param cls closure
+ * @param ret[out] result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to extract.
+ * @return #GNUNET_OK on success
+ */
+static int
+recover_secret_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct RecoverSecretState *rss = cls;
+
+ if (NULL == rss->ri)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (index >= rss->ri->cs_len)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ {
+ struct TALER_TESTING_Trait traits[] = {
+ ANASTASIS_TESTING_make_trait_challenge (index,
+ rss->ri->cs[index]),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+ }
+}
+
+
+/**
+ * Function called on timeout of the secret finishing operation.
+ *
+ * @param cls a `struct RecoverSecretState *`
+ */
+static void
+recovery_fail (void *cls)
+{
+ struct RecoverSecretState *rss = cls;
+
+ rss->recovery_task = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Timeout during secret recovery\n");
+ TALER_TESTING_interpreter_fail (rss->is);
+}
+
+
+/**
+ * Wait @a delay for @a cmd to finish secret recovery.
+ *
+ * @param cmd command to wait on
+ * @param delay how long to wait at most
+ */
+static void
+recover_secret_finish (struct TALER_TESTING_Command *cmd,
+ struct GNUNET_TIME_Relative delay)
+{
+ struct RecoverSecretState *rss = cmd->cls;
+
+ GNUNET_assert (&recover_secret_run == cmd->run);
+ GNUNET_assert (NULL == rss->recovery_task);
+ switch (rss->recovered)
+ {
+ case GNUNET_OK:
+ TALER_TESTING_interpreter_next (rss->is);
+ break;
+ case GNUNET_NO:
+ rss->recovery_task = GNUNET_SCHEDULER_add_delayed (delay,
+ &recovery_fail,
+ rss);
+ break;
+ case GNUNET_SYSERR:
+ TALER_TESTING_interpreter_fail (rss->is);
+ break;
+ }
+}
+
+
+struct TALER_TESTING_Command
+ANASTASIS_TESTING_cmd_recover_secret (
+ const char *label,
+ const char *anastasis_url,
+ const json_t *id_data,
+ unsigned int version,
+ enum ANASTASIS_TESTING_RecoverSecretOption rso,
+ const char *download_ref,
+ const char *core_secret_ref)
+{
+ struct RecoverSecretState *rss;
+
+ rss = GNUNET_new (struct RecoverSecretState);
+ rss->version = version;
+ rss->id_data = json_incref ((json_t *) id_data);
+ rss->rsopt = rso;
+ rss->anastasis_url = anastasis_url;
+ rss->download_reference = download_ref;
+ rss->core_secret_reference = core_secret_ref;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = rss,
+ .label = label,
+ .run = &recover_secret_run,
+ .cleanup = &recover_secret_cleanup,
+ .traits = &recover_secret_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/**
+ * State for a "recover secret finish" CMD.
+ */
+struct RecoverSecretFinishState
+{
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * URL of the anastasis backend.
+ */
+ const char *recover_label;
+
+ /**
+ * Timeout.
+ */
+ struct GNUNET_TIME_Relative timeout;
+
+};
+
+
+/**
+ * Run a "recover secret finish" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command currently being run.
+ * @param is interpreter state.
+ */
+static void
+recover_secret_finish_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct RecoverSecretFinishState *rsfs = cls;
+ struct TALER_TESTING_Command *ref;
+
+ rsfs->is = is;
+ ref = (struct TALER_TESTING_Command *)
+ TALER_TESTING_interpreter_lookup_command (is,
+ rsfs->recover_label);
+ if (NULL == ref)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (rsfs->is);
+ return;
+ }
+ recover_secret_finish (ref,
+ rsfs->timeout);
+}
+
+
+/**
+ * Free the state of a "recover secret finish" CMD, and possibly
+ * cancel it if it did not complete.
+ *
+ * @param cls closure
+ * @param cmd command being freed.
+ */
+static void
+recover_secret_finish_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct RecoverSecretFinishState *rsfs = cls;
+
+ GNUNET_free (rsfs);
+}
+
+
+struct TALER_TESTING_Command
+ANASTASIS_TESTING_cmd_recover_secret_finish (
+ const char *label,
+ const char *recover_label,
+ struct GNUNET_TIME_Relative timeout)
+{
+ struct RecoverSecretFinishState *rsfs;
+
+ rsfs = GNUNET_new (struct RecoverSecretFinishState);
+ rsfs->recover_label = recover_label;
+ rsfs->timeout = timeout;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = rsfs,
+ .label = label,
+ .run = &recover_secret_finish_run,
+ .cleanup = &recover_secret_finish_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_cmd_recover_secret.c */
diff --git a/src/testing/testing_cmd_secret_share.c b/src/testing/testing_cmd_secret_share.c
new file mode 100644
index 0000000..b80006e
--- /dev/null
+++ b/src/testing/testing_cmd_secret_share.c
@@ -0,0 +1,441 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/testing_cmd_secret_share.c
+ * @brief command to execute the anastasis secret share service
+ * @author Christian Grothoff
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ */
+
+#include "platform.h"
+#include "anastasis_testing_lib.h"
+#include <taler/taler_util.h>
+#include <taler/taler_testing_lib.h>
+#include <taler/taler_merchant_service.h>
+
+
+/**
+ * State for a "secret share" CMD.
+ */
+struct SecretShareState
+{
+ /**
+ * Claim token we got back, if any. Otherwise all zeros.
+ */
+ struct TALER_ClaimTokenP token;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Label of this command.
+ */
+ const char *label;
+
+ /**
+ * References to commands of previous policy creations.
+ */
+ const char **cmd_label_array;
+
+ /**
+ * Data to derive user identifier from.
+ */
+ json_t *id_data;
+
+ /**
+ * The core secret to backup/recover.
+ */
+ const void *core_secret;
+
+ /**
+ * URL of the anastasis backend.
+ */
+ const char *anastasis_url;
+
+ /**
+ * URL of a /config command for the @e anastasis_url.
+ */
+ const char *config_ref;
+
+ /**
+ * The /truth GET operation handle.
+ */
+ struct ANASTASIS_SecretShare *sso;
+
+ /**
+ * Reference to previous secret share command we expect to lookup.
+ */
+ const char *prev_secret_share;
+
+ /**
+ * closure for the payment callback
+ */
+ void *spc_cls;
+
+ /**
+ * closure for the result callback
+ */
+ void *src_cls;
+
+ /**
+ * Payment order ID we got back, if any. Otherwise NULL.
+ */
+ char *payment_order_id;
+
+ /**
+ * Size of core_secret.
+ */
+ size_t core_secret_size;
+
+ /**
+ * Length of array of command labels (cmd_label_array).
+ */
+ unsigned int cmd_label_array_length;
+
+ /**
+ * Expected status code.
+ */
+ enum ANASTASIS_ShareStatus want_status;
+
+ /**
+ * Options for how we are supposed to do the upload.
+ */
+ enum ANASTASIS_TESTING_SecretShareOption ssopt;
+};
+
+
+/**
+ * Function called with the results of a #ANASTASIS_secret_share().
+ *
+ * @param cls closure
+ * @param sr result from the operation
+ */
+static void
+secret_share_result_cb (void *cls,
+ const struct ANASTASIS_ShareResult *sr)
+{
+ struct SecretShareState *sss = cls;
+
+ sss->sso = NULL;
+ if (sr->ss != sss->want_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u to command %s in %s:%u\n",
+ sr->ss,
+ sss->is->commands[sss->is->ip].label,
+ __FILE__,
+ __LINE__);
+ TALER_TESTING_interpreter_fail (sss->is);
+ return;
+ }
+ switch (sr->ss)
+ {
+ case ANASTASIS_SHARE_STATUS_SUCCESS:
+ break;
+ case ANASTASIS_SHARE_STATUS_PAYMENT_REQUIRED:
+ {
+ struct TALER_MERCHANT_PayUriData pd;
+
+ GNUNET_assert (0 < sr->details.payment_required.payment_requests_length);
+ if (GNUNET_OK !=
+ TALER_MERCHANT_parse_pay_uri (
+ sr->details.payment_required.payment_requests[0].payment_request_url,
+ &pd))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (sss->is);
+ return;
+ }
+ sss->payment_order_id = GNUNET_strdup (pd.order_id);
+ TALER_MERCHANT_parse_pay_uri_free (&pd);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order ID from Anastasis service is `%s'\n",
+ sss->payment_order_id);
+ }
+ case ANASTASIS_SHARE_STATUS_PROVIDER_FAILED:
+ break;
+ }
+ TALER_TESTING_interpreter_next (sss->is);
+}
+
+
+/**
+ * Run a "secret share" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command currently being run.
+ * @param is interpreter state.
+ */
+static void
+secret_share_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct SecretShareState *sss = cls;
+ const struct ANASTASIS_Policy *policies[sss->cmd_label_array_length];
+ struct ANASTASIS_ProviderDetails pds;
+
+ GNUNET_assert (sss->cmd_label_array_length > 0);
+ GNUNET_assert (NULL != sss->cmd_label_array);
+ sss->is = is;
+ if (NULL != sss->cmd_label_array)
+ {
+ for (unsigned int i = 0; i < sss->cmd_label_array_length; i++)
+ {
+ const struct TALER_TESTING_Command *ref;
+ const struct ANASTASIS_Policy *policy;
+
+ ref = TALER_TESTING_interpreter_lookup_command (is,
+ sss->cmd_label_array[i]);
+ if (NULL == ref)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (sss->is);
+ return;
+ }
+ if (GNUNET_OK !=
+ ANASTASIS_TESTING_get_trait_policy (ref,
+ 0,
+ &policy))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (sss->is);
+ return;
+ }
+ GNUNET_assert (NULL != policy);
+ policies[i] = policy;
+ }
+ }
+
+ if (NULL != sss->prev_secret_share)
+ {
+ const struct TALER_TESTING_Command *ref;
+ const char *order_id;
+
+ ref = TALER_TESTING_interpreter_lookup_command (is,
+ sss->prev_secret_share);
+ if (NULL == ref)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (sss->is);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_order_id (ref,
+ 0,
+ &order_id))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (sss->is);
+ return;
+ }
+ sss->payment_order_id = (char *) order_id;
+
+ if (NULL == sss->payment_order_id)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (sss->is);
+ return;
+ }
+ }
+
+ memset (&pds,
+ 0,
+ sizeof (pds));
+ if (NULL != sss->payment_order_id)
+ {
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (
+ sss->payment_order_id,
+ strlen (sss->payment_order_id),
+ &pds.payment_secret,
+ sizeof (struct ANASTASIS_PaymentSecretP)))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (sss->is);
+ GNUNET_free (sss->payment_order_id);
+ return;
+ }
+ GNUNET_free (sss->payment_order_id);
+ }
+ pds.provider_url = sss->anastasis_url;
+ {
+ const struct TALER_TESTING_Command *ref;
+ const struct ANASTASIS_CRYPTO_ProviderSaltP *salt;
+
+ ref = TALER_TESTING_interpreter_lookup_command (is,
+ sss->config_ref);
+ if (NULL == ref)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (sss->is);
+ return;
+ }
+ if (GNUNET_OK !=
+ ANASTASIS_TESTING_get_trait_salt (ref,
+ 0,
+ &salt))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (sss->is);
+ return;
+ }
+ pds.provider_salt = *salt;
+ }
+
+ sss->sso = ANASTASIS_secret_share (is->ctx,
+ sss->id_data,
+ &pds,
+ 1,
+ policies,
+ sss->cmd_label_array_length,
+ false,
+ GNUNET_TIME_UNIT_ZERO,
+ &secret_share_result_cb,
+ sss,
+ "test-case",
+ sss->core_secret,
+ sss->core_secret_size);
+ if (NULL == sss->sso)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (sss->is);
+ return;
+ }
+}
+
+
+/**
+ * Free the state of a "secret share" CMD, and possibly
+ * cancel it if it did not complete.
+ *
+ * @param cls closure.
+ * @param cmd command being freed.
+ */
+static void
+secret_share_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct SecretShareState *sss = cls;
+
+ if (NULL != sss->cmd_label_array)
+ {
+ GNUNET_free (sss->cmd_label_array);
+ }
+ if (NULL != sss->sso)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Command '%s' did not complete\n",
+ cmd->label);
+ ANASTASIS_secret_share_cancel (sss->sso);
+ sss->sso = NULL;
+ }
+ json_decref (sss->id_data);
+ GNUNET_free (sss);
+}
+
+
+/**
+ * Offer internal data to other commands.
+ *
+ * @param cls closure
+ * @param ret[out] result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to extract.
+ * @return #GNUNET_OK on success
+ */
+static int
+secret_share_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct SecretShareState *sss = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_claim_token (0,
+ &sss->token),
+ ANASTASIS_TESTING_make_trait_core_secret (0,
+ sss->core_secret),
+ TALER_TESTING_make_trait_order_id (0,
+ sss->payment_order_id),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+struct TALER_TESTING_Command
+ANASTASIS_TESTING_cmd_secret_share (
+ const char *label,
+ const char *anastasis_url,
+ const char *config_ref,
+ const char *prev_secret_share,
+ const json_t *id_data,
+ const void *core_secret,
+ size_t core_secret_size,
+ enum ANASTASIS_ShareStatus want_status,
+ enum ANASTASIS_TESTING_SecretShareOption sso,
+ ...)
+{
+ struct SecretShareState *sss;
+
+ sss = GNUNET_new (struct SecretShareState);
+ sss->want_status = want_status;
+ sss->ssopt = sso;
+ sss->anastasis_url = anastasis_url;
+ sss->config_ref = config_ref;
+ sss->label = label;
+ sss->id_data = json_incref ((json_t *) id_data);
+ sss->core_secret = core_secret;
+ sss->core_secret_size = core_secret_size;
+ sss->prev_secret_share = prev_secret_share;
+
+ {
+ const char *policy_create_cmd;
+ va_list ap;
+
+ va_start (ap,
+ sso);
+ while (NULL != (policy_create_cmd = va_arg (ap, const char *)))
+ {
+ GNUNET_array_append (sss->cmd_label_array,
+ sss->cmd_label_array_length,
+ policy_create_cmd);
+ }
+ va_end (ap);
+ }
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = sss,
+ .label = label,
+ .run = &secret_share_run,
+ .cleanup = &secret_share_cleanup,
+ .traits = &secret_share_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_cmd_secret_share.c */
diff --git a/src/testing/testing_cmd_truth_upload.c b/src/testing/testing_cmd_truth_upload.c
new file mode 100644
index 0000000..f9149d5
--- /dev/null
+++ b/src/testing/testing_cmd_truth_upload.c
@@ -0,0 +1,383 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/testing_cmd_truth_upload.c
+ * @brief command to execute the anastasis secret share service
+ * @author Christian Grothoff
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ */
+
+#include "platform.h"
+#include "anastasis_testing_lib.h"
+#include <taler/taler_util.h>
+#include <taler/taler_testing_lib.h>
+
+
+/**
+ * State for a "truth upload" CMD.
+ */
+struct TruthUploadState
+{
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * URL of the anastasis backend.
+ */
+ const char *anastasis_url;
+
+ /**
+ * Label of this command.
+ */
+ const char *label;
+
+ /**
+ * The ID data to generate user identifier
+ */
+ json_t *id_data;
+
+ /**
+ * The escrow method
+ */
+ const char *method;
+
+ /**
+ * Instructions to be returned to client/user
+ * (e.g. "Look at your smartphone. SMS was sent to you")
+ */
+ const char *instructions;
+
+ /**
+ * Mime type of truth_data (eg. jpeg, string etc.)
+ */
+ const char *mime_type;
+
+ /**
+ * The truth_data (e.g. hash of answer to a secure question)
+ */
+ void *truth_data;
+
+ /**
+ * Requested order ID for this upload (if unpaid).
+ */
+ struct ANASTASIS_PaymentSecretP payment_secret_response;
+
+ /**
+ * Size of truth_data
+ */
+ size_t truth_data_size;
+
+ /**
+ * Expected status code.
+ */
+ unsigned int http_status;
+
+ /**
+ * The /truth POST operation handle.
+ */
+ struct ANASTASIS_TruthUpload *tuo;
+
+ /**
+ * closure for the payment callback
+ */
+ void *tpc_cls;
+
+ /**
+ * Reference to salt download.
+ */
+ const char *salt_reference;
+
+ /**
+ * Options for how we are supposed to do the upload.
+ */
+ enum ANASTASIS_TESTING_TruthStoreOption tsopt;
+
+ /**
+ * Truth object
+ */
+ struct ANASTASIS_Truth *truth;
+};
+
+
+/**
+ * Upload information
+ * caller MUST free 't' using ANASTASIS_truth_free()
+ *
+ * @param cls closure for callback
+ * @param t Truth object (contains provider url and truth public key)
+ * @param ud upload details, useful to continue in case of errors, NULL on success
+ */
+static void
+truth_upload_cb (void *cls,
+ struct ANASTASIS_Truth *t,
+ const struct ANASTASIS_UploadDetails *ud)
+{
+ struct TruthUploadState *tus = cls;
+
+ tus->tuo = NULL;
+ if (NULL == ud)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (tus->is);
+ return;
+ }
+ if (ud->http_status != tus->http_status)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (tus->is);
+ return;
+ }
+ if (MHD_HTTP_PAYMENT_REQUIRED == ud->http_status)
+ {
+ if (ANASTASIS_US_PAYMENT_REQUIRED != ud->us)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (tus->is);
+ return;
+ }
+ tus->payment_secret_response = ud->details.payment.ps;
+ TALER_TESTING_interpreter_next (tus->is);
+ return;
+ }
+ if ( (ANASTASIS_US_SUCCESS == ud->us) &&
+ (NULL == t) )
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_next (tus->is);
+ return;
+ }
+ tus->truth = t;
+ TALER_TESTING_interpreter_next (tus->is);
+}
+
+
+/**
+ * Run a "truth upload" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command currently being run.
+ * @param is interpreter state.
+ */
+static void
+truth_upload_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct TruthUploadState *tus = cls;
+ const struct TALER_TESTING_Command *ref;
+ const struct ANASTASIS_CRYPTO_ProviderSaltP *salt;
+ struct ANASTASIS_CRYPTO_UserIdentifierP user_id;
+
+ tus->is = is;
+ if (NULL != tus->salt_reference)
+ {
+ ref = TALER_TESTING_interpreter_lookup_command
+ (is,
+ tus->salt_reference);
+ if (NULL == ref)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (tus->is);
+ return;
+ }
+ if (GNUNET_OK !=
+ ANASTASIS_TESTING_get_trait_salt (ref,
+ 0,
+ &salt))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (tus->is);
+ return;
+ }
+ }
+
+ ANASTASIS_CRYPTO_user_identifier_derive (tus->id_data,
+ salt,
+ &user_id);
+
+ tus->tuo = ANASTASIS_truth_upload (is->ctx,
+ &user_id,
+ tus->anastasis_url,
+ tus->method,
+ tus->instructions,
+ tus->mime_type,
+ salt,
+ tus->truth_data,
+ tus->truth_data_size,
+ false, /* force payment */
+ GNUNET_TIME_UNIT_ZERO,
+ &truth_upload_cb,
+ tus);
+ if (NULL == tus->tuo)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (tus->is);
+ }
+}
+
+
+/**
+ * Free the state of a "truth upload" CMD, and possibly
+ * cancel it if it did not complete.
+ *
+ * @param cls closure.
+ * @param cmd command being freed.
+ */
+static void
+truth_upload_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct TruthUploadState *tus = cls;
+
+ if (NULL != tus->tuo)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Command '%s' did not complete\n",
+ cmd->label);
+ ANASTASIS_truth_upload_cancel (tus->tuo);
+ tus->tuo = NULL;
+ }
+ if (NULL != tus->id_data)
+ {
+ json_decref (tus->id_data);
+ tus->id_data = NULL;
+ }
+ if (NULL != tus->truth)
+ {
+ ANASTASIS_truth_free (tus->truth);
+ tus->truth = NULL;
+ }
+ GNUNET_free (tus->truth_data);
+ GNUNET_free (tus);
+}
+
+
+/**
+ * Offer internal data to other commands.
+ *
+ * @param cls closure
+ * @param ret[out] result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to extract.
+ * @return #GNUNET_OK on success
+ */
+static int
+truth_upload_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct TruthUploadState *tus = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ ANASTASIS_TESTING_make_trait_truth (0,
+ tus->truth),
+ ANASTASIS_TESTING_make_trait_payment_secret (0,
+ &tus->payment_secret_response),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+json_t *
+ANASTASIS_TESTING_make_id_data_example (const char *id_data)
+{
+ json_t *id;
+
+ id = json_pack ("{s:s}",
+ "id_data", id_data);
+ GNUNET_assert (NULL != id);
+ return id;
+}
+
+
+struct TALER_TESTING_Command
+ANASTASIS_TESTING_cmd_truth_upload (
+ const char *label,
+ const char *anastasis_url,
+ const json_t *id_data,
+ const char *method,
+ const char *instructions,
+ const char *mime_type,
+ const void *truth_data,
+ size_t truth_data_size,
+ unsigned int http_status,
+ enum ANASTASIS_TESTING_TruthStoreOption tso,
+ const char *salt_ref)
+{
+ struct TruthUploadState *tus;
+
+ tus = GNUNET_new (struct TruthUploadState);
+ tus->label = label;
+ tus->http_status = http_status;
+ tus->tsopt = tso;
+ tus->anastasis_url = anastasis_url;
+ tus->salt_reference = salt_ref;
+ tus->id_data = json_incref ((json_t *) id_data);
+ tus->method = method;
+ tus->instructions = instructions;
+ tus->mime_type = mime_type;
+ tus->truth_data_size = truth_data_size;
+ tus->truth_data = GNUNET_memdup (truth_data,
+ truth_data_size);
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = tus,
+ .label = label,
+ .run = &truth_upload_run,
+ .cleanup = &truth_upload_cleanup,
+ .traits = &truth_upload_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+struct TALER_TESTING_Command
+ANASTASIS_TESTING_cmd_truth_upload_question (
+ const char *label,
+ const char *anastasis_url,
+ const json_t *id_data,
+ const char *instructions,
+ const char *mime_type,
+ const void *answer,
+ unsigned int http_status,
+ enum ANASTASIS_TESTING_TruthStoreOption tso,
+ const char *salt_ref)
+{
+ return ANASTASIS_TESTING_cmd_truth_upload (label,
+ anastasis_url,
+ id_data,
+ "question",
+ instructions,
+ mime_type,
+ answer,
+ strlen (answer),
+ http_status,
+ tso,
+ salt_ref);
+}
+
+
+/* end of testing_cmd_truth_upload.c */
diff --git a/src/testing/testing_trait_challenge.c b/src/testing/testing_trait_challenge.c
new file mode 100644
index 0000000..5c40d8e
--- /dev/null
+++ b/src/testing/testing_trait_challenge.c
@@ -0,0 +1,72 @@
+/*
+ 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
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/testing_trait_challenge.c
+ * @brief traits to offer a challenge
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ */
+#include "platform.h"
+#include "anastasis_testing_lib.h"
+
+#define ANASTASIS_TESTING_TRAIT_CHALLENGE "anastasis-challenge"
+
+/**
+ * Obtain a challenge from @a cmd.
+ *
+ * @param cmd command to extract the challenge from.
+ * @param index the index of the challenge
+ * @param c[out] set to the challenge coming from @a cmd.
+ * @return #GNUNET_OK on success.
+ */
+int
+ANASTASIS_TESTING_get_trait_challenge (const struct TALER_TESTING_Command *cmd,
+ unsigned int index,
+ const struct ANASTASIS_Challenge **c)
+{
+ return cmd->traits (cmd->cls,
+ (const void **) c,
+ ANASTASIS_TESTING_TRAIT_CHALLENGE,
+ index);
+}
+
+
+/**
+ * Offer a challenge.
+ *
+ * @param index the challenge index number.
+ * @param c the challenge to offer.
+ * @return #GNUNET_OK on success.
+ */
+struct TALER_TESTING_Trait
+ANASTASIS_TESTING_make_trait_challenge
+ (unsigned int index,
+ const struct ANASTASIS_Challenge *c)
+{
+ struct TALER_TESTING_Trait ret = {
+ .index = index,
+ .trait_name = ANASTASIS_TESTING_TRAIT_CHALLENGE,
+ .ptr = (const void *) c
+ };
+ return ret;
+}
+
+
+/* end of testing_trait_challenge.c */
diff --git a/src/testing/testing_trait_core_secret.c b/src/testing/testing_trait_core_secret.c
new file mode 100644
index 0000000..100a249
--- /dev/null
+++ b/src/testing/testing_trait_core_secret.c
@@ -0,0 +1,74 @@
+/*
+ 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
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/testing_trait_core_secret.c
+ * @brief traits to offer the core secret
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ */
+#include "platform.h"
+#include "anastasis_testing_lib.h"
+
+#define ANASTASIS_TESTING_TRAIT_CORE_SECRET "anastasis-core-secret"
+
+
+/**
+ * Obtain the core secret from @a cmd.
+ *
+ * @param cmd command to extract the core secret from.
+ * @param index the index of the core secret (usually 0)
+ * @param s[out] set to the core secret coming from @a cmd.
+ * @return #GNUNET_OK on success.
+ */
+int
+ANASTASIS_TESTING_get_trait_core_secret (const struct
+ TALER_TESTING_Command *cmd,
+ unsigned int index,
+ const void **s)
+{
+ return cmd->traits (cmd->cls,
+ s,
+ ANASTASIS_TESTING_TRAIT_CORE_SECRET,
+ index);
+}
+
+
+/**
+ * Offer the core secret.
+ *
+ * @param index the core secret's index number (usually 0).
+ * @param s the core secret to offer.
+ * @return #GNUNET_OK on success.
+ */
+struct TALER_TESTING_Trait
+ANASTASIS_TESTING_make_trait_core_secret
+ (unsigned int index,
+ const void *s)
+{
+ struct TALER_TESTING_Trait ret = {
+ .index = index,
+ .trait_name = ANASTASIS_TESTING_TRAIT_CORE_SECRET,
+ .ptr = s
+ };
+ return ret;
+}
+
+
+/* end of testing_trait_core_secret.c */
diff --git a/src/testing/testing_trait_policy.c b/src/testing/testing_trait_policy.c
new file mode 100644
index 0000000..45e773c
--- /dev/null
+++ b/src/testing/testing_trait_policy.c
@@ -0,0 +1,73 @@
+/*
+ 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
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/testing_trait_policy.c
+ * @brief traits to offer a policy
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ */
+#include "platform.h"
+#include "anastasis_testing_lib.h"
+
+#define ANASTASIS_TESTING_TRAIT_POLICY "anastasis-policy"
+
+
+/**
+ * Obtain a policy from @a cmd.
+ *
+ * @param cmd command to extract the policy from.
+ * @param index the index of the policy
+ * @param t[out] set to the policy coming from @a cmd.
+ * @return #GNUNET_OK on success.
+ */
+int
+ANASTASIS_TESTING_get_trait_policy (const struct TALER_TESTING_Command *cmd,
+ unsigned int index,
+ const struct ANASTASIS_Policy **p)
+{
+ return cmd->traits (cmd->cls,
+ (const void **) p,
+ ANASTASIS_TESTING_TRAIT_POLICY,
+ index);
+}
+
+
+/**
+ * Offer a policy.
+ *
+ * @param index the policy's index number.
+ * @param t the policy to offer.
+ * @return #GNUNET_OK on success.
+ */
+struct TALER_TESTING_Trait
+ANASTASIS_TESTING_make_trait_policy
+ (unsigned int index,
+ const struct ANASTASIS_Policy *p)
+{
+ struct TALER_TESTING_Trait ret = {
+ .index = index,
+ .trait_name = ANASTASIS_TESTING_TRAIT_POLICY,
+ .ptr = (const void *) p
+ };
+ return ret;
+}
+
+
+/* end of testing_trait_policy.c */
diff --git a/src/testing/testing_trait_truth.c b/src/testing/testing_trait_truth.c
new file mode 100644
index 0000000..51696e1
--- /dev/null
+++ b/src/testing/testing_trait_truth.c
@@ -0,0 +1,73 @@
+/*
+ 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
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/testing_trait_truth.c
+ * @brief traits to offer a truth
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ */
+#include "platform.h"
+#include "anastasis_testing_lib.h"
+
+#define ANASTASIS_TESTING_TRAIT_TRUTH "anastasis-truth"
+
+
+/**
+ * Obtain a truth from @a cmd.
+ *
+ * @param cmd command to extract the truth from.
+ * @param index the index of the truth
+ * @param t[out] set to the truth coming from @a cmd.
+ * @return #GNUNET_OK on success.
+ */
+int
+ANASTASIS_TESTING_get_trait_truth (const struct TALER_TESTING_Command *cmd,
+ unsigned int index,
+ const struct ANASTASIS_Truth **t)
+{
+ return cmd->traits (cmd->cls,
+ (const void **) t,
+ ANASTASIS_TESTING_TRAIT_TRUTH,
+ index);
+}
+
+
+/**
+ * Offer a truth.
+ *
+ * @param index the truth's index number.
+ * @param t the truth to offer.
+ * @return #GNUNET_OK on success.
+ */
+struct TALER_TESTING_Trait
+ANASTASIS_TESTING_make_trait_truth
+ (unsigned int index,
+ const struct ANASTASIS_Truth *t)
+{
+ struct TALER_TESTING_Trait ret = {
+ .index = index,
+ .trait_name = ANASTASIS_TESTING_TRAIT_TRUTH,
+ .ptr = (const void *) t
+ };
+ return ret;
+}
+
+
+/* end of testing_trait_truth.c */
diff --git a/src/util/Makefile.am b/src/util/Makefile.am
new file mode 100644
index 0000000..657ec0c
--- /dev/null
+++ b/src/util/Makefile.am
@@ -0,0 +1,57 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include $(LIBGCRYPT_CFLAGS)
+
+if USE_COVERAGE
+ AM_CFLAGS = --coverage -O0
+ XLIB = -lgcov
+endif
+
+pkgcfgdir = $(prefix)/share/anastasis/config.d/
+
+pkgcfg_DATA = \
+ paths.conf
+
+EXTRA_DIST = \
+ anastasis-config.in \
+ $(pkgcfg_DATA)
+
+edit_script = $(SED) -e 's,%libdir%,$(libdir),'g $(NULL)
+
+anastasis-config: anastasis-config.in
+ rm -f $@ $@.tmp && \
+ $(edit_script) $< >$@.tmp && \
+ chmod a-w+x $@.tmp && \
+ mv $@.tmp $@
+
+bin_SCRIPTS = \
+ anastasis-config
+
+lib_LTLIBRARIES = \
+ libanastasisutil.la
+
+libanastasisutil_la_SOURCES = \
+ anastasis_crypto.c \
+ os_installation.c
+libanastasisutil_la_LIBADD = \
+ -lgnunetutil \
+ $(LIBGCRYPT_LIBS) \
+ -ljansson \
+ -ltalerutil \
+ $(XLIB)
+libanastasisutil_la_LDFLAGS = \
+ -version-info 0:0:0 \
+ -no-undefined
+
+check_PROGRAMS = \
+ test_anastasis_crypto
+
+TESTS = \
+ $(check_PROGRAMS)
+
+test_anastasis_crypto_SOURCES = \
+ test_anastasis_crypto.c
+test_anastasis_crypto_LDADD = \
+ -lgnunetutil \
+ -ltalerutil \
+ libanastasisutil.la \
+ $(XLIB)
diff --git a/src/util/anastasis-config.in b/src/util/anastasis-config.in
new file mode 100644
index 0000000..0e94921
--- /dev/null
+++ b/src/util/anastasis-config.in
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+
+set -eu
+
+if ! type gnunet-config >/dev/null; then
+ echo "$0 needs gnunet-config to be installed"
+ exit 1
+fi
+
+GC=`which gnunet-config`
+export LD_PRELOAD=${LD_PRELOAD:-}:%libdir%/libanastasisutil.so
+exec gnunet-config "$@"
diff --git a/src/util/anastasis_crypto.c b/src/util/anastasis_crypto.c
new file mode 100644
index 0000000..ace0162
--- /dev/null
+++ b/src/util/anastasis_crypto.c
@@ -0,0 +1,637 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis 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
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/anastasis_crypto.c
+ * @brief anastasis crypto api
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ */
+
+#include "platform.h"
+#include "anastasis_crypto_lib.h"
+#include <gcrypt.h>
+#include <taler/taler_json_lib.h>
+#include <gnunet/gnunet_util_lib.h>
+#include <string.h>
+
+#if defined(DEBUG) || defined(DEBUG2)
+#define SCRYPT_ITERATION 1
+
+#else
+#define SCRYPT_ITERATION 1000
+#endif
+
+
+void
+ANASTASIS_hash_answer (uint64_t code,
+ struct GNUNET_HashCode *hashed_code)
+{
+ char cbuf[40];
+
+ GNUNET_snprintf (cbuf,
+ sizeof (cbuf),
+ "%llu",
+ (unsigned long long) code);
+ GNUNET_CRYPTO_hash (cbuf,
+ strlen (cbuf),
+ hashed_code);
+}
+
+
+void
+ANASTASIS_CRYPTO_secure_answer_hash (
+ const char *answer,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid,
+ const struct ANASTASIS_CRYPTO_QuestionSaltP *salt,
+ struct GNUNET_HashCode *result)
+{
+ struct GNUNET_HashCode pow;
+
+ GNUNET_CRYPTO_pow_hash (&salt->pow_salt,
+ answer,
+ strlen (answer),
+ &pow);
+ GNUNET_assert (GNUNET_YES ==
+ GNUNET_CRYPTO_kdf (
+ result,
+ sizeof (*result),
+ "Anastasis-secure-question-uuid-salting",
+ strlen ("Anastasis-secure-question-uuid-salting"),
+ &pow,
+ sizeof (pow),
+ uuid,
+ sizeof (*uuid),
+ NULL,
+ 0));
+}
+
+
+/**
+ * Compute @a key and @a iv.
+ *
+ * @param key_material key for calculation
+ * @param key_m_len length of key
+ * @param nonce nonce for calculation
+ * @param salt salt value for calculation
+ * @param[out] key where to write the en-/description key
+ * @param[out] iv where to write the IV
+ */
+static void
+get_iv_key (const void *key_material,
+ size_t key_m_len,
+ const struct ANASTASIS_CRYPTO_NonceP *nonce,
+ const char *salt,
+ const struct ANASTASIS_CRYPTO_SymKeyP *key,
+ struct ANASTASIS_CRYPTO_IvP *iv)
+{
+ char res[sizeof (struct ANASTASIS_CRYPTO_SymKeyP)
+ + sizeof (struct ANASTASIS_CRYPTO_IvP)];
+
+ if (GNUNET_YES !=
+ GNUNET_CRYPTO_hkdf (res,
+ sizeof (res),
+ GCRY_MD_SHA512,
+ GCRY_MD_SHA256,
+ key_material,
+ key_m_len,
+ nonce,
+ sizeof (struct ANASTASIS_CRYPTO_NonceP),
+ salt,
+ strlen (salt),
+ NULL,
+ 0))
+ {
+ GNUNET_break (0);
+ return;
+ }
+ memcpy ((void *) key,
+ res,
+ sizeof (*key));
+ memcpy (iv,
+ &res[sizeof (*key)],
+ sizeof (*iv));
+}
+
+
+/**
+ * Encryption of data like recovery document etc.
+ *
+ * @param nonce value to use for the nonce
+ * @param key key which is used to derive a key/iv pair from
+ * @param key_len length of key
+ * @param data data to encrypt
+ * @param data_size size of the data
+ * @param salt salt value which is used for key derivation
+ * @param res[out] ciphertext output
+ * @param res_size[out] size of the ciphertext
+ */
+static void
+anastasis_encrypt (const struct ANASTASIS_CRYPTO_NonceP *nonce,
+ const void *key,
+ size_t key_len,
+ const void *data,
+ size_t data_size,
+ const char *salt,
+ void **res,
+ size_t *res_size)
+{
+ struct ANASTASIS_CRYPTO_NonceP *nonceptr;
+ gcry_cipher_hd_t cipher;
+ struct ANASTASIS_CRYPTO_SymKeyP sym_key;
+ struct ANASTASIS_CRYPTO_IvP iv;
+ int rc;
+ struct ANASTASIS_CRYPTO_AesTagP *tag;
+ char *ciphertext;
+
+ *res_size = data_size
+ + sizeof (struct ANASTASIS_CRYPTO_NonceP)
+ + sizeof (struct ANASTASIS_CRYPTO_AesTagP);
+ if (*res_size <= data_size)
+ {
+ GNUNET_break (0);
+ return;
+ }
+ *res = GNUNET_malloc (*res_size);
+ if (*res_size != data_size
+ + sizeof (struct ANASTASIS_CRYPTO_NonceP)
+ + sizeof (struct ANASTASIS_CRYPTO_AesTagP))
+ {
+ GNUNET_break (0);
+ return;
+ }
+ nonceptr = (struct ANASTASIS_CRYPTO_NonceP *) *res;
+ tag = (struct ANASTASIS_CRYPTO_AesTagP *) &nonceptr[1];
+ ciphertext = (char *) &tag[1];
+ memcpy (nonceptr,
+ nonce,
+ sizeof (*nonce));
+ get_iv_key (key,
+ key_len,
+ nonce,
+ salt,
+ &sym_key,
+ &iv);
+ GNUNET_assert (0 ==
+ gcry_cipher_open (&cipher,
+ GCRY_CIPHER_AES256,
+ GCRY_CIPHER_MODE_GCM,
+ 0));
+ rc = gcry_cipher_setkey (cipher,
+ &sym_key,
+ sizeof (sym_key));
+ GNUNET_assert ((0 == rc) || ((char) rc == GPG_ERR_WEAK_KEY));
+ rc = gcry_cipher_setiv (cipher,
+ &iv,
+ sizeof (iv));
+ GNUNET_assert ((0 == rc) || ((char) rc == GPG_ERR_WEAK_KEY));
+
+ GNUNET_assert (0 ==
+ gcry_cipher_encrypt (cipher,
+ ciphertext,
+ data_size,
+ data,
+ data_size));
+ GNUNET_assert (0 ==
+ gcry_cipher_gettag (cipher,
+ tag,
+ sizeof (struct ANASTASIS_CRYPTO_AesTagP)));
+ gcry_cipher_close (cipher);
+}
+
+
+/**
+ * Decryption of data like encrypted recovery document etc.
+ *
+ * @param key key which is used to derive a key/iv pair from
+ * @param key_len length of key
+ * @param data data to decrypt
+ * @param data_size size of the data
+ * @param salt salt value which is used for key derivation
+ * @param res[out] plaintext output
+ * @param res_size[out] size of the plaintext
+ */
+static void
+anastasis_decrypt (const void *key,
+ size_t key_len,
+ const void *data,
+ size_t data_size,
+ const char *salt,
+ void **res,
+ size_t *res_size)
+{
+ const struct ANASTASIS_CRYPTO_NonceP *nonce;
+ gcry_cipher_hd_t cipher;
+ const struct ANASTASIS_CRYPTO_SymKeyP sym_key;
+ struct ANASTASIS_CRYPTO_IvP iv;
+ int rc;
+ const struct ANASTASIS_CRYPTO_AesTagP *tag;
+ const char *ciphertext;
+
+ *res_size = data_size
+ - sizeof (struct ANASTASIS_CRYPTO_NonceP)
+ - sizeof (struct ANASTASIS_CRYPTO_AesTagP);
+ if (*res_size >= data_size)
+ {
+ GNUNET_break (0);
+ return;
+ }
+ *res = GNUNET_malloc (*res_size);
+ if (*res_size != data_size
+ - sizeof (struct ANASTASIS_CRYPTO_NonceP)
+ - sizeof (struct ANASTASIS_CRYPTO_AesTagP))
+ {
+ GNUNET_break (0);
+ GNUNET_free (*res);
+ return;
+ }
+
+ nonce = (const struct ANASTASIS_CRYPTO_NonceP *) data;
+ tag = (struct ANASTASIS_CRYPTO_AesTagP *) &nonce[1];
+ ciphertext = (const char *) &tag[1];
+ get_iv_key (key,
+ key_len,
+ nonce,
+ salt,
+ &sym_key,
+ &iv);
+ GNUNET_assert (0 ==
+ gcry_cipher_open (&cipher,
+ GCRY_CIPHER_AES256,
+ GCRY_CIPHER_MODE_GCM,
+ 0));
+ rc = gcry_cipher_setkey (cipher,
+ &sym_key,
+ sizeof (sym_key));
+ GNUNET_assert ((0 == rc) || ((char) rc == GPG_ERR_WEAK_KEY));
+
+ rc = gcry_cipher_setiv (cipher,
+ &iv,
+ sizeof (iv));
+ GNUNET_assert ((0 == rc) || ((char) rc == GPG_ERR_WEAK_KEY));
+
+ GNUNET_assert (0 == gcry_cipher_decrypt (cipher,
+ *res,
+ *res_size,
+ ciphertext,
+ *res_size));
+ if (0 !=
+ gcry_cipher_checktag (cipher,
+ tag,
+ sizeof (struct ANASTASIS_CRYPTO_AesTagP)))
+ {
+ GNUNET_break (0);
+ GNUNET_free (*res);
+ return;
+ }
+ gcry_cipher_close (cipher);
+}
+
+
+void
+ANASTASIS_CRYPTO_user_identifier_derive (
+ const json_t *id_data,
+ const struct ANASTASIS_CRYPTO_ProviderSaltP *server_salt,
+ struct ANASTASIS_CRYPTO_UserIdentifierP *id)
+{
+ char *json_enc;
+ struct GNUNET_HashCode hash;
+
+ json_enc = json_dumps (id_data,
+ JSON_COMPACT | JSON_SORT_KEYS);
+ GNUNET_assert (NULL != json_enc);
+ GNUNET_CRYPTO_pow_hash (&server_salt->salt,
+ json_enc,
+ strlen (json_enc),
+ &hash);
+ id->hash = hash;
+ free (json_enc);
+}
+
+
+void
+ANASTASIS_CRYPTO_account_private_key_derive (
+ const struct ANASTASIS_CRYPTO_UserIdentifierP *id,
+ struct ANASTASIS_CRYPTO_AccountPrivateKeyP *priv_key)
+{
+ /* priv_key = ver_secret */
+ if (GNUNET_YES !=
+ GNUNET_CRYPTO_hkdf (&priv_key->priv,
+ sizeof (priv_key->priv),
+ GCRY_MD_SHA512,
+ GCRY_MD_SHA256,
+ id,
+ sizeof (struct ANASTASIS_CRYPTO_UserIdentifierP),
+ "ver",
+ strlen ("ver"),
+ NULL,
+ 0))
+ {
+ GNUNET_break (0);
+ return;
+ }
+ /* go from ver_secret to proper private key (eddsa_d_to_a() in spec) */
+ priv_key->priv.d[0] = (priv_key->priv.d[0] & 0x7f) | 0x40;
+ priv_key->priv.d[31] &= 0xf8;
+}
+
+
+void
+ANASTASIS_CRYPTO_account_public_key_derive (
+ const struct ANASTASIS_CRYPTO_UserIdentifierP *id,
+ struct ANASTASIS_CRYPTO_AccountPublicKeyP *pub_key)
+{
+ struct ANASTASIS_CRYPTO_AccountPrivateKeyP priv;
+
+ ANASTASIS_CRYPTO_account_private_key_derive (id,
+ &priv);
+ GNUNET_CRYPTO_eddsa_key_get_public (&priv.priv,
+ &pub_key->pub);
+}
+
+
+void
+ANASTASIS_CRYPTO_recovery_document_encrypt (
+ const struct ANASTASIS_CRYPTO_UserIdentifierP *id,
+ const void *rec_doc,
+ size_t rd_size,
+ void **enc_rec_doc,
+ size_t *erd_size)
+{
+ const char *salt = "erd";
+ struct ANASTASIS_CRYPTO_NonceP nonce;
+
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ &nonce,
+ sizeof (nonce));
+ anastasis_encrypt (&nonce,
+ id,
+ sizeof (struct ANASTASIS_CRYPTO_UserIdentifierP),
+ rec_doc,
+ rd_size,
+ salt,
+ enc_rec_doc,
+ erd_size);
+}
+
+
+void
+ANASTASIS_CRYPTO_recovery_document_decrypt (
+ const struct ANASTASIS_CRYPTO_UserIdentifierP *id,
+ const void *enc_rec_doc,
+ size_t erd_size,
+ void **rec_doc,
+ size_t *rd_size)
+{
+ const char *salt = "erd";
+
+ anastasis_decrypt (id,
+ sizeof (struct ANASTASIS_CRYPTO_UserIdentifierP),
+ enc_rec_doc,
+ erd_size,
+ salt,
+ rec_doc,
+ rd_size);
+}
+
+
+void
+ANASTASIS_CRYPTO_keyshare_encrypt (
+ const struct ANASTASIS_CRYPTO_KeyShareP *key_share,
+ const struct ANASTASIS_CRYPTO_UserIdentifierP *id,
+ const char *xsalt,
+ struct ANASTASIS_CRYPTO_EncryptedKeyShareP *enc_key_share)
+{
+ const char *salt = "eks";
+ size_t eks_size = 0;
+ void *eks = NULL;
+ struct ANASTASIS_CRYPTO_NonceP nonce;
+
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ &nonce,
+ sizeof (nonce));
+ anastasis_encrypt (&nonce,
+ id,
+ sizeof (struct ANASTASIS_CRYPTO_UserIdentifierP),
+ key_share,
+ sizeof (struct ANASTASIS_CRYPTO_KeyShareP),
+ (NULL == xsalt) ? salt : xsalt,
+ &eks,
+ &eks_size);
+ GNUNET_assert (eks_size ==
+ sizeof (struct ANASTASIS_CRYPTO_EncryptedKeyShareP));
+ memcpy (enc_key_share,
+ eks,
+ sizeof (struct ANASTASIS_CRYPTO_EncryptedKeyShareP));
+ GNUNET_free (eks);
+}
+
+
+void
+ANASTASIS_CRYPTO_keyshare_decrypt (
+ const struct ANASTASIS_CRYPTO_EncryptedKeyShareP *enc_key_share,
+ const struct ANASTASIS_CRYPTO_UserIdentifierP *id,
+ const char *xsalt,
+ struct ANASTASIS_CRYPTO_KeyShareP *key_share)
+{
+ const char *salt = "eks";
+ size_t ks_size = 0;
+ void *ks = NULL;
+
+ anastasis_decrypt (id,
+ sizeof (struct ANASTASIS_CRYPTO_UserIdentifierP),
+ enc_key_share,
+ sizeof (struct ANASTASIS_CRYPTO_EncryptedKeyShareP),
+ (NULL == xsalt) ? salt : xsalt,
+ &ks,
+ &ks_size);
+ GNUNET_assert (ks_size ==
+ sizeof (struct ANASTASIS_CRYPTO_KeyShareP));
+ memcpy (key_share,
+ ks,
+ sizeof (struct ANASTASIS_CRYPTO_KeyShareP));
+ GNUNET_free (ks);
+}
+
+
+void
+ANASTASIS_CRYPTO_truth_encrypt (
+ const struct ANASTASIS_CRYPTO_NonceP *nonce,
+ const struct ANASTASIS_CRYPTO_TruthKeyP *truth_enc_key,
+ const void *truth,
+ size_t truth_size,
+ void **enc_truth,
+ size_t *ect_size)
+{
+ const char *salt = "ect";
+
+ anastasis_encrypt (nonce,
+ truth_enc_key,
+ sizeof (struct ANASTASIS_CRYPTO_TruthKeyP),
+ truth,
+ truth_size,
+ salt,
+ enc_truth,
+ ect_size);
+}
+
+
+void
+ANASTASIS_CRYPTO_truth_decrypt (
+ const struct ANASTASIS_CRYPTO_TruthKeyP *truth_enc_key,
+ const void *enc_truth,
+ size_t ect_size,
+ void **truth,
+ size_t *truth_size)
+{
+ const char *salt = "ect";
+
+ anastasis_decrypt (truth_enc_key,
+ sizeof (struct ANASTASIS_CRYPTO_TruthKeyP),
+ enc_truth,
+ ect_size,
+ salt,
+ truth,
+ truth_size);
+}
+
+
+void
+ANASTASIS_CRYPTO_keyshare_create (
+ struct ANASTASIS_CRYPTO_KeyShareP *key_share)
+{
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG,
+ key_share,
+ sizeof (struct ANASTASIS_CRYPTO_KeyShareP));
+}
+
+
+void
+ANASTASIS_CRYPTO_policy_key_derive (
+ const struct ANASTASIS_CRYPTO_KeyShareP *key_shares,
+ unsigned int keyshare_length,
+ const struct ANASTASIS_CRYPTO_MasterSaltP *salt,
+ struct ANASTASIS_CRYPTO_PolicyKeyP *policy_key)
+{
+ GNUNET_CRYPTO_hkdf (policy_key,
+ sizeof (*policy_key),
+ GCRY_MD_SHA512,
+ GCRY_MD_SHA256,
+ key_shares,
+ keyshare_length * sizeof (*key_shares),
+ salt,
+ sizeof (*salt),
+ NULL, 0);
+}
+
+
+void
+ANASTASIS_CRYPTO_core_secret_encrypt (
+ const struct ANASTASIS_CRYPTO_PolicyKeyP *policy_keys,
+ unsigned int policy_keys_length,
+ const void *core_secret,
+ size_t core_secret_size,
+ void **enc_core_secret,
+ struct ANASTASIS_CRYPTO_EncryptedMasterKeyP *encrypted_master_keys)
+{
+ struct GNUNET_CRYPTO_SymmetricSessionKey sk;
+ struct GNUNET_CRYPTO_SymmetricInitializationVector iv;
+ struct GNUNET_HashCode master_key;
+
+ *enc_core_secret = GNUNET_malloc (core_secret_size);
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG,
+ &master_key,
+ sizeof (struct GNUNET_HashCode));
+ GNUNET_CRYPTO_hash_to_aes_key (&master_key,
+ &sk,
+ &iv);
+ GNUNET_assert (GNUNET_SYSERR !=
+ GNUNET_CRYPTO_symmetric_encrypt (core_secret,
+ core_secret_size,
+ &sk,
+ &iv,
+ *enc_core_secret));
+ for (unsigned int i = 0; i < policy_keys_length; i++)
+ {
+ struct GNUNET_CRYPTO_SymmetricSessionKey i_sk;
+ struct GNUNET_CRYPTO_SymmetricInitializationVector i_iv;
+ struct GNUNET_HashCode key = policy_keys[i].key;
+
+ GNUNET_CRYPTO_hash_to_aes_key (&key,
+ &i_sk,
+ &i_iv);
+ GNUNET_assert (
+ GNUNET_SYSERR !=
+ GNUNET_CRYPTO_symmetric_encrypt (&master_key,
+ sizeof (struct GNUNET_HashCode),
+ &i_sk,
+ &i_iv,
+ &encrypted_master_keys[i]));
+ }
+}
+
+
+void
+ANASTASIS_CRYPTO_core_secret_recover (
+ const struct ANASTASIS_CRYPTO_EncryptedMasterKeyP *encrypted_master_key,
+ const struct ANASTASIS_CRYPTO_PolicyKeyP *policy_key,
+ const void *encrypted_core_secret,
+ size_t encrypted_core_secret_size,
+ void **core_secret,
+ size_t *core_secret_size)
+{
+ struct GNUNET_CRYPTO_SymmetricSessionKey mk_sk;
+ struct GNUNET_CRYPTO_SymmetricInitializationVector mk_iv;
+ struct GNUNET_CRYPTO_SymmetricSessionKey core_sk;
+ struct GNUNET_CRYPTO_SymmetricInitializationVector core_iv;
+ struct GNUNET_HashCode master_key;
+ struct GNUNET_HashCode key = policy_key->key;
+
+ *core_secret = GNUNET_malloc (encrypted_core_secret_size);
+ GNUNET_CRYPTO_hash_to_aes_key (&key,
+ &mk_sk,
+ &mk_iv);
+ GNUNET_assert (
+ GNUNET_SYSERR !=
+ GNUNET_CRYPTO_symmetric_decrypt (
+ encrypted_master_key,
+ sizeof (struct ANASTASIS_CRYPTO_EncryptedMasterKeyP),
+ &mk_sk,
+ &mk_iv,
+ &master_key));
+ GNUNET_CRYPTO_hash_to_aes_key (&master_key,
+ &core_sk,
+ &core_iv);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "At %s:%d encrypted core secret is %s-%llu b\n", __FILE__,
+ __LINE__,
+ TALER_b2s (encrypted_core_secret, encrypted_core_secret_size),
+ (unsigned long long) encrypted_core_secret_size);
+ *core_secret_size = GNUNET_CRYPTO_symmetric_decrypt (encrypted_core_secret,
+ encrypted_core_secret_size,
+ &core_sk,
+ &core_iv,
+ *core_secret);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "At %s:%d decrypted core secret is %s-%llu b\n", __FILE__,
+ __LINE__,
+ TALER_b2s (*core_secret, *core_secret_size),
+ (unsigned long long) *core_secret_size);
+ GNUNET_assert (GNUNET_SYSERR != *core_secret_size);
+}
+
+
+/* end of anastasis_crypto.c */
diff --git a/src/util/os_installation.c b/src/util/os_installation.c
new file mode 100644
index 0000000..d9608d3
--- /dev/null
+++ b/src/util/os_installation.c
@@ -0,0 +1,71 @@
+/*
+ This file is part of GNU Anastasis.
+ Copyright (C) 2019 Taler Systems SA
+
+ Anastasis 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.
+
+ Anastasis 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 Anastasis; see the file COPYING. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+/**
+ * @file os_installation.c
+ * @brief initialize libgnunet OS subsystem for Anastasis.
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+
+
+/**
+ * Default project data used for installation path detection
+ * for GNU Anastasis.
+ */
+static const struct GNUNET_OS_ProjectData anastasis_pd = {
+ .libname = "libanastasisutil",
+ .project_dirname = "anastasis",
+ .binary_name = "anastasis-httpd",
+ .env_varname = "ANASTASIS_PREFIX",
+ .base_config_varname = "ANASTASIS_BASE_CONFIG",
+ .bug_email = "contact@anastasis.lu",
+ .homepage = "https://anastasis.lu/",
+ .config_file = "anastasis.conf",
+ .user_config_file = "~/.config/anastasis.conf",
+ .version = PACKAGE_VERSION,
+ .is_gnu = 0,
+ .gettext_domain = "anastasis",
+ .gettext_path = NULL,
+};
+
+
+/**
+ * Return default project data used by Anastasis.
+ */
+const struct GNUNET_OS_ProjectData *
+ANASTASIS_project_data_default (void)
+{
+ return &anastasis_pd;
+}
+
+
+/**
+ * Initialize libanastasisutil.
+ */
+void __attribute__ ((constructor))
+ANASTASIS_OS_init ()
+{
+ GNUNET_OS_init (&anastasis_pd);
+}
+
+
+/* end of os_installation.c */
diff --git a/src/util/paths.conf b/src/util/paths.conf
new file mode 100644
index 0000000..c62a24a
--- /dev/null
+++ b/src/util/paths.conf
@@ -0,0 +1,29 @@
+# This file is in the public domain.
+#
+[PATHS]
+# The PATHS section is special, as filenames including $-expression are
+# expanded using the values from PATHS or the system environment (PATHS
+# is checked first). Anastasis also supports expanding $-expressions using
+# defaults with the syntax "${VAR:-default}". Here, "default" can again
+# be a $-expression.
+#
+# We usually want $HOME for $ANASTASIS_HOME, but we allow testcases to
+# easily override this by setting $ANASTASIS_TEST_HOME.
+#
+ANASTASIS_HOME = ${ANASTASIS_TEST_HOME:-${HOME:-${USERPROFILE}}}
+
+# see XDG Base Directory Specification at
+# http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
+# for how these should be used.
+
+# Persistent data storage
+ANASTASIS_DATA_HOME = ${XDG_DATA_HOME:-$ANASTASIS_HOME/.local/share}/anastasis/
+
+# Configuration files
+ANASTASIS_CONFIG_HOME = ${XDG_CONFIG_HOME:-$ANASTASIS_HOME/.config}/anastasis/
+
+# Cached data, no big deal if lost
+ANASTASIS_CACHE_HOME = ${XDG_CACHE_HOME:-$ANASTASIS_HOME/.cache}/anastasis/
+
+# Runtime data (always lost on system boot)
+ANASTASIS_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/anastasis-system-runtime/
diff --git a/src/util/test_anastasis_crypto.c b/src/util/test_anastasis_crypto.c
new file mode 100644
index 0000000..9a6a98c
--- /dev/null
+++ b/src/util/test_anastasis_crypto.c
@@ -0,0 +1,346 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-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
+ <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file lib/test_anastasis_api.c
+ * @brief testcase to test anastasis' HTTP API interface
+ * @author Christian Grothoff
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ */
+#include "platform.h"
+#include <taler/taler_util.h>
+#include <gnunet/gnunet_util_lib.h>
+#include "anastasis_crypto_lib.h"
+
+/**
+ * Testing derivation of the user identifier
+ */
+static int
+test_user_identifier_derive (void)
+{
+ json_t *id_data_1;
+ json_t *id_data_2;
+ json_t *id_data_3;
+ struct ANASTASIS_CRYPTO_UserIdentifierP id_1;
+ struct ANASTASIS_CRYPTO_UserIdentifierP id_2;
+ struct ANASTASIS_CRYPTO_UserIdentifierP id_3;
+ struct ANASTASIS_CRYPTO_ProviderSaltP server_salt;
+
+ char *salt_str = "Server-Salt-Test";
+
+ GNUNET_memcpy (&server_salt,
+ salt_str,
+ strlen (salt_str));
+ // sample data 1
+ id_data_1 = json_object ();
+ json_object_set_new (id_data_1, "arg1", json_string ("Hallo"));
+ // sample data 2, equal to sample data 1
+ id_data_2 = json_object ();
+ json_object_set_new (id_data_2, "arg1", json_string ("Hallo"));
+ // sample data 3, differs
+ id_data_3 = json_object ();
+ json_object_set_new (id_data_3, "arg1", json_string ("Hallo2"));
+
+ ANASTASIS_CRYPTO_user_identifier_derive (id_data_1,
+ &server_salt,
+ &id_1);
+ ANASTASIS_CRYPTO_user_identifier_derive (id_data_2,
+ &server_salt,
+ &id_2);
+ ANASTASIS_CRYPTO_user_identifier_derive (id_data_3,
+ &server_salt,
+ &id_3);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "UserIdentifier_1: %s\n",
+ TALER_B2S (&id_1));
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "UserIdentifier_2: %s\n",
+ TALER_B2S (&id_2));
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "UserIdentifier_3: %s\n",
+ TALER_B2S (&id_3));
+ GNUNET_assert (0 == GNUNET_memcmp (&id_1, &id_2));
+ GNUNET_assert (0 != GNUNET_memcmp (&id_1, &id_3));
+ json_decref (id_data_1);
+ json_decref (id_data_2);
+ json_decref (id_data_3);
+ return 0;
+}
+
+
+/**
+ * Testing the encryption of an recovery document and the
+ * decryption of the encrypted recovery document
+ */
+static int
+test_recovery_document (void)
+{
+ void *ciphertext;
+ size_t size_ciphertext;
+ void *plaintext;
+ size_t size_plaintext;
+ struct ANASTASIS_CRYPTO_UserIdentifierP id;
+ struct ANASTASIS_CRYPTO_ProviderSaltP server_salt;
+ int ret;
+
+ json_t *id_data = json_object ();
+ const char *test = "TEST_ERD";
+ char *salt_str = "Server-Salt-Test";
+
+ GNUNET_memcpy (&server_salt,
+ salt_str,
+ strlen (salt_str));
+ json_object_set_new (id_data, "arg1", json_string ("ID_DATA"));
+ ANASTASIS_CRYPTO_user_identifier_derive (id_data,
+ &server_salt,
+ &id);
+ ANASTASIS_CRYPTO_recovery_document_encrypt (&id,
+ test,
+ strlen (test),
+ &ciphertext,
+ &size_ciphertext);
+
+ ANASTASIS_CRYPTO_recovery_document_decrypt (&id,
+ ciphertext,
+ size_ciphertext,
+ &plaintext,
+ &size_plaintext);
+ GNUNET_assert (strlen (test) == size_plaintext);
+ ret = strncmp (plaintext, test, strlen (test));
+ json_decref (id_data);
+ GNUNET_free (ciphertext);
+ GNUNET_free (plaintext);
+ return ret;
+}
+
+
+static int
+test_key_share (void)
+{
+ struct ANASTASIS_CRYPTO_EncryptedKeyShareP ciphertext;
+ struct ANASTASIS_CRYPTO_KeyShareP plaintext;
+ struct ANASTASIS_CRYPTO_UserIdentifierP id;
+ struct ANASTASIS_CRYPTO_KeyShareP key_share;
+ struct ANASTASIS_CRYPTO_KeyShareP key_share_1;
+ struct ANASTASIS_CRYPTO_KeyShareP key_share_2;
+
+ // testing creation of keyshares
+ ANASTASIS_CRYPTO_keyshare_create (&key_share_1);
+ ANASTASIS_CRYPTO_keyshare_create (&key_share_2);
+ GNUNET_assert (0 !=
+ GNUNET_memcmp (&key_share_1,
+ &key_share_2));
+
+ // testing of enc-/decryption of a keyshare
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ &id,
+ sizeof (struct ANASTASIS_CRYPTO_UserIdentifierP));
+ ANASTASIS_CRYPTO_keyshare_create (&key_share);
+ ANASTASIS_CRYPTO_keyshare_encrypt (&key_share,
+ &id,
+ NULL,
+ &ciphertext);
+ ANASTASIS_CRYPTO_keyshare_decrypt (&ciphertext,
+ &id,
+ NULL,
+ &plaintext);
+ return GNUNET_memcmp (&key_share,
+ &plaintext);
+}
+
+
+static int
+test_truth (void)
+{
+ const char *test = "TEST_TRUTH";
+ void *ciphertext;
+ size_t size_ciphertext;
+ void *plaintext;
+ size_t size_plaintext;
+ struct ANASTASIS_CRYPTO_TruthKeyP truth_enc_key;
+ int ret;
+ struct ANASTASIS_CRYPTO_NonceP nonce;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "TRUTH_BEFORE: %s\n",
+ TALER_b2s (test,
+ strlen (test)));
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ &truth_enc_key,
+ sizeof (struct ANASTASIS_CRYPTO_TruthKeyP));
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ &nonce,
+ sizeof (nonce));
+ ANASTASIS_CRYPTO_truth_encrypt (&nonce,
+ &truth_enc_key,
+ test,
+ strlen (test),
+ &ciphertext,
+ &size_ciphertext);
+
+ ANASTASIS_CRYPTO_truth_decrypt (&truth_enc_key,
+ ciphertext,
+ size_ciphertext,
+ &plaintext,
+ &size_plaintext);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "TRUTH_AFTER: %s\n",
+ TALER_b2s (plaintext, size_plaintext));
+ GNUNET_assert (strlen (test) == size_plaintext);
+ ret = strncmp (plaintext, test, strlen (test));
+ GNUNET_free (ciphertext);
+ GNUNET_free (plaintext);
+ return ret;
+}
+
+
+static int
+test_core_secret (void)
+{
+ const char *test = "TEST_CORE_SECRET";
+ const char *test_wrong = "TEST_CORE_WRONG";
+ void *enc_core_secret;
+ unsigned int policy_keys_length = 5;
+ struct ANASTASIS_CRYPTO_MasterSaltP salt;
+ struct ANASTASIS_CRYPTO_EncryptedMasterKeyP
+ encrypted_master_keys[policy_keys_length];
+
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &salt,
+ sizeof (salt));
+
+ // construction of PolicyKey-array
+ struct ANASTASIS_CRYPTO_PolicyKeyP policy_keys[policy_keys_length];
+ for (unsigned int i = 0; i < policy_keys_length; i++)
+ {
+ // construction of KeyShare-array
+ unsigned int keyshare_length = 5;
+ struct ANASTASIS_CRYPTO_KeyShareP keyshares[keyshare_length];
+ for (unsigned int j = 0; j < keyshare_length; j++)
+ {
+ ANASTASIS_CRYPTO_keyshare_create (&keyshares[j]);
+ if (j > 0)
+ GNUNET_assert (0 !=
+ GNUNET_memcmp (&keyshares[j - 1], &keyshares[j]));
+ }
+
+ // derive policy-keys
+ ANASTASIS_CRYPTO_policy_key_derive ((struct
+ ANASTASIS_CRYPTO_KeyShareP *)
+ keyshares,
+ keyshare_length,
+ &salt,
+ &policy_keys[i]);
+ if (i > 0)
+ GNUNET_assert (0 !=
+ GNUNET_memcmp (&policy_keys[i - 1], &policy_keys[i]));
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "CORE_SECRET_BEFORE: %s\n",
+ TALER_b2s (test, strlen (test)));
+
+ // test encryption of core_secret
+ ANASTASIS_CRYPTO_core_secret_encrypt (policy_keys,
+ policy_keys_length,
+ test,
+ strlen (test),
+ &enc_core_secret,
+ (struct
+ ANASTASIS_CRYPTO_EncryptedMasterKeyP *)
+ &encrypted_master_keys);
+
+ // test recover of core secret
+ for (unsigned int k = 0; k < policy_keys_length; k++)
+ {
+ void *dec_core_secret;
+ size_t core_secret_size;
+
+ ANASTASIS_CRYPTO_core_secret_recover (&encrypted_master_keys[k],
+ &policy_keys[k],
+ enc_core_secret,
+ strlen (test),
+ &dec_core_secret,
+ &core_secret_size);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "CORE_SECRET_AFTER_%i: %s\n",
+ k,
+ TALER_b2s (dec_core_secret, strlen (test)));
+ GNUNET_assert (strlen (test) == core_secret_size);
+ GNUNET_assert (0 ==
+ strncmp (dec_core_secret, test, strlen (test)));
+ GNUNET_assert (0 !=
+ strncmp (dec_core_secret, test_wrong, strlen (
+ test)));
+ GNUNET_free (dec_core_secret);
+ }
+ GNUNET_free (enc_core_secret);
+ return 0;
+}
+
+
+static int
+test_public_key_derive (void)
+{
+ struct ANASTASIS_CRYPTO_UserIdentifierP id;
+ struct ANASTASIS_CRYPTO_AccountPublicKeyP pub_key;
+ struct ANASTASIS_CRYPTO_ProviderSaltP server_salt;
+ json_t *id_data = json_object ();
+ const char *salt_str = "Server-Salt-Test";
+
+ GNUNET_memcpy (&server_salt,
+ salt_str,
+ strlen (salt_str));
+
+ json_object_set_new (id_data, "arg1", json_string ("ID_DATA"));
+ ANASTASIS_CRYPTO_user_identifier_derive (id_data,
+ &server_salt,
+ &id);
+
+ ANASTASIS_CRYPTO_account_public_key_derive (&id,
+ &pub_key);
+ // FIXME: write a real test, e.g. signing and verification
+ json_decref (id_data);
+ return 0;
+}
+
+
+int
+main (int argc,
+ const char *const argv[])
+{
+ GNUNET_log_setup (argv[0], "DEBUG", NULL);
+ if (0 != test_recovery_document ())
+ return 1;
+ if (0 != test_user_identifier_derive ())
+ return 1;
+ if (0 != test_key_share ())
+ return 1;
+ if (0 != test_truth ())
+ return 1;
+ if (0 != test_core_secret ())
+ return 1;
+ if (0 != test_public_key_derive ())
+ return 1;
+ return 0;
+}
+
+
+/* end of test_anastasis_crypto.c */