/* This file is part of Anastasis Copyright (C) 2020, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with Anastasis; see the file COPYING.GPL. If not, see */ /** * @file testing/testing_cmd_recover_secret.c * @brief command to execute the anastasis recovery service * @author Christian Grothoff * @author Dennis Neufeld * @author Dominik Meister */ #include "platform.h" #include "anastasis_testing_lib.h" #include #include /** * State for a "recover secret" CMD. */ struct RecoverSecretState { /** * The interpreter state. */ struct TALER_TESTING_Interpreter *is; /** * URL of the anastasis backend. */ const char *anastasis_url; /** * The /policy GET operation handle. */ struct ANASTASIS_Recovery *recovery; /** * Reference to download command we expect to look up. */ const char *download_reference; /** * Reference to core secret share we expect to look up. */ const char *core_secret_reference; /** * Options for how we are supposed to do the download. */ enum ANASTASIS_TESTING_RecoverSecretOption rsopt; /** * Identification data from the user */ json_t *id_data; /** * Salt to be used to derive the id */ struct ANASTASIS_CRYPTO_ProviderSaltP *salt; /** * Recovery information from the lookup */ struct ANASTASIS_RecoveryInformation *ri; /** * Coresecret to check if decryption worked */ const void *core_secret; /** * Task scheduled to wait for recovery to complete. */ struct GNUNET_SCHEDULER_Task *recovery_task; /** * version of the recovery document */ unsigned int version; /** * #GNUNET_OK if the secret was recovered, #GNUNET_SYSERR if * recovery failed (yielded wrong secret). */ int recovered; }; /** * Callback which passes back the recovery document and its possible * policies. Also passes back the version of the document for the user * to check. * * @param cls closure for the callback * @param ri recovery information struct which contains the policies */ static void policy_lookup_cb (void *cls, const struct ANASTASIS_RecoveryInformation *ri) { struct RecoverSecretState *rss = cls; rss->ri = (struct ANASTASIS_RecoveryInformation *) ri; if (NULL == ri) { GNUNET_break (0); TALER_TESTING_interpreter_fail (rss->is); return; } TALER_TESTING_interpreter_next (rss->is); } /** * This function is called whenever the recovery process ends. * On success, the secret is returned in @a secret. * * @param cls closure * @param rc error code * @param secret contains the core secret which is passed to the user * @param secret_size defines the size of the core secret */ static void core_secret_cb (void *cls, enum ANASTASIS_RecoveryStatus rc, const void *secret, size_t secret_size) { struct RecoverSecretState *rss = cls; rss->recovery = NULL; if (ANASTASIS_RS_SUCCESS != rc) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Recovery failed with status %d\n", rc); TALER_TESTING_interpreter_fail (rss->is); return; } if (0 != memcmp (secret, rss->core_secret, secret_size)) { GNUNET_break (0); rss->recovered = GNUNET_SYSERR; if (NULL != rss->recovery_task) { GNUNET_SCHEDULER_cancel (rss->recovery_task); rss->recovery_task = NULL; TALER_TESTING_interpreter_fail (rss->is); } return; } rss->recovered = GNUNET_OK; if (NULL != rss->recovery_task) { GNUNET_SCHEDULER_cancel (rss->recovery_task); rss->recovery_task = NULL; TALER_TESTING_interpreter_next (rss->is); } } /** * Run a "recover secret" CMD. * * @param cls closure. * @param cmd command currently being run. * @param is interpreter state. */ static void recover_secret_run (void *cls, const struct TALER_TESTING_Command *cmd, struct TALER_TESTING_Interpreter *is) { struct RecoverSecretState *rss = cls; const struct TALER_TESTING_Command *ref; const struct ANASTASIS_CRYPTO_ProviderSaltP *salt; rss->is = is; if (NULL != rss->download_reference) { ref = TALER_TESTING_interpreter_lookup_command (is, rss->download_reference); if (NULL == ref) { GNUNET_break (0); TALER_TESTING_interpreter_fail (rss->is); return; } if (GNUNET_OK != ANASTASIS_TESTING_get_trait_salt (ref, 0, &salt)) { GNUNET_break (0); TALER_TESTING_interpreter_fail (rss->is); return; } } if (NULL != rss->core_secret_reference) { ref = TALER_TESTING_interpreter_lookup_command ( is, rss->core_secret_reference); if (NULL == ref) { GNUNET_break (0); TALER_TESTING_interpreter_fail (rss->is); return; } if (GNUNET_OK != ANASTASIS_TESTING_get_trait_core_secret (ref, 0, &rss->core_secret)) { GNUNET_break (0); TALER_TESTING_interpreter_fail (rss->is); return; } } rss->recovery = ANASTASIS_recovery_begin (is->ctx, rss->id_data, rss->version, rss->anastasis_url, salt, &policy_lookup_cb, rss, &core_secret_cb, rss); if (NULL == rss->recovery) { GNUNET_break (0); TALER_TESTING_interpreter_fail (rss->is); return; } } /** * Task to run the abort routine on the given @a cls object * after the stack has fully unwound. * * @param cls a `struct ANASTASIS_Recovery *` */ static void delayed_abort (void *cls) { struct ANASTASIS_Recovery *recovery = cls; ANASTASIS_recovery_abort (recovery); } /** * Free the state of a "recover secret" CMD, and possibly * cancel it if it did not complete. * * @param cls closure * @param cmd command being freed. */ static void recover_secret_cleanup (void *cls, const struct TALER_TESTING_Command *cmd) { struct RecoverSecretState *rss = cls; if (NULL != rss->recovery) { /* must run first, or at least before #core_secret_cb */ (void) GNUNET_SCHEDULER_add_with_priority ( GNUNET_SCHEDULER_PRIORITY_SHUTDOWN, &delayed_abort, rss->recovery); rss->recovery = NULL; } if (NULL != rss->recovery_task) { GNUNET_SCHEDULER_cancel (rss->recovery_task); rss->recovery_task = NULL; } json_decref (rss->id_data); GNUNET_free (rss); } /** * Offer internal data to other commands. * * @param cls closure * @param[out] ret result (could be anything) * @param trait name of the trait * @param index index number of the object to extract. * @return #GNUNET_OK on success */ static int recover_secret_traits (void *cls, const void **ret, const char *trait, unsigned int index) { struct RecoverSecretState *rss = cls; if (NULL == rss->ri) { GNUNET_break (0); return GNUNET_SYSERR; } if (index >= rss->ri->cs_len) { GNUNET_break (0); return GNUNET_SYSERR; } { struct TALER_TESTING_Trait traits[] = { ANASTASIS_TESTING_make_trait_challenge (index, rss->ri->cs[index]), TALER_TESTING_trait_end () }; return TALER_TESTING_get_trait (traits, ret, trait, index); } } /** * Function called on timeout of the secret finishing operation. * * @param cls a `struct RecoverSecretState *` */ static void recovery_fail (void *cls) { struct RecoverSecretState *rss = cls; rss->recovery_task = NULL; GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Timeout during secret recovery\n"); TALER_TESTING_interpreter_fail (rss->is); } /** * Wait @a delay for @a cmd to finish secret recovery. * * @param cmd command to wait on * @param delay how long to wait at most */ static void recover_secret_finish (struct TALER_TESTING_Command *cmd, struct GNUNET_TIME_Relative delay) { struct RecoverSecretState *rss = cmd->cls; GNUNET_assert (&recover_secret_run == cmd->run); GNUNET_assert (NULL == rss->recovery_task); switch (rss->recovered) { case GNUNET_OK: TALER_TESTING_interpreter_next (rss->is); break; case GNUNET_NO: rss->recovery_task = GNUNET_SCHEDULER_add_delayed (delay, &recovery_fail, rss); break; case GNUNET_SYSERR: TALER_TESTING_interpreter_fail (rss->is); break; } } struct TALER_TESTING_Command ANASTASIS_TESTING_cmd_recover_secret ( const char *label, const char *anastasis_url, const json_t *id_data, unsigned int version, enum ANASTASIS_TESTING_RecoverSecretOption rso, const char *download_ref, const char *core_secret_ref) { struct RecoverSecretState *rss; rss = GNUNET_new (struct RecoverSecretState); rss->version = version; rss->id_data = json_incref ((json_t *) id_data); rss->rsopt = rso; rss->anastasis_url = anastasis_url; rss->download_reference = download_ref; rss->core_secret_reference = core_secret_ref; { struct TALER_TESTING_Command cmd = { .cls = rss, .label = label, .run = &recover_secret_run, .cleanup = &recover_secret_cleanup, .traits = &recover_secret_traits }; return cmd; } } /** * State for a "recover secret finish" CMD. */ struct RecoverSecretFinishState { /** * The interpreter state. */ struct TALER_TESTING_Interpreter *is; /** * URL of the anastasis backend. */ const char *recover_label; /** * Timeout. */ struct GNUNET_TIME_Relative timeout; }; /** * Run a "recover secret finish" CMD. * * @param cls closure. * @param cmd command currently being run. * @param is interpreter state. */ static void recover_secret_finish_run (void *cls, const struct TALER_TESTING_Command *cmd, struct TALER_TESTING_Interpreter *is) { struct RecoverSecretFinishState *rsfs = cls; struct TALER_TESTING_Command *ref; rsfs->is = is; ref = (struct TALER_TESTING_Command *) TALER_TESTING_interpreter_lookup_command (is, rsfs->recover_label); if (NULL == ref) { GNUNET_break (0); TALER_TESTING_interpreter_fail (rsfs->is); return; } recover_secret_finish (ref, rsfs->timeout); } /** * Free the state of a "recover secret finish" CMD, and possibly * cancel it if it did not complete. * * @param cls closure * @param cmd command being freed. */ static void recover_secret_finish_cleanup (void *cls, const struct TALER_TESTING_Command *cmd) { struct RecoverSecretFinishState *rsfs = cls; GNUNET_free (rsfs); } struct TALER_TESTING_Command ANASTASIS_TESTING_cmd_recover_secret_finish ( const char *label, const char *recover_label, struct GNUNET_TIME_Relative timeout) { struct RecoverSecretFinishState *rsfs; rsfs = GNUNET_new (struct RecoverSecretFinishState); rsfs->recover_label = recover_label; rsfs->timeout = timeout; { struct TALER_TESTING_Command cmd = { .cls = rsfs, .label = label, .run = &recover_secret_finish_run, .cleanup = &recover_secret_finish_cleanup }; return cmd; } } /* end of testing_cmd_recover_secret.c */