summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2021-07-30 10:38:27 +0200
committerChristian Grothoff <christian@grothoff.org>2021-07-30 10:38:27 +0200
commit7e669bcf6b6336ec429da949bcb4aa456971dba2 (patch)
treed19912f950d1cac1c38b857b7d5bdaba2289544e /src/lib
downloadanastasis-7e669bcf6b6336ec429da949bcb4aa456971dba2.tar.gz
anastasis-7e669bcf6b6336ec429da949bcb4aa456971dba2.tar.bz2
anastasis-7e669bcf6b6336ec429da949bcb4aa456971dba2.zip
folding history in preparation of GNU Anastasis v0.0.0 release
Diffstat (limited to 'src/lib')
-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
4 files changed, 2432 insertions, 0 deletions
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
new file mode 100644
index 0000000..07460d4
--- /dev/null
+++ b/src/lib/Makefile.am
@@ -0,0 +1,27 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include -I$(top_srcdir)/src/backend -I$(top_srcdir)/src/lib
+
+if USE_COVERAGE
+ AM_CFLAGS = --coverage -O0
+ XLIB = -lgcov
+endif
+
+lib_LTLIBRARIES = \
+ libanastasis.la
+
+libanastasis_la_LDFLAGS = \
+ -version-info 0:0:0 \
+ -no-undefined
+libanastasis_la_SOURCES = \
+ anastasis_backup.c \
+ anastasis_recovery.c
+libanastasis_la_LIBADD = \
+ $(top_builddir)/src/util/libanastasisutil.la \
+ $(top_builddir)/src/restclient/libanastasisrest.la \
+ -ltalerutil \
+ -ltalermerchant \
+ -lgnunetjson \
+ -lgnunetutil \
+ -ljansson \
+ -lz \
+ $(XLIB)
diff --git a/src/lib/anastasis_backup.c b/src/lib/anastasis_backup.c
new file mode 100644
index 0000000..ea55e6a
--- /dev/null
+++ b/src/lib/anastasis_backup.c
@@ -0,0 +1,979 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 2021 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @brief anastasis client api
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ */
+#include "platform.h"
+#include "anastasis.h"
+#include <taler/taler_merchant_service.h>
+#include <zlib.h>
+
+
+struct ANASTASIS_Truth
+{
+ /**
+ * Identification of the truth.
+ */
+ struct ANASTASIS_CRYPTO_TruthUUIDP uuid;
+
+ /**
+ * Keyshare of this truth, used to generate policy keys
+ */
+ struct ANASTASIS_CRYPTO_KeyShareP key_share;
+
+ /**
+ * Nonce used for the symmetric encryption.
+ */
+ struct ANASTASIS_CRYPTO_NonceP nonce;
+
+ /**
+ * Key used to encrypt this truth
+ */
+ struct ANASTASIS_CRYPTO_TruthKeyP truth_key;
+
+ /**
+ * Server salt used to derive user identifier
+ */
+ struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt;
+
+ /**
+ * Server salt used to derive hash from security answer
+ */
+ struct ANASTASIS_CRYPTO_QuestionSaltP salt;
+
+ /**
+ * Url of the server
+ */
+ char *url;
+
+ /**
+ * Method used for this truth
+ */
+ char *type;
+
+ /**
+ * Instructions for the user to recover this truth.
+ */
+ char *instructions;
+
+ /**
+ * Mime type of the truth, NULL if not given.
+ */
+ char *mime_type;
+
+};
+
+
+struct ANASTASIS_Truth *
+ANASTASIS_truth_from_json (const json_t *json)
+{
+ struct ANASTASIS_Truth *t = GNUNET_new (struct ANASTASIS_Truth);
+ const char *url;
+ const char *type;
+ const char *instructions;
+ const char *mime_type = NULL;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("url",
+ &url),
+ GNUNET_JSON_spec_string ("type",
+ &type),
+ GNUNET_JSON_spec_string ("instructions",
+ &instructions),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("mime_type",
+ &mime_type)),
+ GNUNET_JSON_spec_fixed_auto ("uuid",
+ &t->uuid),
+ GNUNET_JSON_spec_fixed_auto ("nonce",
+ &t->nonce),
+ GNUNET_JSON_spec_fixed_auto ("key_share",
+ &t->key_share),
+ GNUNET_JSON_spec_fixed_auto ("truth_key",
+ &t->truth_key),
+ GNUNET_JSON_spec_fixed_auto ("salt",
+ &t->salt),
+ GNUNET_JSON_spec_fixed_auto ("provider_salt",
+ &t->provider_salt),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (t);
+ return NULL;
+ }
+ t->url = GNUNET_strdup (url);
+ t->type = GNUNET_strdup (type);
+ t->instructions = GNUNET_strdup (instructions);
+ if (NULL != mime_type)
+ t->mime_type = GNUNET_strdup (mime_type);
+ return t;
+}
+
+
+json_t *
+ANASTASIS_truth_to_json (const struct ANASTASIS_Truth *t)
+{
+ return json_pack (
+ "{s:o,s:o,s:o,s:o,s:o"
+ ",s:o,s:s,s:s,s:s,s:s?}",
+ "uuid",
+ GNUNET_JSON_from_data_auto (&t->uuid),
+ "key_share",
+ GNUNET_JSON_from_data_auto (&t->key_share),
+ "truth_key",
+ GNUNET_JSON_from_data_auto (&t->truth_key),
+ "salt",
+ GNUNET_JSON_from_data_auto (&t->salt),
+ "nonce",
+ GNUNET_JSON_from_data_auto (&t->nonce),
+ "provider_salt",
+ GNUNET_JSON_from_data_auto (&t->provider_salt),
+ "url",
+ t->url,
+ "type",
+ t->type,
+ "instructions",
+ t->instructions,
+ "mime_type",
+ t->mime_type);
+}
+
+
+struct ANASTASIS_TruthUpload
+{
+
+ /**
+ * User identifier used for the keyshare encryption
+ */
+ struct ANASTASIS_CRYPTO_UserIdentifierP id;
+
+ /**
+ * CURL Context for the Post Request
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * Callback which sends back the generated truth object later used to build the policy
+ */
+ ANASTASIS_TruthCallback tc;
+
+ /**
+ * Closure for the Callback
+ */
+ void *tc_cls;
+
+ /**
+ * Reference to the Truthstore Operation
+ */
+ struct ANASTASIS_TruthStoreOperation *tso;
+
+ /**
+ * The truth we are uploading.
+ */
+ struct ANASTASIS_Truth *t;
+
+};
+
+
+/**
+ * Function called with the result of trying to upload truth.
+ *
+ * @param cls our `struct ANASTASIS_TruthUpload`
+ * @param ud details about the upload result
+ */
+static void
+truth_store_callback (void *cls,
+ const struct ANASTASIS_UploadDetails *ud)
+{
+ struct ANASTASIS_TruthUpload *tu = cls;
+
+ tu->tso = NULL;
+ tu->tc (tu->tc_cls,
+ tu->t,
+ ud);
+ tu->t = NULL;
+ ANASTASIS_truth_upload_cancel (tu);
+}
+
+
+struct ANASTASIS_TruthUpload *
+ANASTASIS_truth_upload3 (struct GNUNET_CURL_Context *ctx,
+ const struct ANASTASIS_CRYPTO_UserIdentifierP *user_id,
+ struct ANASTASIS_Truth *t,
+ const void *truth_data,
+ size_t truth_data_size,
+ uint32_t payment_years_requested,
+ struct GNUNET_TIME_Relative pay_timeout,
+ ANASTASIS_TruthCallback tc,
+ void *tc_cls)
+{
+ struct ANASTASIS_TruthUpload *tu;
+ struct ANASTASIS_CRYPTO_EncryptedKeyShareP encrypted_key_share;
+ struct GNUNET_HashCode nt;
+ void *encrypted_truth;
+ size_t encrypted_truth_size;
+
+ tu = GNUNET_new (struct ANASTASIS_TruthUpload);
+ tu->tc = tc;
+ tu->tc_cls = tc_cls;
+ tu->ctx = ctx;
+ tu->id = *user_id;
+ tu->tc = tc;
+ tu->tc_cls = tc_cls;
+ tu->t = t;
+
+ if (0 == strcmp ("question",
+ t->type))
+ {
+ char *answer;
+
+ answer = GNUNET_strndup (truth_data,
+ truth_data_size);
+ ANASTASIS_CRYPTO_secure_answer_hash (answer,
+ &t->uuid,
+ &t->salt,
+ &nt);
+ ANASTASIS_CRYPTO_keyshare_encrypt (&t->key_share,
+ &tu->id,
+ answer,
+ &encrypted_key_share);
+ GNUNET_free (answer);
+ truth_data = &nt;
+ truth_data_size = sizeof (nt);
+ }
+ else
+ {
+ ANASTASIS_CRYPTO_keyshare_encrypt (&t->key_share,
+ &tu->id,
+ NULL,
+ &encrypted_key_share);
+ }
+ ANASTASIS_CRYPTO_truth_encrypt (&t->nonce,
+ &t->truth_key,
+ truth_data,
+ truth_data_size,
+ &encrypted_truth,
+ &encrypted_truth_size);
+ tu->tso = ANASTASIS_truth_store (tu->ctx,
+ t->url,
+ &t->uuid,
+ t->type,
+ &encrypted_key_share,
+ t->mime_type,
+ encrypted_truth_size,
+ encrypted_truth,
+ payment_years_requested,
+ pay_timeout,
+ &truth_store_callback,
+ tu);
+ GNUNET_free (encrypted_truth);
+ if (NULL == tu->tso)
+ {
+ GNUNET_break (0);
+ ANASTASIS_truth_free (t);
+ ANASTASIS_truth_upload_cancel (tu);
+ return NULL;
+ }
+ return tu;
+}
+
+
+struct ANASTASIS_TruthUpload *
+ANASTASIS_truth_upload2 (
+ struct GNUNET_CURL_Context *ctx,
+ const struct ANASTASIS_CRYPTO_UserIdentifierP *user_id,
+ const char *provider_url,
+ const char *type,
+ const char *instructions,
+ const char *mime_type,
+ const struct ANASTASIS_CRYPTO_ProviderSaltP *provider_salt,
+ const void *truth_data,
+ size_t truth_data_size,
+ uint32_t payment_years_requested,
+ struct GNUNET_TIME_Relative pay_timeout,
+ const struct ANASTASIS_CRYPTO_NonceP *nonce,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid,
+ const struct ANASTASIS_CRYPTO_QuestionSaltP *salt,
+ const struct ANASTASIS_CRYPTO_TruthKeyP *truth_key,
+ const struct ANASTASIS_CRYPTO_KeyShareP *key_share,
+ ANASTASIS_TruthCallback tc,
+ void *tc_cls)
+{
+ struct ANASTASIS_Truth *t;
+
+ t = GNUNET_new (struct ANASTASIS_Truth);
+ t->url = GNUNET_strdup (provider_url);
+ t->type = GNUNET_strdup (type);
+ t->instructions = (NULL != instructions)
+ ? GNUNET_strdup (instructions)
+ : NULL;
+ t->mime_type = (NULL != mime_type)
+ ? GNUNET_strdup (mime_type)
+ : NULL;
+ t->provider_salt = *provider_salt;
+ t->salt = *salt;
+ t->nonce = *nonce;
+ t->uuid = *uuid;
+ t->truth_key = *truth_key;
+ t->key_share = *key_share;
+ return ANASTASIS_truth_upload3 (ctx,
+ user_id,
+ t,
+ truth_data,
+ truth_data_size,
+ payment_years_requested,
+ pay_timeout,
+ tc,
+ tc_cls);
+}
+
+
+struct ANASTASIS_TruthUpload *
+ANASTASIS_truth_upload (
+ struct GNUNET_CURL_Context *ctx,
+ const struct ANASTASIS_CRYPTO_UserIdentifierP *user_id,
+ const char *provider_url,
+ const char *type,
+ const char *instructions,
+ const char *mime_type,
+ const struct ANASTASIS_CRYPTO_ProviderSaltP *provider_salt,
+ const void *truth_data,
+ size_t truth_data_size,
+ uint32_t payment_years_requested,
+ struct GNUNET_TIME_Relative pay_timeout,
+ ANASTASIS_TruthCallback tc,
+ void *tc_cls)
+{
+ struct ANASTASIS_CRYPTO_QuestionSaltP question_salt;
+ struct ANASTASIS_CRYPTO_TruthUUIDP uuid;
+ struct ANASTASIS_CRYPTO_TruthKeyP truth_key;
+ struct ANASTASIS_CRYPTO_KeyShareP key_share;
+ struct ANASTASIS_CRYPTO_NonceP nonce;
+
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ &nonce,
+ sizeof (nonce));
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ &question_salt,
+ sizeof (question_salt));
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ &uuid,
+ sizeof (uuid));
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG,
+ &truth_key,
+ sizeof (truth_key));
+ ANASTASIS_CRYPTO_keyshare_create (&key_share);
+ return ANASTASIS_truth_upload2 (ctx,
+ user_id,
+ provider_url,
+ type,
+ instructions,
+ mime_type,
+ provider_salt,
+ truth_data,
+ truth_data_size,
+ payment_years_requested,
+ pay_timeout,
+ &nonce,
+ &uuid,
+ &question_salt,
+ &truth_key,
+ &key_share,
+ tc,
+ tc_cls);
+}
+
+
+void
+ANASTASIS_truth_upload_cancel (struct ANASTASIS_TruthUpload *tu)
+{
+ if (NULL != tu->tso)
+ {
+ ANASTASIS_truth_store_cancel (tu->tso);
+ tu->tso = NULL;
+ }
+ if (NULL != tu->t)
+ {
+ ANASTASIS_truth_free (tu->t);
+ tu->t = NULL;
+ }
+ GNUNET_free (tu);
+}
+
+
+void
+ANASTASIS_truth_free (struct ANASTASIS_Truth *t)
+{
+ GNUNET_free (t->url);
+ GNUNET_free (t->type);
+ GNUNET_free (t->instructions);
+ GNUNET_free (t->mime_type);
+ GNUNET_free (t);
+}
+
+
+struct ANASTASIS_Policy
+{
+ /**
+ * Encrypted policy master key
+ */
+ struct ANASTASIS_CRYPTO_PolicyKeyP policy_key;
+
+ /**
+ * Salt used to encrypt the master key
+ */
+ struct ANASTASIS_CRYPTO_MasterSaltP salt;
+
+ /**
+ * Array of truths
+ */
+ struct ANASTASIS_Truth **truths;
+
+ /**
+ * Length of @ truths array.
+ */
+ uint32_t truths_length;
+
+};
+
+
+/**
+ * Duplicate truth object.
+ *
+ * @param t object to duplicate
+ * @return copy of @a t
+ */
+static struct ANASTASIS_Truth *
+truth_dup (const struct ANASTASIS_Truth *t)
+{
+ struct ANASTASIS_Truth *d = GNUNET_new (struct ANASTASIS_Truth);
+
+ *d = *t;
+ d->url = GNUNET_strdup (t->url);
+ d->type = GNUNET_strdup (t->type);
+ d->instructions = GNUNET_strdup (t->instructions);
+ if (NULL != t->mime_type)
+ d->mime_type = GNUNET_strdup (t->mime_type);
+ return d;
+}
+
+
+struct ANASTASIS_Policy *
+ANASTASIS_policy_create (const struct ANASTASIS_Truth *truths[],
+ unsigned int truths_len)
+{
+ struct ANASTASIS_Policy *p;
+
+ p = GNUNET_new (struct ANASTASIS_Policy);
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ &p->salt,
+ sizeof (p->salt));
+ {
+ struct ANASTASIS_CRYPTO_KeyShareP key_shares[truths_len];
+
+ for (unsigned int i = 0; i < truths_len; i++)
+ key_shares[i] = truths[i]->key_share;
+ ANASTASIS_CRYPTO_policy_key_derive (key_shares,
+ truths_len,
+ &p->salt,
+ &p->policy_key);
+ }
+ p->truths = GNUNET_new_array (truths_len,
+ struct ANASTASIS_Truth *);
+ for (unsigned int i = 0; i<truths_len; i++)
+ p->truths[i] = truth_dup (truths[i]);
+ p->truths_length = truths_len;
+ return p;
+}
+
+
+void
+ANASTASIS_policy_destroy (struct ANASTASIS_Policy *p)
+{
+ for (unsigned int i = 0; i<p->truths_length; i++)
+ ANASTASIS_truth_free (p->truths[i]);
+ GNUNET_free (p->truths);
+ GNUNET_free (p);
+}
+
+
+/**
+ * State for a "policy store" CMD.
+ */
+struct PolicyStoreState
+{
+ /**
+ * User identifier used as entropy source for the account public key
+ */
+ struct ANASTASIS_CRYPTO_UserIdentifierP id;
+
+ /**
+ * Hash of the current upload. Used to check the server's response.
+ */
+ struct GNUNET_HashCode curr_hash;
+
+ /**
+ * Payment identifier.
+ */
+ struct ANASTASIS_PaymentSecretP payment_secret;
+
+ /**
+ * Server salt. Points into a truth object from which we got the
+ * salt.
+ */
+ struct ANASTASIS_CRYPTO_ProviderSaltP server_salt;
+
+ /**
+ * The /policy POST operation handle.
+ */
+ struct ANASTASIS_PolicyStoreOperation *pso;
+
+ /**
+ * URL of the anastasis backend.
+ */
+ char *anastasis_url;
+
+ /**
+ * Payment request returned by this provider, if any.
+ */
+ char *payment_request;
+
+ /**
+ * reference to SecretShare
+ */
+ struct ANASTASIS_SecretShare *ss;
+
+ /**
+ * Version of the policy created at the provider.
+ */
+ unsigned long long policy_version;
+
+ /**
+ * When will the policy expire at the provider.
+ */
+ struct GNUNET_TIME_Absolute policy_expiration;
+
+};
+
+/**
+* Defines a recovery document upload process (recovery document consists of multiple policies)
+*/
+struct ANASTASIS_SecretShare
+{
+ /**
+ * Closure for the Result Callback
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * Callback which gives back the result of the POST Request
+ */
+ ANASTASIS_ShareResultCallback src;
+
+ /**
+ * Closure for the Result Callback
+ */
+ void *src_cls;
+
+ /**
+ * References for the upload states and operations (size of truths passed)
+ */
+ struct PolicyStoreState *pss;
+
+ /**
+ * Closure for the Result Callback
+ */
+ unsigned int pss_length;
+};
+
+
+/**
+ * Callback to process a POST /policy request
+ *
+ * @param cls closure
+ * @param ec anastasis-specific error code
+ * @param obj the decoded response body
+ */
+static void
+policy_store_cb (void *cls,
+ const struct ANASTASIS_UploadDetails *ud)
+{
+ struct PolicyStoreState *pss = cls;
+ struct ANASTASIS_SecretShare *ss = pss->ss;
+ enum ANASTASIS_UploadStatus us;
+
+ pss->pso = NULL;
+ if (NULL == ud)
+ us = ANASTASIS_US_HTTP_ERROR;
+ else
+ us = ud->us;
+ if ( (ANASTASIS_US_SUCCESS == us) &&
+ (0 != GNUNET_memcmp (&pss->curr_hash,
+ ud->details.success.curr_backup_hash)) )
+ {
+ GNUNET_break_op (0);
+ us = ANASTASIS_US_SERVER_ERROR;
+ }
+ switch (us)
+ {
+ case ANASTASIS_US_SUCCESS:
+ pss->policy_version = ud->details.success.policy_version;
+ pss->policy_expiration = ud->details.success.policy_expiration;
+ break;
+ case ANASTASIS_US_PAYMENT_REQUIRED:
+ pss->payment_request = GNUNET_strdup (ud->details.payment.payment_request);
+ pss->payment_secret = ud->details.payment.ps;
+ break;
+ case ANASTASIS_US_HTTP_ERROR:
+ case ANASTASIS_US_CLIENT_ERROR:
+ case ANASTASIS_US_SERVER_ERROR:
+ {
+ struct ANASTASIS_ShareResult sr = {
+ .ss = ANASTASIS_SHARE_STATUS_PROVIDER_FAILED,
+ .details.provider_failure.provider_url = pss->anastasis_url,
+ .details.provider_failure.http_status = ud->http_status,
+ .details.provider_failure.ec = us,
+ };
+
+ ss->src (ss->src_cls,
+ &sr);
+ ANASTASIS_secret_share_cancel (ss);
+ return;
+ }
+ case ANASTASIS_US_CONFLICTING_TRUTH:
+ GNUNET_break (0);
+ break;
+ }
+ for (unsigned int i = 0; i<ss->pss_length; i++)
+ if (NULL != ss->pss[i].pso)
+ /* some upload is still pending, let's wait for it to finish */
+ return;
+
+ {
+ struct ANASTASIS_SharePaymentRequest spr[GNUNET_NZL (ss->pss_length)];
+ struct ANASTASIS_ProviderSuccessStatus apss[GNUNET_NZL (ss->pss_length)];
+ unsigned int off = 0;
+ unsigned int voff = 0;
+ struct ANASTASIS_ShareResult sr;
+
+ for (unsigned int i = 0; i<ss->pss_length; i++)
+ {
+ struct PolicyStoreState *pssi = &ss->pss[i];
+
+ if (NULL == pssi->payment_request)
+ {
+ apss[voff].policy_version = pssi->policy_version;
+ apss[voff].provider_url = pssi->anastasis_url;
+ apss[voff].policy_expiration = pssi->policy_expiration;
+ voff++;
+ }
+ else
+ {
+ spr[off].payment_request_url = pssi->payment_request;
+ spr[off].provider_url = pssi->anastasis_url;
+ spr[off].payment_secret = pssi->payment_secret;
+ off++;
+ }
+ }
+ if (off > 0)
+ {
+ sr.ss = ANASTASIS_SHARE_STATUS_PAYMENT_REQUIRED;
+ sr.details.payment_required.payment_requests = spr;
+ sr.details.payment_required.payment_requests_length = off;
+ }
+ else
+ {
+ sr.ss = ANASTASIS_SHARE_STATUS_SUCCESS;
+ sr.details.success.pss = apss;
+ sr.details.success.num_providers = voff;
+ }
+ ss->src (ss->src_cls,
+ &sr);
+ }
+ ANASTASIS_secret_share_cancel (ss);
+}
+
+
+struct ANASTASIS_SecretShare *
+ANASTASIS_secret_share (struct GNUNET_CURL_Context *ctx,
+ const json_t *id_data,
+ const struct ANASTASIS_ProviderDetails providers[],
+ unsigned int pss_length,
+ const struct ANASTASIS_Policy *policies[],
+ unsigned int policies_len,
+ uint32_t payment_years_requested,
+ struct GNUNET_TIME_Relative pay_timeout,
+ ANASTASIS_ShareResultCallback src,
+ void *src_cls,
+ const char *secret_name,
+ const void *core_secret,
+ size_t core_secret_size)
+{
+ struct ANASTASIS_SecretShare *ss;
+ struct ANASTASIS_CRYPTO_EncryptedMasterKeyP
+ encrypted_master_keys[GNUNET_NZL (policies_len)];
+ void *encrypted_core_secret;
+ json_t *dec_policies;
+ json_t *esc_methods;
+ size_t recovery_document_size;
+ char *recovery_document_str;
+
+ if (0 == pss_length)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ ss = GNUNET_new (struct ANASTASIS_SecretShare);
+ ss->src = src;
+ ss->src_cls = src_cls;
+ ss->pss = GNUNET_new_array (pss_length,
+ struct PolicyStoreState);
+ ss->pss_length = pss_length;
+ ss->ctx = ctx;
+
+ {
+ struct ANASTASIS_CRYPTO_PolicyKeyP policy_keys[GNUNET_NZL (policies_len)];
+
+ for (unsigned int i = 0; i < policies_len; i++)
+ policy_keys[i] = policies[i]->policy_key;
+ ANASTASIS_CRYPTO_core_secret_encrypt (policy_keys,
+ policies_len,
+ core_secret,
+ core_secret_size,
+ &encrypted_core_secret,
+ encrypted_master_keys);
+ }
+ dec_policies = json_array ();
+ GNUNET_assert (NULL != dec_policies);
+ for (unsigned int k = 0; k < policies_len; k++)
+ {
+ const struct ANASTASIS_Policy *policy = policies[k];
+ json_t *uuids = json_array ();
+
+ GNUNET_assert (NULL != uuids);
+ for (unsigned int b = 0; b < policy->truths_length; b++)
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ uuids,
+ GNUNET_JSON_from_data_auto (
+ &policy->truths[b]->uuid)));
+ if (0 !=
+ json_array_append_new (
+ dec_policies,
+ json_pack ("{s:o, s:o, s:o}",
+ "master_key",
+ GNUNET_JSON_from_data_auto (
+ &encrypted_master_keys[k]),
+ "uuids",
+ uuids,
+ "salt",
+ GNUNET_JSON_from_data_auto (&policy->salt))))
+ {
+ GNUNET_break (0);
+ json_decref (dec_policies);
+ ANASTASIS_secret_share_cancel (ss);
+ return NULL;
+ }
+ }
+
+ esc_methods = json_array ();
+ for (unsigned int k = 0; k < policies_len; k++)
+ {
+ const struct ANASTASIS_Policy *policy = policies[k];
+
+ for (unsigned int l = 0; l < policy->truths_length; l++)
+ {
+ const struct ANASTASIS_Truth *pt = policy->truths[l];
+ bool unique = true;
+
+ /* Only append each truth once */
+ for (unsigned int k2 = 0; k2 < k; k2++)
+ {
+ const struct ANASTASIS_Policy *p2 = policies[k2];
+ for (unsigned int l2 = 0; l2 < p2->truths_length; l2++)
+ if (0 ==
+ GNUNET_memcmp (&pt->uuid,
+ &p2->truths[l2]->uuid))
+ {
+ unique = false;
+ break;
+ }
+ if (! unique)
+ break;
+ }
+ if (! unique)
+ continue;
+
+ if (0 !=
+ json_array_append_new (
+ esc_methods,
+ json_pack ("{s:o," /* truth uuid */
+ " s:s," /* provider url */
+ " s:s," /* instructions */
+ " s:o," /* truth key */
+ " s:o," /* truth salt */
+ " s:o," /* provider salt */
+ " s:s}", /* escrow method */
+ "uuid",
+ GNUNET_JSON_from_data_auto (
+ &pt->uuid),
+ "url",
+ pt->url,
+ "instructions",
+ pt->instructions,
+ "truth_key", GNUNET_JSON_from_data_auto (
+ &pt->truth_key),
+ "salt", GNUNET_JSON_from_data_auto (
+ &pt->salt),
+ "provider_salt", GNUNET_JSON_from_data_auto (
+ &pt->provider_salt),
+ "escrow_type",
+ pt->type)))
+ {
+ GNUNET_break (0);
+ json_decref (esc_methods);
+ json_decref (dec_policies);
+ ANASTASIS_secret_share_cancel (ss);
+ return NULL;
+ }
+ }
+ }
+
+ {
+ json_t *recovery_document;
+ size_t rd_size;
+ char *rd_str;
+ Bytef *cbuf;
+ uLongf cbuf_size;
+ int ret;
+ uint32_t be_size;
+
+ recovery_document = json_pack (
+ "{s:s?, s:o, s:o, s:o}",
+ "secret_name", secret_name,
+ "policies", dec_policies,
+ "escrow_methods", esc_methods,
+ "encrypted_core_secret", GNUNET_JSON_from_data (encrypted_core_secret,
+ core_secret_size));
+ GNUNET_assert (NULL != recovery_document);
+ GNUNET_free (encrypted_core_secret);
+
+ rd_str = json_dumps (recovery_document,
+ JSON_COMPACT | JSON_SORT_KEYS);
+ GNUNET_assert (NULL != rd_str);
+ json_decref (recovery_document);
+ rd_size = strlen (rd_str);
+ cbuf_size = compressBound (rd_size);
+ be_size = htonl ((uint32_t) rd_size);
+ cbuf = GNUNET_malloc (cbuf_size + sizeof (uint32_t));
+ memcpy (cbuf,
+ &be_size,
+ sizeof (uint32_t));
+ ret = compress (cbuf + sizeof (uint32_t),
+ &cbuf_size,
+ (const Bytef *) rd_str,
+ rd_size);
+ if (Z_OK != ret)
+ {
+ /* compression failed!? */
+ GNUNET_break (0);
+ free (rd_str);
+ GNUNET_free (cbuf);
+ ANASTASIS_secret_share_cancel (ss);
+ return NULL;
+ }
+ free (rd_str);
+ recovery_document_size = (size_t) (cbuf_size + sizeof (uint32_t));
+ recovery_document_str = (char *) cbuf;
+ }
+
+ for (unsigned int l = 0; l < ss->pss_length; l++)
+ {
+ struct PolicyStoreState *pss = &ss->pss[l];
+ void *recovery_data;
+ size_t recovery_data_size;
+ struct ANASTASIS_CRYPTO_AccountPrivateKeyP anastasis_priv;
+
+ pss->ss = ss;
+ pss->anastasis_url = GNUNET_strdup (providers[l].provider_url);
+ pss->server_salt = providers[l].provider_salt;
+ pss->payment_secret = providers[l].payment_secret;
+ ANASTASIS_CRYPTO_user_identifier_derive (id_data,
+ &pss->server_salt,
+ &pss->id);
+ ANASTASIS_CRYPTO_account_private_key_derive (&pss->id,
+ &anastasis_priv);
+ ANASTASIS_CRYPTO_recovery_document_encrypt (&pss->id,
+ recovery_document_str,
+ recovery_document_size,
+ &recovery_data,
+ &recovery_data_size);
+ GNUNET_CRYPTO_hash (recovery_data,
+ recovery_data_size,
+ &pss->curr_hash);
+ pss->pso = ANASTASIS_policy_store (
+ ss->ctx,
+ pss->anastasis_url,
+ &anastasis_priv,
+ recovery_data,
+ recovery_data_size,
+ payment_years_requested,
+ (! GNUNET_is_zero (&pss->payment_secret))
+ ? &pss->payment_secret
+ : NULL,
+ pay_timeout,
+ &policy_store_cb,
+ pss);
+ GNUNET_free (recovery_data);
+ if (NULL == pss->pso)
+ {
+ GNUNET_break (0);
+ ANASTASIS_secret_share_cancel (ss);
+ GNUNET_free (recovery_document_str);
+ return NULL;
+ }
+ }
+ GNUNET_free (recovery_document_str);
+ return ss;
+}
+
+
+void
+ANASTASIS_secret_share_cancel (struct ANASTASIS_SecretShare *ss)
+{
+ for (unsigned int i = 0; i<ss->pss_length; i++)
+ {
+ struct PolicyStoreState *pssi = &ss->pss[i];
+
+ if (NULL != pssi->pso)
+ {
+ ANASTASIS_policy_store_cancel (pssi->pso);
+ pssi->pso = NULL;
+ }
+ GNUNET_free (pssi->anastasis_url);
+ GNUNET_free (pssi->payment_request);
+ }
+ GNUNET_free (ss->pss);
+ GNUNET_free (ss);
+}
diff --git a/src/lib/anastasis_recovery.c b/src/lib/anastasis_recovery.c
new file mode 100644
index 0000000..5b0726f
--- /dev/null
+++ b/src/lib/anastasis_recovery.c
@@ -0,0 +1,1425 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 2021 Taler Systems SA
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @brief anastasis client api
+ * @author Christian Grothoff
+ * @author Dominik Meister
+ * @author Dennis Neufeld
+ */
+#include "platform.h"
+#include "anastasis.h"
+#include <taler/taler_json_lib.h>
+#include <gnunet/gnunet_util_lib.h>
+#include <taler/taler_merchant_service.h>
+#include <zlib.h>
+
+
+/**
+ * Challenge struct contains the uuid and public key's needed for the
+ * recovery process and a reference to ANASTASIS_Recovery.
+ */
+struct ANASTASIS_Challenge
+{
+
+ /**
+ * Information exported to clients about this challenge.
+ */
+ struct ANASTASIS_ChallengeDetails ci;
+
+ /**
+ * Key used to encrypt the truth passed to the server
+ */
+ struct ANASTASIS_CRYPTO_TruthKeyP truth_key;
+
+ /**
+ * Salt; used to derive hash from security question answers.
+ */
+ struct ANASTASIS_CRYPTO_QuestionSaltP salt;
+
+ /**
+ * Provider salt; used to derive our key material from our identity
+ * key.
+ */
+ struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt;
+
+ /**
+ * Decrypted key share for this challenge. Set once the
+ * challenge was @e ri.solved.
+ */
+ struct ANASTASIS_CRYPTO_KeyShareP key_share;
+
+ /**
+ * Callback which gives back the instructions and a status code of
+ * the request to the user when answering a challenge was initiated.
+ */
+ ANASTASIS_AnswerFeedback af;
+
+ /**
+ * Closure for the challenge callback
+ */
+ void *af_cls;
+
+ /**
+ * Defines the base URL of the Anastasis provider used for the challenge.
+ */
+ char *url;
+
+ /**
+ * What is the type of this challenge (E-Mail, Security Question, SMS...)
+ */
+ char *type;
+
+ /**
+ * Instructions for solving the challenge (generic, set client-side
+ * when challenge was established).
+ */
+ char *instructions;
+
+ /**
+ * Answer to the security question, if @a type is "question". Otherwise NULL.
+ */
+ char *answer;
+
+ /**
+ * Reference to the recovery process which is ongoing
+ */
+ struct ANASTASIS_Recovery *recovery;
+
+ /**
+ * keyshare lookup operation
+ */
+ struct ANASTASIS_KeyShareLookupOperation *kslo;
+
+};
+
+
+/**
+ * Defines a decryption policy with multiple escrow methods
+ */
+struct DecryptionPolicy
+{
+
+ /**
+ * Publicly visible details about a decryption policy.
+ */
+ struct ANASTASIS_DecryptionPolicy pub_details;
+
+ /**
+ * Encrypted masterkey (encrypted with the policy key).
+ */
+ struct ANASTASIS_CRYPTO_EncryptedMasterKeyP emk;
+
+ /**
+ * Salt used to decrypt master key.
+ */
+ struct ANASTASIS_CRYPTO_MasterSaltP salt;
+
+};
+
+
+/**
+ * stores provider URLs, identity key material, decrypted recovery document (internally!)
+ */
+struct ANASTASIS_Recovery
+{
+
+ /**
+ * Identity key material used for the derivation of keys
+ */
+ struct ANASTASIS_CRYPTO_UserIdentifierP id;
+
+ /**
+ * Recovery information which is given to the user
+ */
+ struct ANASTASIS_RecoveryInformation ri;
+
+ /**
+ * Internal of @e ri.dps_len policies that would allow recovery of the core secret.
+ */
+ struct DecryptionPolicy *dps;
+
+ /**
+ * Array of @e ri.cs_len challenges to be solved (for any of the policies).
+ */
+ struct ANASTASIS_Challenge *cs;
+
+ /**
+ * Identity data to user id from.
+ */
+ json_t *id_data;
+
+ /**
+ * Callback to send back a recovery document with the policies and the version
+ */
+ ANASTASIS_PolicyCallback pc;
+
+ /**
+ * closure for the Policy callback
+ */
+ void *pc_cls;
+
+ /**
+ * Callback to send back the core secret which was saved by
+ * anastasis, after all challenges are completed
+ */
+ ANASTASIS_CoreSecretCallback csc;
+
+ /**
+ * Closure for the core secret callback
+ */
+ void *csc_cls;
+
+ /**
+ * Curl context
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * Reference to the policy lookup operation which is executed
+ */
+ struct ANASTASIS_PolicyLookupOperation *plo;
+
+ /**
+ * Array of challenges that have been solved.
+ * Valid entries up to @e solved_challenge_pos.
+ * Length matches the total number of challenges in @e ri.
+ */
+ struct ANASTASIS_Challenge **solved_challenges;
+
+ /**
+ * Our provider URL.
+ */
+ char *provider_url;
+
+ /**
+ * Name of the secret, can be NULL.
+ */
+ char *secret_name;
+
+ /**
+ * Task to run @e pc asynchronously.
+ */
+ struct GNUNET_SCHEDULER_Task *do_async;
+
+ /**
+ * Retrieved encrypted core secret from policy
+ */
+ void *enc_core_secret;
+
+ /**
+ * Size of the @e enc_core_secret
+ */
+ size_t enc_core_secret_size;
+
+ /**
+ * Current offset in the @e solved_challenges array.
+ */
+ unsigned int solved_challenge_pos;
+
+};
+
+
+/**
+ * Function called with the results of a #ANASTASIS_keyshare_lookup().
+ *
+ * @param cls closure
+ * @param http_status HTTP status of the request
+ * @param ud details about the lookup operation
+ */
+static void
+keyshare_lookup_cb (void *cls,
+ const struct ANASTASIS_KeyShareDownloadDetails *dd)
+{
+ struct ANASTASIS_Challenge *c = cls;
+ struct ANASTASIS_Recovery *recovery = c->recovery;
+ struct ANASTASIS_CRYPTO_UserIdentifierP id;
+ struct DecryptionPolicy *rdps;
+
+ c->kslo = NULL;
+ switch (dd->status)
+ {
+ case ANASTASIS_KSD_SUCCESS:
+ break;
+ case ANASTASIS_KSD_PAYMENT_REQUIRED:
+ {
+ struct ANASTASIS_ChallengeStartResponse csr = {
+ .cs = ANASTASIS_CHALLENGE_STATUS_PAYMENT_REQUIRED,
+ .challenge = c,
+ .details.payment_required.taler_pay_uri
+ = dd->details.payment_required.taler_pay_uri,
+ .details.payment_required.payment_secret
+ = dd->details.payment_required.payment_secret
+ };
+
+ c->af (c->af_cls,
+ &csr);
+ return;
+ }
+ case ANASTASIS_KSD_INVALID_ANSWER:
+ {
+ struct ANASTASIS_ChallengeStartResponse csr = {
+ .cs = ANASTASIS_CHALLENGE_STATUS_INSTRUCTIONS,
+ .challenge = c,
+ .details.open_challenge.body
+ = dd->details.open_challenge.body,
+ .details.open_challenge.content_type
+ = dd->details.open_challenge.content_type,
+ .details.open_challenge.body_size
+ = dd->details.open_challenge.body_size,
+ .details.open_challenge.http_status
+ = dd->details.open_challenge.http_status
+ };
+
+ c->af (c->af_cls,
+ &csr);
+ return;
+ }
+ case ANASTASIS_KSD_REDIRECT_FOR_AUTHENTICATION:
+ {
+ struct ANASTASIS_ChallengeStartResponse csr = {
+ .cs = ANASTASIS_CHALLENGE_STATUS_REDIRECT_FOR_AUTHENTICATION,
+ .challenge = c,
+ .details.redirect_url
+ = dd->details.redirect_url
+ };
+
+ c->af (c->af_cls,
+ &csr);
+ return;
+ }
+ case ANASTASIS_KSD_TRUTH_UNKNOWN:
+ {
+ struct ANASTASIS_ChallengeStartResponse csr = {
+ .cs = ANASTASIS_CHALLENGE_STATUS_TRUTH_UNKNOWN,
+ .challenge = c
+ };
+
+ c->af (c->af_cls,
+ &csr);
+ return;
+ }
+ case ANASTASIS_KSD_RATE_LIMIT_EXCEEDED:
+ {
+ struct ANASTASIS_ChallengeStartResponse csr = {
+ .cs = ANASTASIS_CHALLENGE_STATUS_RATE_LIMIT_EXCEEDED,
+ .challenge = c
+ };
+
+ c->af (c->af_cls,
+ &csr);
+ return;
+ }
+ case ANASTASIS_KSD_SERVER_ERROR:
+ case ANASTASIS_KSD_CLIENT_FAILURE:
+ {
+ struct ANASTASIS_ChallengeStartResponse csr = {
+ .cs = ANASTASIS_CHALLENGE_STATUS_SERVER_FAILURE,
+ .challenge = c,
+ .details.server_failure.ec
+ = dd->details.server_failure.ec,
+ .details.server_failure.http_status
+ = dd->details.server_failure.http_status
+ };
+
+ c->af (c->af_cls,
+ &csr);
+ return;
+ }
+ }
+
+ GNUNET_assert (NULL != dd);
+ ANASTASIS_CRYPTO_user_identifier_derive (recovery->id_data,
+ &c->provider_salt,
+ &id);
+ ANASTASIS_CRYPTO_keyshare_decrypt (&dd->details.eks,
+ &id,
+ c->answer,
+ &c->key_share);
+ recovery->solved_challenges[recovery->solved_challenge_pos++] = c;
+
+ {
+ struct ANASTASIS_ChallengeStartResponse csr = {
+ .cs = ANASTASIS_CHALLENGE_STATUS_SOLVED,
+ .challenge = c
+ };
+
+ c->ci.solved = true;
+ c->af (c->af_cls,
+ &csr);
+ }
+
+
+ /* Check if there is a policy for which all challenges have
+ been satisfied, if so, store it in 'rdps'. */
+ rdps = NULL;
+ for (unsigned int i = 0; i < recovery->ri.dps_len; i++)
+ {
+ struct DecryptionPolicy *dps = &recovery->dps[i];
+ bool missing = false;
+
+ for (unsigned int j = 0; j < dps->pub_details.challenges_length; j++)
+ {
+ bool found = false;
+
+ for (unsigned int k = 0; k < recovery->solved_challenge_pos; k++)
+ {
+ if (dps->pub_details.challenges[j] == recovery->solved_challenges[k])
+ {
+ found = true;
+ break;
+ }
+ }
+ if (! found)
+ {
+ missing = true;
+ break;
+ }
+ }
+ if (! missing)
+ {
+ rdps = dps;
+ break;
+ }
+ }
+ if (NULL == rdps)
+ return;
+
+ {
+ void *core_secret;
+ size_t core_secret_size;
+ struct ANASTASIS_CRYPTO_KeyShareP
+ key_shares[rdps->pub_details.challenges_length];
+ struct ANASTASIS_CRYPTO_PolicyKeyP policy_key;
+
+ for (unsigned int l = 0; l < rdps->pub_details.challenges_length; l++)
+ for (unsigned int m = 0; m < recovery->solved_challenge_pos; m++)
+ if (rdps->pub_details.challenges[l] == recovery->solved_challenges[m])
+ key_shares[l] = recovery->solved_challenges[m]->key_share;
+ ANASTASIS_CRYPTO_policy_key_derive (key_shares,
+ rdps->pub_details.challenges_length,
+ &rdps->salt,
+ &policy_key);
+ ANASTASIS_CRYPTO_core_secret_recover (&rdps->emk,
+ &policy_key,
+ recovery->enc_core_secret,
+ recovery->enc_core_secret_size,
+ &core_secret,
+ &core_secret_size);
+ recovery->csc (recovery->csc_cls,
+ ANASTASIS_RS_SUCCESS,
+ core_secret,
+ core_secret_size);
+ GNUNET_free (core_secret);
+ ANASTASIS_recovery_abort (recovery);
+ }
+}
+
+
+const struct ANASTASIS_ChallengeDetails *
+ANASTASIS_challenge_get_details (struct ANASTASIS_Challenge *challenge)
+{
+ return &challenge->ci;
+}
+
+
+int
+ANASTASIS_challenge_start (struct ANASTASIS_Challenge *c,
+ const struct ANASTASIS_PaymentSecretP *psp,
+ struct GNUNET_TIME_Relative timeout,
+ const struct GNUNET_HashCode *hashed_answer,
+ ANASTASIS_AnswerFeedback af,
+ void *af_cls)
+{
+ if (c->ci.solved)
+ {
+ GNUNET_break (0);
+ return GNUNET_NO; /* already solved */
+ }
+ if (NULL != c->kslo)
+ {
+ GNUNET_break (0);
+ return GNUNET_NO; /* already solving */
+ }
+ c->af = af;
+ c->af_cls = af_cls;
+ c->kslo = ANASTASIS_keyshare_lookup (c->recovery->ctx,
+ c->url,
+ &c->ci.uuid,
+ &c->truth_key,
+ psp,
+ timeout,
+ hashed_answer,
+ &keyshare_lookup_cb,
+ c);
+ if (NULL == c->kslo)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+int
+ANASTASIS_challenge_answer (
+ struct ANASTASIS_Challenge *c,
+ const struct ANASTASIS_PaymentSecretP *psp,
+ struct GNUNET_TIME_Relative timeout,
+ const char *answer_str,
+ ANASTASIS_AnswerFeedback af,
+ void *af_cls)
+{
+ struct GNUNET_HashCode hashed_answer;
+
+ GNUNET_free (c->answer);
+ c->answer = GNUNET_strdup (answer_str);
+ ANASTASIS_CRYPTO_secure_answer_hash (answer_str,
+ &c->ci.uuid,
+ &c->salt,
+ &hashed_answer);
+ return ANASTASIS_challenge_start (c,
+ psp,
+ timeout,
+ &hashed_answer,
+ af,
+ af_cls);
+}
+
+
+int
+ANASTASIS_challenge_answer2 (struct ANASTASIS_Challenge *c,
+ const struct ANASTASIS_PaymentSecretP *psp,
+ struct GNUNET_TIME_Relative timeout,
+ uint64_t answer,
+ ANASTASIS_AnswerFeedback af,
+ void *af_cls)
+{
+ struct GNUNET_HashCode answer_s;
+
+ ANASTASIS_hash_answer (answer,
+ &answer_s);
+ return ANASTASIS_challenge_start (c,
+ psp,
+ timeout,
+ &answer_s,
+ af,
+ af_cls);
+}
+
+
+void
+ANASTASIS_challenge_abort (struct ANASTASIS_Challenge *c)
+{
+ if (NULL == c->kslo)
+ {
+ GNUNET_break (0);
+ return;
+ }
+ ANASTASIS_keyshare_lookup_cancel (c->kslo);
+ c->kslo = NULL;
+ c->af = NULL;
+ c->af_cls = NULL;
+}
+
+
+/**
+ * Function called with the results of a ANASTASIS_policy_lookup
+ *
+ * @param cls closure
+ * @param http_status HTTP status of the request
+ * @param ud details about the lookup operation
+ */
+static void
+policy_lookup_cb (void *cls,
+ unsigned int http_status,
+ const struct ANASTASIS_DownloadDetails *dd)
+{
+ struct ANASTASIS_Recovery *r = cls;
+ void *plaintext;
+ size_t size_plaintext;
+ json_error_t json_error;
+ json_t *dec_policies;
+ json_t *esc_methods;
+
+ r->plo = NULL;
+ switch (http_status)
+ {
+ case MHD_HTTP_OK:
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ r->csc (r->csc_cls,
+ ANASTASIS_RS_POLICY_UNKNOWN,
+ NULL,
+ 0);
+ ANASTASIS_recovery_abort (r);
+ return;
+ case MHD_HTTP_NO_CONTENT:
+ /* Account known, policy expired */
+ r->csc (r->csc_cls,
+ ANASTASIS_RS_POLICY_GONE,
+ NULL,
+ 0);
+ ANASTASIS_recovery_abort (r);
+ return;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Bad server... */
+ r->csc (r->csc_cls,
+ ANASTASIS_RS_POLICY_SERVER_ERROR,
+ NULL,
+ 0);
+ ANASTASIS_recovery_abort (r);
+ return;
+ case MHD_HTTP_NOT_MODIFIED:
+ /* Should not be possible, we do not cache, fall-through! */
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u in %s:%u\n",
+ http_status,
+ __FILE__,
+ __LINE__);
+ r->csc (r->csc_cls,
+ ANASTASIS_RS_POLICY_DOWNLOAD_FAILED,
+ NULL,
+ 0);
+ ANASTASIS_recovery_abort (r);
+ return;
+ }
+ if (NULL == dd->policy)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "No recovery data available");
+ r->csc (r->csc_cls,
+ ANASTASIS_RS_POLICY_DOWNLOAD_NO_POLICY,
+ NULL,
+ 0);
+ ANASTASIS_recovery_abort (r);
+ return;
+ }
+ ANASTASIS_CRYPTO_recovery_document_decrypt (&r->id,
+ dd->policy,
+ dd->policy_size,
+ &plaintext,
+ &size_plaintext);
+ if (size_plaintext < sizeof (uint32_t))
+ {
+ GNUNET_break_op (0);
+ r->csc (r->csc_cls,
+ ANASTASIS_RS_POLICY_DOWNLOAD_INVALID_COMPRESSION,
+ NULL,
+ 0);
+ ANASTASIS_recovery_abort (r);
+ GNUNET_free (plaintext);
+ return;
+ }
+ {
+ json_t *recovery_document;
+ uint32_t be_size;
+ uLongf pt_size;
+ char *pt;
+
+ memcpy (&be_size,
+ plaintext,
+ sizeof (uint32_t));
+ pt_size = ntohl (be_size);
+ pt = GNUNET_malloc_large (pt_size);
+ if (NULL == pt)
+ {
+ GNUNET_break_op (0);
+ r->csc (r->csc_cls,
+ ANASTASIS_RS_POLICY_DOWNLOAD_TOO_BIG,
+ NULL,
+ 0);
+ ANASTASIS_recovery_abort (r);
+ GNUNET_free (plaintext);
+ return;
+ }
+ if (Z_OK !=
+ uncompress ((Bytef *) pt,
+ &pt_size,
+ (const Bytef *) plaintext + sizeof (uint32_t),
+ size_plaintext - sizeof (uint32_t)))
+ {
+ GNUNET_break_op (0);
+ r->csc (r->csc_cls,
+ ANASTASIS_RS_POLICY_DOWNLOAD_INVALID_COMPRESSION,
+ NULL,
+ 0);
+ GNUNET_free (plaintext);
+ GNUNET_free (pt);
+ ANASTASIS_recovery_abort (r);
+ return;
+ }
+ GNUNET_free (plaintext);
+ recovery_document = json_loadb ((char *) pt,
+ pt_size,
+ JSON_DECODE_ANY,
+ &json_error);
+ GNUNET_free (pt);
+ if (NULL == recovery_document)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to read JSON input: %s at %d:%s (offset: %d)\n",
+ json_error.text,
+ json_error.line,
+ json_error.source,
+ json_error.position);
+ GNUNET_break_op (0);
+ r->csc (r->csc_cls,
+ ANASTASIS_RS_POLICY_DOWNLOAD_NO_JSON,
+ NULL,
+ 0);
+ ANASTASIS_recovery_abort (r);
+ return;
+ }
+
+ {
+ const char *secret_name = NULL;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_json ("policies",
+ &dec_policies),
+ GNUNET_JSON_spec_json ("escrow_methods",
+ &esc_methods),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("secret_name",
+ &secret_name)),
+ GNUNET_JSON_spec_varsize ("encrypted_core_secret",
+ &r->enc_core_secret,
+ &r->enc_core_secret_size),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (recovery_document,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ json_dumpf (recovery_document,
+ stderr,
+ 0);
+ json_decref (recovery_document);
+ r->csc (r->csc_cls,
+ ANASTASIS_RS_POLICY_MALFORMED_JSON,
+ NULL,
+ 0);
+ ANASTASIS_recovery_abort (r);
+ return;
+ }
+ if (NULL != secret_name)
+ {
+ GNUNET_break (NULL == r->secret_name);
+ r->secret_name = GNUNET_strdup (secret_name);
+ r->ri.secret_name = r->secret_name;
+ }
+ }
+ json_decref (recovery_document);
+ }
+
+ r->ri.version = dd->version;
+ r->ri.cs_len = json_array_size (esc_methods);
+ r->ri.dps_len = json_array_size (dec_policies);
+ r->ri.dps = GNUNET_new_array (r->ri.dps_len,
+ struct ANASTASIS_DecryptionPolicy *);
+ r->dps = GNUNET_new_array (r->ri.dps_len,
+ struct DecryptionPolicy);
+ r->solved_challenges = GNUNET_new_array (r->ri.cs_len,
+ struct ANASTASIS_Challenge *);
+ r->ri.cs = GNUNET_new_array (r->ri.cs_len,
+ struct ANASTASIS_Challenge *);
+ r->cs = GNUNET_new_array (r->ri.cs_len,
+ struct ANASTASIS_Challenge);
+ for (unsigned int i = 0; i < r->ri.cs_len; i++)
+ {
+ struct ANASTASIS_Challenge *cs = &r->cs[i];
+ const char *instructions;
+ const char *url;
+ const char *escrow_type;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("uuid",
+ &cs->ci.uuid),
+ GNUNET_JSON_spec_string ("url",
+ &url),
+ GNUNET_JSON_spec_string ("instructions",
+ &instructions),
+ GNUNET_JSON_spec_fixed_auto ("truth_key",
+ &cs->truth_key),
+ GNUNET_JSON_spec_fixed_auto ("salt",
+ &cs->salt),
+ GNUNET_JSON_spec_fixed_auto ("provider_salt",
+ &cs->provider_salt),
+ GNUNET_JSON_spec_string ("escrow_type",
+ &escrow_type),
+ GNUNET_JSON_spec_end ()
+ };
+
+ r->ri.cs[i] = cs;
+ cs->recovery = r;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json_array_get (esc_methods,
+ i),
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ json_decref (esc_methods);
+ json_decref (dec_policies);
+ r->csc (r->csc_cls,
+ ANASTASIS_RS_POLICY_MALFORMED_JSON,
+ NULL,
+ 0);
+ ANASTASIS_recovery_abort (r);
+ return;
+ }
+ cs->url = GNUNET_strdup (url);
+ cs->type = GNUNET_strdup (escrow_type);
+ cs->ci.type = cs->type;
+ cs->ci.provider_url = cs->url;
+ cs->instructions = GNUNET_strdup (instructions);
+ cs->ci.instructions = cs->instructions;
+ }
+ json_decref (esc_methods);
+
+ for (unsigned int j = 0; j < r->ri.dps_len; j++)
+ {
+ struct DecryptionPolicy *dp = &r->dps[j];
+ json_t *uuids = NULL;
+ json_t *uuid;
+ size_t n_index;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("master_key",
+ &dp->emk),
+ GNUNET_JSON_spec_fixed_auto ("salt",
+ &dp->salt),
+ GNUNET_JSON_spec_json ("uuids",
+ &uuids),
+ GNUNET_JSON_spec_end ()
+ };
+
+ r->ri.dps[j] = &r->dps[j].pub_details;
+ if ( (GNUNET_OK !=
+ GNUNET_JSON_parse (json_array_get (dec_policies,
+ j),
+ spec,
+ NULL, NULL)) ||
+ (! json_is_array (uuids)) )
+ {
+ GNUNET_break_op (0);
+ json_decref (uuids);
+ json_decref (dec_policies);
+ r->csc (r->csc_cls,
+ ANASTASIS_RS_POLICY_MALFORMED_JSON,
+ NULL,
+ 0);
+ ANASTASIS_recovery_abort (r);
+ return;
+ }
+
+ dp->pub_details.challenges_length = json_array_size (uuids);
+ dp->pub_details.challenges
+ = GNUNET_new_array (dp->pub_details.challenges_length,
+ struct ANASTASIS_Challenge *);
+ json_array_foreach (uuids, n_index, uuid)
+ {
+ const char *uuid_str = json_string_value (uuid);
+ struct ANASTASIS_CRYPTO_TruthUUIDP uuid;
+ bool found = false;
+
+ if ( (NULL == uuid_str) ||
+ (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (
+ uuid_str,
+ strlen (uuid_str),
+ &uuid,
+ sizeof (uuid))) )
+ {
+ GNUNET_break_op (0);
+ json_decref (dec_policies);
+ json_decref (uuids);
+ r->csc (r->csc_cls,
+ ANASTASIS_RS_POLICY_MALFORMED_JSON,
+ NULL,
+ 0);
+ ANASTASIS_recovery_abort (r);
+ return;
+ }
+ for (unsigned int i = 0; i<r->ri.cs_len; i++)
+ {
+ if (0 !=
+ GNUNET_memcmp (&uuid,
+ &r->cs[i].ci.uuid))
+ continue;
+ found = true;
+ dp->pub_details.challenges[n_index] = &r->cs[i];
+ break;
+ }
+ if (! found)
+ {
+ GNUNET_break_op (0);
+ json_decref (dec_policies);
+ json_decref (uuids);
+ r->csc (r->csc_cls,
+ ANASTASIS_RS_POLICY_MALFORMED_JSON,
+ NULL,
+ 0);
+ ANASTASIS_recovery_abort (r);
+ return;
+ }
+ }
+ json_decref (uuids);
+ }
+ json_decref (dec_policies);
+ r->pc (r->pc_cls,
+ &r->ri);
+}
+
+
+struct ANASTASIS_Recovery *
+ANASTASIS_recovery_begin (
+ struct GNUNET_CURL_Context *ctx,
+ const json_t *id_data,
+ unsigned int version,
+ const char *anastasis_provider_url,
+ const struct ANASTASIS_CRYPTO_ProviderSaltP *provider_salt,
+ ANASTASIS_PolicyCallback pc,
+ void *pc_cls,
+ ANASTASIS_CoreSecretCallback csc,
+ void *csc_cls)
+{
+ struct ANASTASIS_Recovery *r;
+ struct ANASTASIS_CRYPTO_AccountPublicKeyP pub_key;
+
+ r = GNUNET_new (struct ANASTASIS_Recovery);
+ r->csc = csc;
+ r->csc_cls = csc_cls;
+ r->pc = pc;
+ r->pc_cls = pc_cls;
+ r->ctx = ctx;
+ r->id_data = json_incref ((json_t *) id_data);
+ r->provider_url = GNUNET_strdup (anastasis_provider_url);
+ ANASTASIS_CRYPTO_user_identifier_derive (id_data,
+ provider_salt,
+ &r->id);
+ ANASTASIS_CRYPTO_account_public_key_derive (&r->id,
+ &pub_key);
+ r->ri.version = version;
+ if (0 != version)
+ {
+ r->plo = ANASTASIS_policy_lookup_version (r->ctx,
+ anastasis_provider_url,
+ &pub_key,
+ &policy_lookup_cb,
+ r,
+ version);
+ }
+ else
+ {
+ r->plo = ANASTASIS_policy_lookup (r->ctx,
+ anastasis_provider_url,
+ &pub_key,
+ &policy_lookup_cb,
+ r);
+ }
+ if (NULL == r->plo)
+ {
+ GNUNET_break (0);
+ ANASTASIS_recovery_abort (r);
+ return NULL;
+ }
+ return r;
+}
+
+
+json_t *
+ANASTASIS_recovery_serialize (const struct ANASTASIS_Recovery *r)
+{
+ json_t *dps_arr;
+ json_t *cs_arr;
+
+ dps_arr = json_array ();
+ GNUNET_assert (NULL != dps_arr);
+ for (unsigned int i = 0; i<r->ri.dps_len; i++)
+ {
+ const struct DecryptionPolicy *dp = &r->dps[i];
+ json_t *c_arr;
+ json_t *dps;
+
+ c_arr = json_array ();
+ GNUNET_assert (NULL != c_arr);
+ for (unsigned int j = 0; j < dp->pub_details.challenges_length; j++)
+ {
+ const struct ANASTASIS_Challenge *c = dp->pub_details.challenges[j];
+ json_t *cs;
+
+ cs = json_pack ("{s:o}",
+ "uuid",
+ GNUNET_JSON_from_data_auto (&c->ci.uuid));
+ GNUNET_assert (NULL != cs);
+ GNUNET_assert (0 ==
+ json_array_append_new (c_arr,
+ cs));
+ }
+ dps = json_pack ("{s:o, s:o, s:o}",
+ "emk",
+ GNUNET_JSON_from_data_auto (&dp->emk),
+ "salt",
+ GNUNET_JSON_from_data_auto (&dp->salt),
+ "challenges",
+ c_arr);
+ GNUNET_assert (NULL != dps);
+ GNUNET_assert (0 ==
+ json_array_append_new (dps_arr,
+ dps));
+ }
+ cs_arr = json_array ();
+ GNUNET_assert (NULL != cs_arr);
+ for (unsigned int i = 0; i<r->ri.cs_len; i++)
+ {
+ const struct ANASTASIS_Challenge *c = &r->cs[i];
+ json_t *cs;
+
+ cs = json_pack ("{s:o,s:o,s:o,s:o,s:o?,"
+ " s:s,s:s,s:s,s:b}",
+ "uuid",
+ GNUNET_JSON_from_data_auto (&c->ci.uuid),
+ "truth_key",
+ GNUNET_JSON_from_data_auto (&c->truth_key),
+ "salt",
+ GNUNET_JSON_from_data_auto (&c->salt),
+ "provider_salt",
+ GNUNET_JSON_from_data_auto (&c->provider_salt),
+ "key_share",
+ c->ci.solved
+ ? GNUNET_JSON_from_data_auto (&c->key_share)
+ : NULL,
+ "url",
+ c->url,
+ "type",
+ c->type,
+ "instructions",
+ c->instructions,
+ "solved",
+ c->ci.solved);
+ GNUNET_assert (NULL != cs);
+ GNUNET_assert (0 ==
+ json_array_append_new (cs_arr,
+ cs));
+ }
+
+ return json_pack ("{s:o, s:o, s:o, s:I, s:O, "
+ " s:s, s:s?, s:o}",
+ "id",
+ GNUNET_JSON_from_data_auto (&r->id),
+ "dps",
+ dps_arr,
+ "cs",
+ cs_arr,
+ "version",
+ (json_int_t) r->ri.version,
+ "id_data",
+ r->id_data,
+ "provider_url",
+ r->provider_url,
+ "secret_name",
+ r->secret_name,
+ "core_secret",
+ GNUNET_JSON_from_data (r->enc_core_secret,
+ r->enc_core_secret_size));
+}
+
+
+/**
+ * Parse the @a cs_array and update @a r accordingly
+ *
+ * @param[in,out] r recovery information to update
+ * @param cs_arr serialized data to parse
+ * @return #GNUNET_OK on success
+ */
+static int
+parse_cs_array (struct ANASTASIS_Recovery *r,
+ json_t *cs_arr)
+{
+ json_t *cs;
+ unsigned int n_index;
+
+ if (! json_is_array (cs_arr))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ r->ri.cs_len = json_array_size (cs_arr);
+ r->solved_challenges = GNUNET_new_array (r->ri.cs_len,
+ struct ANASTASIS_Challenge *);
+ r->ri.cs = GNUNET_new_array (r->ri.cs_len,
+ struct ANASTASIS_Challenge *);
+ r->cs = GNUNET_new_array (r->ri.cs_len,
+ struct ANASTASIS_Challenge);
+ json_array_foreach (cs_arr, n_index, cs)
+ {
+ struct ANASTASIS_Challenge *c = &r->cs[n_index];
+ const char *instructions;
+ const char *url;
+ const char *escrow_type;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("uuid",
+ &c->ci.uuid),
+ GNUNET_JSON_spec_string ("url",
+ &url),
+ GNUNET_JSON_spec_string ("instructions",
+ &instructions),
+ GNUNET_JSON_spec_fixed_auto ("truth_key",
+ &c->truth_key),
+ GNUNET_JSON_spec_fixed_auto ("salt",
+ &c->salt),
+ GNUNET_JSON_spec_fixed_auto ("provider_salt",
+ &c->provider_salt),
+ GNUNET_JSON_spec_string ("type",
+ &escrow_type),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("key_share",
+ &c->key_share)),
+ GNUNET_JSON_spec_end ()
+ };
+
+ r->ri.cs[n_index] = c;
+ c->recovery = r;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (cs,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ c->url = GNUNET_strdup (url);
+ c->type = GNUNET_strdup (escrow_type);
+ c->ci.type = c->type;
+ c->instructions = GNUNET_strdup (instructions);
+ c->ci.instructions = c->instructions;
+ c->ci.provider_url = c->url;
+ {
+ json_t *ks;
+
+ ks = json_object_get (cs,
+ "key_share");
+ if ( (NULL != ks) &&
+ (! json_is_null (ks)) )
+ {
+ c->ci.solved = true;
+ r->solved_challenges[r->solved_challenge_pos++] = c;
+ }
+ }
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse the @a dps_array and update @a r accordingly
+ *
+ * @param[in,out] r recovery information to update
+ * @param dps_arr serialized data to parse
+ * @return #GNUNET_OK on success
+ */
+static int
+parse_dps_array (struct ANASTASIS_Recovery *r,
+ json_t *dps_arr)
+{
+ json_t *dps;
+ unsigned int n_index;
+
+ if (! json_is_array (dps_arr))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ r->ri.dps_len = json_array_size (dps_arr);
+ r->dps = GNUNET_new_array (r->ri.dps_len,
+ struct DecryptionPolicy);
+ r->ri.dps = GNUNET_new_array (r->ri.dps_len,
+ struct ANASTASIS_DecryptionPolicy *);
+
+ json_array_foreach (dps_arr, n_index, dps)
+ {
+ struct DecryptionPolicy *dp = &r->dps[n_index];
+ json_t *challenges;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("emk",
+ &dp->emk),
+ GNUNET_JSON_spec_fixed_auto ("salt",
+ &dp->salt),
+ GNUNET_JSON_spec_json ("challenges",
+ &challenges),
+ GNUNET_JSON_spec_end ()
+ };
+ const char *err_json_name;
+ unsigned int err_line;
+
+ r->ri.dps[n_index] = &dp->pub_details;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (dps,
+ spec,
+ &err_json_name,
+ &err_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse decryption policy JSON entry `%s'\n",
+ err_json_name);
+ json_dumpf (dps,
+ stderr,
+ JSON_INDENT (2));
+ return GNUNET_SYSERR;
+ }
+ if (! json_is_array (challenges))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_SYSERR;
+ }
+ dp->pub_details.challenges_length = json_array_size (challenges);
+ dp->pub_details.challenges = GNUNET_new_array (
+ dp->pub_details.challenges_length,
+ struct ANASTASIS_Challenge *);
+
+ {
+ json_t *challenge;
+ unsigned int c_index;
+ json_array_foreach (challenges, c_index, challenge)
+ {
+ struct ANASTASIS_CRYPTO_TruthUUIDP uuid;
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_fixed_auto ("uuid",
+ &uuid),
+ GNUNET_JSON_spec_end ()
+ };
+ bool found = false;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (challenge,
+ ispec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_SYSERR;
+ }
+ for (unsigned int i = 0; i<r->ri.cs_len; i++)
+ {
+ if (0 !=
+ GNUNET_memcmp (&uuid,
+ &r->cs[i].ci.uuid))
+ continue;
+ dp->pub_details.challenges[c_index] = &r->cs[i];
+ found = true;
+ }
+ if (! found)
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_SYSERR;
+ }
+ }
+ }
+ GNUNET_JSON_parse_free (spec);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Asynchronously call "pc" on the recovery information.
+ *
+ * @param cls a `struct ANASTASIS_Recovery *`
+ */
+static void
+run_async_pc (void *cls)
+{
+ struct ANASTASIS_Recovery *r = cls;
+
+ r->do_async = NULL;
+ r->pc (r->pc_cls,
+ &r->ri);
+}
+
+
+struct ANASTASIS_Recovery *
+ANASTASIS_recovery_deserialize (struct GNUNET_CURL_Context *ctx,
+ const json_t *input,
+ ANASTASIS_PolicyCallback pc,
+ void *pc_cls,
+ ANASTASIS_CoreSecretCallback csc,
+ void *csc_cls)
+{
+ struct ANASTASIS_Recovery *r;
+
+ r = GNUNET_new (struct ANASTASIS_Recovery);
+ r->csc = csc;
+ r->csc_cls = csc_cls;
+ r->pc = pc;
+ r->pc_cls = pc_cls;
+ r->ctx = ctx;
+ {
+ const char *err_json_name;
+ unsigned int err_line;
+ uint32_t version;
+ json_t *dps_arr;
+ json_t *cs_arr;
+ json_t *id_data;
+ const char *provider_url;
+ const char *secret_name;
+ void *ecs;
+ size_t ecs_size;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("id",
+ &r->id),
+ GNUNET_JSON_spec_string ("provider_url",
+ &provider_url),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("secret_name",
+ &secret_name)),
+ GNUNET_JSON_spec_uint32 ("version",
+ &version),
+ GNUNET_JSON_spec_json ("dps",
+ &dps_arr),
+ GNUNET_JSON_spec_json ("cs",
+ &cs_arr),
+ GNUNET_JSON_spec_json ("id_data",
+ &id_data),
+ GNUNET_JSON_spec_varsize ("core_secret",
+ &ecs,
+ &ecs_size),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (input,
+ spec,
+ &err_json_name,
+ &err_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse recovery document JSON entry `%s'\n",
+ err_json_name);
+ json_dumpf (input,
+ stderr,
+ JSON_INDENT (2));
+ return NULL;
+ }
+ r->ri.version = version;
+ if ( (GNUNET_OK !=
+ parse_cs_array (r,
+ cs_arr)) ||
+ (GNUNET_OK !=
+ parse_dps_array (r,
+ dps_arr)) )
+ {
+ GNUNET_break_op (0);
+ ANASTASIS_recovery_abort (r);
+ GNUNET_JSON_parse_free (spec);
+ return NULL;
+ }
+ r->id_data = json_incref (id_data);
+ r->provider_url = GNUNET_strdup (provider_url);
+ if (NULL != secret_name)
+ r->secret_name = GNUNET_strdup (secret_name);
+ r->ri.secret_name = r->secret_name;
+ if (0 != ecs_size)
+ {
+ r->enc_core_secret = GNUNET_memdup (ecs,
+ ecs_size);
+ r->enc_core_secret_size = ecs_size;
+ }
+ GNUNET_JSON_parse_free (spec);
+ }
+ if (0 == r->ri.dps_len)
+ {
+ struct ANASTASIS_CRYPTO_AccountPublicKeyP pub_key;
+
+ ANASTASIS_CRYPTO_account_public_key_derive (&r->id,
+ &pub_key);
+ if (0 != r->ri.version)
+ {
+ r->plo = ANASTASIS_policy_lookup_version (r->ctx,
+ r->provider_url,
+ &pub_key,
+ &policy_lookup_cb,
+ r,
+ r->ri.version);
+ }
+ else
+ {
+ r->plo = ANASTASIS_policy_lookup (r->ctx,
+ r->provider_url,
+ &pub_key,
+ &policy_lookup_cb,
+ r);
+ }
+ if (NULL == r->plo)
+ {
+ GNUNET_break (0);
+ ANASTASIS_recovery_abort (r);
+ return NULL;
+ }
+ }
+ else
+ {
+ r->do_async = GNUNET_SCHEDULER_add_now (&run_async_pc,
+ r);
+ }
+ return r;
+}
+
+
+void
+ANASTASIS_recovery_abort (struct ANASTASIS_Recovery *r)
+{
+ if (NULL != r->do_async)
+ {
+ GNUNET_SCHEDULER_cancel (r->do_async);
+ r->do_async = NULL;
+ }
+ if (NULL != r->plo)
+ {
+ ANASTASIS_policy_lookup_cancel (r->plo);
+ r->plo = NULL;
+ }
+ GNUNET_free (r->solved_challenges);
+ for (unsigned int j = 0; j < r->ri.dps_len; j++)
+ GNUNET_free (r->dps[j].pub_details.challenges);
+ GNUNET_free (r->ri.dps);
+ for (unsigned int i = 0; i < r->ri.cs_len; i++)
+ {
+ struct ANASTASIS_Challenge *cs = r->ri.cs[i];
+
+ if (NULL != cs->kslo)
+ {
+ ANASTASIS_keyshare_lookup_cancel (cs->kslo);
+ cs->kslo = NULL;
+ }
+ GNUNET_free (cs->url);
+ GNUNET_free (cs->type);
+ GNUNET_free (cs->instructions);
+ GNUNET_free (cs->answer);
+ }
+ GNUNET_free (r->ri.cs);
+ GNUNET_free (r->cs);
+ GNUNET_free (r->dps);
+ json_decref (r->id_data);
+ GNUNET_free (r->provider_url);
+ GNUNET_free (r->secret_name);
+ GNUNET_free (r->enc_core_secret);
+ GNUNET_free (r);
+}
diff --git a/src/lib/test_merchant.priv b/src/lib/test_merchant.priv
new file mode 100644
index 0000000..9c18c35
--- /dev/null
+++ b/src/lib/test_merchant.priv
@@ -0,0 +1 @@
+`ì&-Èí–ñ./öÀ¿ jxÌGÝ¢O:6l,ζXT4í \ No newline at end of file