summaryrefslogtreecommitdiff
path: root/src/lib/anastasis_recovery.c
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/anastasis_recovery.c
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/anastasis_recovery.c')
-rw-r--r--src/lib/anastasis_recovery.c1425
1 files changed, 1425 insertions, 0 deletions
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);
+}