/*
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);
}