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 ()
+ };