From 7e669bcf6b6336ec429da949bcb4aa456971dba2 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Fri, 30 Jul 2021 10:38:27 +0200 Subject: folding history in preparation of GNU Anastasis v0.0.0 release --- src/lib/Makefile.am | 27 + src/lib/anastasis_backup.c | 979 +++++++++++++++++++++++++++++ src/lib/anastasis_recovery.c | 1425 ++++++++++++++++++++++++++++++++++++++++++ src/lib/test_merchant.priv | 1 + 4 files changed, 2432 insertions(+) create mode 100644 src/lib/Makefile.am create mode 100644 src/lib/anastasis_backup.c create mode 100644 src/lib/anastasis_recovery.c create mode 100644 src/lib/test_merchant.priv (limited to 'src/lib') 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 +*/ +/** + * @brief anastasis client api + * @author Christian Grothoff + * @author Dominik Meister + * @author Dennis Neufeld + */ +#include "platform.h" +#include "anastasis.h" +#include +#include + + +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; itruths[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; itruths_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; ipss_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; ipss_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; ipss_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 +*/ +/** + * @brief anastasis client api + * @author Christian Grothoff + * @author Dominik Meister + * @author Dennis Neufeld + */ +#include "platform.h" +#include "anastasis.h" +#include +#include +#include +#include + + +/** + * 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; iri.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; iri.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; iri.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; iri.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 -- cgit v1.2.3