/* 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_challenge_answer.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 #include /** * State for a "challenge answer" CMD. */ struct ChallengeState { /** * The interpreter state. */ struct TALER_TESTING_Interpreter *is; /** * Reference to the challenge we are solving */ struct ANASTASIS_Challenge *c; /** * Answer to the challenge we are solving */ const char *answer; /** * Reference to the recovery process */ const char *challenge_ref; /** * Reference to the payment */ const char *payment_ref; /** * "taler://pay/" URL we got back, if any. Otherwise NULL. */ char *payment_uri; /** * Order ID extracted from @e payment_uri, or NULL. */ char *order_id; /** * Payment order ID we are to provide in the request. */ struct ANASTASIS_PaymentSecretP payment_order_req; /** * Expected status code. */ enum ANASTASIS_ChallengeStatus expected_cs; /** * Index of the challenge we are solving */ unsigned int challenge_index; /** * 0 for no plugin needed 1 for plugin needed to authenticate */ unsigned int mode; /** * code we read in the file generated by the plugin */ char code[22]; }; static void challenge_answer_cb (void *af_cls, const struct ANASTASIS_ChallengeStartResponse *csr) { struct ChallengeState *cs = af_cls; cs->c = NULL; if (csr->cs != cs->expected_cs) { GNUNET_break (0); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Expected status %u, got %u\n", cs->expected_cs, csr->cs); TALER_TESTING_interpreter_fail (cs->is); return; } switch (csr->cs) { case ANASTASIS_CHALLENGE_STATUS_SOLVED: break; case ANASTASIS_CHALLENGE_STATUS_INSTRUCTIONS: { FILE *file; char *fn; if (0 == strcasecmp (csr->details.open_challenge.content_type, "application/json")) { const char *filename; json_t *in; in = json_loadb (csr->details.open_challenge.body, csr->details.open_challenge.body_size, JSON_REJECT_DUPLICATES, NULL); if (NULL == in) { GNUNET_break (0); TALER_TESTING_interpreter_fail (cs->is); return; } filename = json_string_value (json_object_get (in, "filename")); if (NULL == filename) { GNUNET_break (0); json_decref (in); TALER_TESTING_interpreter_fail (cs->is); return; } fn = GNUNET_strdup (filename); json_decref (in); } else { fn = GNUNET_strndup (csr->details.open_challenge.body, csr->details.open_challenge.body_size); } file = fopen (fn, "r"); if (NULL == file) { GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "open", fn); GNUNET_free (fn); TALER_TESTING_interpreter_fail (cs->is); return; } if (0 == fscanf (file, "%21s", cs->code)) { GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "fscanf", fn); TALER_TESTING_interpreter_fail (cs->is); fclose (file); GNUNET_free (fn); return; } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Read challenge answer `%s' from file `%s'\n", cs->code, fn); TALER_TESTING_interpreter_next (cs->is); GNUNET_break (0 == fclose (file)); GNUNET_free (fn); return; } case ANASTASIS_CHALLENGE_STATUS_PAYMENT_REQUIRED: if (0 != strncmp (csr->details.payment_required.taler_pay_uri, "taler+http://pay/", strlen ("taler+http://pay/"))) { GNUNET_break (0); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Invalid payment URI `%s'\n", csr->details.payment_required.taler_pay_uri); TALER_TESTING_interpreter_fail (cs->is); return; } cs->payment_uri = GNUNET_strdup ( csr->details.payment_required.taler_pay_uri); { struct TALER_MERCHANT_PayUriData pud; if (GNUNET_OK != TALER_MERCHANT_parse_pay_uri (cs->payment_uri, &pud)) { GNUNET_break (0); TALER_TESTING_interpreter_fail (cs->is); return; } cs->order_id = GNUNET_strdup (pud.order_id); if (GNUNET_OK != GNUNET_STRINGS_string_to_data (cs->order_id, strlen (cs->order_id), &cs->payment_order_req, sizeof (cs->payment_order_req))) { GNUNET_break (0); TALER_TESTING_interpreter_fail (cs->is); return; } TALER_MERCHANT_parse_pay_uri_free (&pud); } TALER_TESTING_interpreter_next (cs->is); return; case ANASTASIS_CHALLENGE_STATUS_TRUTH_UNKNOWN: break; case ANASTASIS_CHALLENGE_STATUS_REDIRECT_FOR_AUTHENTICATION: break; case ANASTASIS_CHALLENGE_STATUS_SERVER_FAILURE: GNUNET_break (0); TALER_TESTING_interpreter_fail (cs->is); return; case ANASTASIS_CHALLENGE_STATUS_RATE_LIMIT_EXCEEDED: break; case ANASTASIS_CHALLENGE_STATUS_AUTH_TIMEOUT: break; case ANASTASIS_CHALLENGE_STATUS_EXTERNAL_INSTRUCTIONS: break; } TALER_TESTING_interpreter_next (cs->is); } /** * Run a "recover secret" CMD. * * @param cls closure. * @param cmd command currently being run. * @param is interpreter state. */ static void challenge_answer_run (void *cls, const struct TALER_TESTING_Command *cmd, struct TALER_TESTING_Interpreter *is) { struct ChallengeState *cs = cls; const struct ANASTASIS_Challenge *c; const struct ANASTASIS_PaymentSecretP *ps; cs->is = is; if (NULL != cs->challenge_ref) { const struct TALER_TESTING_Command *ref; ref = TALER_TESTING_interpreter_lookup_command ( is, cs->challenge_ref); if (NULL == ref) { GNUNET_break (0); TALER_TESTING_interpreter_fail (cs->is); return; } if (GNUNET_OK != ANASTASIS_TESTING_get_trait_challenge (ref, cs->challenge_index, &c)) { GNUNET_break (0); TALER_TESTING_interpreter_fail (cs->is); return; } } if (NULL != cs->payment_ref) { const struct TALER_TESTING_Command *ref; ref = TALER_TESTING_interpreter_lookup_command (is, cs->payment_ref); if (NULL == ref) { GNUNET_break (0); TALER_TESTING_interpreter_fail (cs->is); return; } if (GNUNET_OK != ANASTASIS_TESTING_get_trait_payment_secret (ref, 0, &ps)) { GNUNET_break (0); TALER_TESTING_interpreter_fail (cs->is); return; } } else { ps = NULL; } cs->c = (struct ANASTASIS_Challenge *) c; if (1 == cs->mode) { const struct TALER_TESTING_Command *ref; const char *answer; unsigned long long code; char dummy; ref = TALER_TESTING_interpreter_lookup_command (is, cs->answer); if (NULL == ref) { GNUNET_break (0); TALER_TESTING_interpreter_fail (cs->is); return; } if (GNUNET_OK != ANASTASIS_TESTING_get_trait_code (ref, 0, &answer)) { GNUNET_break (0); TALER_TESTING_interpreter_fail (cs->is); return; } if (1 != sscanf (answer, "%llu%c", &code, &dummy)) { GNUNET_break (0); TALER_TESTING_interpreter_fail (cs->is); return; } if (GNUNET_OK != ANASTASIS_challenge_answer2 (cs->c, ps, GNUNET_TIME_UNIT_ZERO, code, &challenge_answer_cb, cs)) { GNUNET_break (0); cs->c = NULL; TALER_TESTING_interpreter_fail (cs->is); return; } } else { if (GNUNET_OK != ANASTASIS_challenge_answer (cs->c, ps, GNUNET_TIME_UNIT_ZERO, cs->answer, &challenge_answer_cb, cs)) { GNUNET_break (0); cs->c = NULL; TALER_TESTING_interpreter_fail (cs->is); return; } } } /** * Run a "recover secret" CMD. * * @param cls closure. * @param cmd command currently being run. * @param is interpreter state. */ static void challenge_start_run (void *cls, const struct TALER_TESTING_Command *cmd, struct TALER_TESTING_Interpreter *is) { struct ChallengeState *cs = cls; const struct ANASTASIS_Challenge *c; const struct TALER_TESTING_Command *ref; const struct ANASTASIS_PaymentSecretP *ps; cs->is = is; ref = TALER_TESTING_interpreter_lookup_command ( is, cs->challenge_ref); if (NULL == ref) { GNUNET_break (0); TALER_TESTING_interpreter_fail (cs->is); return; } if (GNUNET_OK != ANASTASIS_TESTING_get_trait_challenge (ref, cs->challenge_index, &c)) { GNUNET_break (0); TALER_TESTING_interpreter_fail (cs->is); return; } if (NULL != cs->payment_ref) { const struct TALER_TESTING_Command *ref; ref = TALER_TESTING_interpreter_lookup_command (is, cs->payment_ref); if (NULL == ref) { GNUNET_break (0); TALER_TESTING_interpreter_fail (cs->is); return; } if (GNUNET_OK != ANASTASIS_TESTING_get_trait_payment_secret (ref, 0, &ps)) { GNUNET_break (0); TALER_TESTING_interpreter_fail (cs->is); return; } } else { ps = NULL; } if (GNUNET_OK != ANASTASIS_challenge_start ((struct ANASTASIS_Challenge *) c, ps, GNUNET_TIME_UNIT_ZERO, NULL, &challenge_answer_cb, cs)) { GNUNET_break (0); TALER_TESTING_interpreter_fail (cs->is); return; } } /** * 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 challenge_cleanup (void *cls, const struct TALER_TESTING_Command *cmd) { struct ChallengeState *cs = cls; if (NULL != cs->c) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Command '%s' did not complete (challenge answer)\n", cmd->label); ANASTASIS_challenge_abort (cs->c); cs->c = NULL; } GNUNET_free (cs->payment_uri); GNUNET_free (cs->order_id); GNUNET_free (cs); } /** * 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 challenge_create_traits (void *cls, const void **ret, const char *trait, unsigned int index) { struct ChallengeState *cs = cls; struct TALER_TESTING_Trait traits[] = { ANASTASIS_TESTING_make_trait_code (0, cs->code), ANASTASIS_TESTING_make_trait_payment_secret (0, &cs->payment_order_req), TALER_TESTING_make_trait_url (TALER_TESTING_UT_TALER_URL, cs->payment_uri), TALER_TESTING_make_trait_order_id (0, cs->order_id), TALER_TESTING_trait_end () }; return TALER_TESTING_get_trait (traits, ret, trait, index); } struct TALER_TESTING_Command ANASTASIS_TESTING_cmd_challenge_start ( const char *label, const char *payment_ref, const char *challenge_ref, unsigned int challenge_index, enum ANASTASIS_ChallengeStatus expected_cs) { struct ChallengeState *cs; cs = GNUNET_new (struct ChallengeState); cs->expected_cs = expected_cs; cs->challenge_ref = challenge_ref; cs->payment_ref = payment_ref; cs->challenge_index = challenge_index; { struct TALER_TESTING_Command cmd = { .cls = cs, .label = label, .run = &challenge_start_run, .cleanup = &challenge_cleanup, .traits = &challenge_create_traits }; return cmd; } } struct TALER_TESTING_Command ANASTASIS_TESTING_cmd_challenge_answer ( const char *label, const char *payment_ref, const char *challenge_ref, unsigned int challenge_index, const char *answer, unsigned int mode, enum ANASTASIS_ChallengeStatus expected_cs) { struct ChallengeState *cs; cs = GNUNET_new (struct ChallengeState); cs->expected_cs = expected_cs; cs->challenge_ref = challenge_ref; cs->payment_ref = payment_ref; cs->answer = answer; cs->challenge_index = challenge_index; cs->mode = mode; { struct TALER_TESTING_Command cmd = { .cls = cs, .label = label, .run = &challenge_answer_run, .cleanup = &challenge_cleanup, .traits = &challenge_create_traits }; return cmd; } } /* end of testing_cmd_challenge_answer.c */