diff options
27 files changed, 1465 insertions, 2886 deletions
diff --git a/doc/sphinx/rest.rst b/doc/sphinx/rest.rst index 206eda7..4a5aad5 100644 --- a/doc/sphinx/rest.rst +++ b/doc/sphinx/rest.rst @@ -1,6 +1,6 @@ .. This file is part of Anastasis - Copyright (C) 2019-2021 Anastasis SARL + Copyright (C) 2019-2022 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software @@ -789,13 +789,14 @@ charge per truth operation using GNU Taler. .. ts:def:: ChallengeInstructionMessage type ChallengeInstructionMessage = + | FileChallengeInstructionMessage | IbanChallengeInstructionMessage | PinChallengeInstructionMessage; interface IbanChallengeInstructionMessage { // What kind of challenge is this? - method: "iban"; + method: "IBAN_WIRE"; // How much should be wired? amount: Amount; @@ -817,11 +818,21 @@ charge per truth operation using GNU Taler. interface PinChallengeInstructionMessage { // What kind of challenge is this? - method: "pin"; + method: "TAN_SENT"; // Where was the PIN code sent? Note that this // address will most likely have been obscured // to improve privacy. - address_hint: string; + tan_address_hint: string; + + } + + interface FileChallengeInstructionMessage { + + // What kind of challenge is this? + method: "FILE_WRITTEN"; + + // Name of the file where the PIN code was written. + filename: string; } diff --git a/src/authorization/anastasis_authorization_plugin_email.c b/src/authorization/anastasis_authorization_plugin_email.c index 2284988..78be9b6 100644 --- a/src/authorization/anastasis_authorization_plugin_email.c +++ b/src/authorization/anastasis_authorization_plugin_email.c @@ -463,12 +463,9 @@ email_process (struct ANASTASIS_AUTHORIZATION_State *as, user = GNUNET_strndup (as->email, len); resp = TALER_MHD_MAKE_JSON_PACK ( - GNUNET_JSON_pack_uint64 ("code", - TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED), - GNUNET_JSON_pack_string ("hint", - TALER_ErrorCode_get_hint ( - TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED)), - GNUNET_JSON_pack_string ("detail", + GNUNET_JSON_pack_string ("challenge_type", + "TAN_SENT"), + GNUNET_JSON_pack_string ("tan_address_hint", user)); GNUNET_free (user); } @@ -494,7 +491,7 @@ email_process (struct ANASTASIS_AUTHORIZATION_State *as, "text/plain")); } mres = MHD_queue_response (connection, - MHD_HTTP_FORBIDDEN, + MHD_HTTP_OK, resp); MHD_destroy_response (resp); if (MHD_YES != mres) diff --git a/src/authorization/anastasis_authorization_plugin_file.c b/src/authorization/anastasis_authorization_plugin_file.c index 863db11..5186ae8 100644 --- a/src/authorization/anastasis_authorization_plugin_file.c +++ b/src/authorization/anastasis_authorization_plugin_file.c @@ -235,6 +235,8 @@ file_process (struct ANASTASIS_AUTHORIZATION_State *as, "application/json")) { resp = TALER_MHD_MAKE_JSON_PACK ( + GNUNET_JSON_pack_string ("challenge_type", + "FILE_WRITTEN"), GNUNET_JSON_pack_string ("filename", as->filename)); } @@ -260,7 +262,7 @@ file_process (struct ANASTASIS_AUTHORIZATION_State *as, MHD_RESULT mres; mres = MHD_queue_response (connection, - MHD_HTTP_FORBIDDEN, + MHD_HTTP_OK, resp); MHD_destroy_response (resp); if (MHD_YES != mres) diff --git a/src/authorization/anastasis_authorization_plugin_iban.c b/src/authorization/anastasis_authorization_plugin_iban.c index fec0d6d..bd58c51 100644 --- a/src/authorization/anastasis_authorization_plugin_iban.c +++ b/src/authorization/anastasis_authorization_plugin_iban.c @@ -340,23 +340,18 @@ respond_with_challenge (struct ANASTASIS_AUTHORIZATION_State *as, "Anastasis %llu", (unsigned long long) as->code); resp = TALER_MHD_MAKE_JSON_PACK ( - GNUNET_JSON_pack_string ("method", - "iban"), - GNUNET_JSON_pack_bool ("async", - true), + GNUNET_JSON_pack_string ("challenge_type", + "IBAN_WIRE"), GNUNET_JSON_pack_uint64 ("answer_code", as->code), - GNUNET_JSON_pack_object_steal ( - "details", - GNUNET_JSON_PACK ( - TALER_JSON_pack_amount ("challenge_amount", - &ctx->expected_amount), - GNUNET_JSON_pack_string ("credit_iban", - ctx->business_iban), - GNUNET_JSON_pack_string ("business_name", - ctx->business_name), - GNUNET_JSON_pack_string ("wire_transfer_subject", - subject)))); + TALER_JSON_pack_amount ("challenge_amount", + &ctx->expected_amount), + GNUNET_JSON_pack_string ("credit_iban", + ctx->business_iban), + GNUNET_JSON_pack_string ("business_name", + ctx->business_name), + GNUNET_JSON_pack_string ("wire_transfer_subject", + subject)); } else { diff --git a/src/authorization/anastasis_authorization_plugin_post.c b/src/authorization/anastasis_authorization_plugin_post.c index 4f901d2..ad0ed15 100644 --- a/src/authorization/anastasis_authorization_plugin_post.c +++ b/src/authorization/anastasis_authorization_plugin_post.c @@ -524,12 +524,9 @@ post_process (struct ANASTASIS_AUTHORIZATION_State *as, "application/json")) { resp = TALER_MHD_MAKE_JSON_PACK ( - GNUNET_JSON_pack_uint64 ("code", - TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED), - GNUNET_JSON_pack_string ("hint", - TALER_ErrorCode_get_hint ( - TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED)), - GNUNET_JSON_pack_string ("detail", + GNUNET_JSON_pack_string ("challenge_type", + "TAN_SENT"), + GNUNET_JSON_pack_string ("tan_address_hint", zip)); } else @@ -549,7 +546,7 @@ post_process (struct ANASTASIS_AUTHORIZATION_State *as, TALER_MHD_add_global_headers (resp); } mres = MHD_queue_response (connection, - MHD_HTTP_FORBIDDEN, + MHD_HTTP_OK, resp); MHD_destroy_response (resp); if (MHD_YES != mres) diff --git a/src/authorization/anastasis_authorization_plugin_sms.c b/src/authorization/anastasis_authorization_plugin_sms.c index 98152ef..6598d29 100644 --- a/src/authorization/anastasis_authorization_plugin_sms.c +++ b/src/authorization/anastasis_authorization_plugin_sms.c @@ -455,12 +455,9 @@ sms_process (struct ANASTASIS_AUTHORIZATION_State *as, "application/json")) { resp = TALER_MHD_MAKE_JSON_PACK ( - GNUNET_JSON_pack_uint64 ("code", - TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED), - GNUNET_JSON_pack_string ("hint", - TALER_ErrorCode_get_hint ( - TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED)), - GNUNET_JSON_pack_string ("detail", + GNUNET_JSON_pack_string ("challenge_type", + "TAN_SENT"), + GNUNET_JSON_pack_string ("tan_address_hint", end)); } else @@ -484,7 +481,7 @@ sms_process (struct ANASTASIS_AUTHORIZATION_State *as, "text/plain")); } mres = MHD_queue_response (connection, - MHD_HTTP_FORBIDDEN, + MHD_HTTP_OK, resp); MHD_destroy_response (resp); if (MHD_YES != mres) diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am index 83877bc..db37478 100644 --- a/src/backend/Makefile.am +++ b/src/backend/Makefile.am @@ -20,7 +20,7 @@ anastasis_httpd_SOURCES = \ anastasis-httpd_policy.c anastasis-httpd_policy.h \ anastasis-httpd_policy-meta.c anastasis-httpd_policy-meta.h \ anastasis-httpd_policy-upload.c \ - anastasis-httpd_truth.c anastasis-httpd_truth.h \ + anastasis-httpd_truth.h \ anastasis-httpd_terms.c anastasis-httpd_terms.h \ anastasis-httpd_config.c anastasis-httpd_config.h \ anastasis-httpd_truth-challenge.c \ diff --git a/src/backend/anastasis-httpd.c b/src/backend/anastasis-httpd.c index 0c9d957..4428851 100644 --- a/src/backend/anastasis-httpd.c +++ b/src/backend/anastasis-httpd.c @@ -439,17 +439,19 @@ url_handler (void *cls, TALER_EC_GENERIC_PARAMETER_MALFORMED, "truth UUID"); } - if ( (NULL == end) && - (0 == strcmp (method, - MHD_HTTP_METHOD_GET)) ) - { - return AH_handler_truth_get (connection, - &tu, - hc); - } - if ( (NULL == end) && - (0 == strcmp (method, - MHD_HTTP_METHOD_POST)) ) + if ( (NULL != end) && + (0 != strcmp (end, "/solve")) && + (0 != strcmp (end, "/challenge")) ) + return TMH_MHD_handler_static_response (&h404, + connection); + if (0 == strcmp (method, + MHD_HTTP_METHOD_OPTIONS)) + return TALER_MHD_reply_cors_preflight (connection); + if (0 != strcmp (method, + MHD_HTTP_METHOD_POST)) + return TMH_MHD_handler_static_response (&h405, + connection); + if (NULL == end) { return AH_handler_truth_post (connection, hc, @@ -457,11 +459,8 @@ url_handler (void *cls, upload_data, upload_data_size); } - if ( (NULL != end) && - (0 == strcmp (end, - "/solve")) && - (0 == strcmp (method, - MHD_HTTP_METHOD_POST)) ) + if (0 == strcmp (end, + "/solve")) { return AH_handler_truth_solve (connection, hc, @@ -469,11 +468,8 @@ url_handler (void *cls, upload_data, upload_data_size); } - if ( (NULL != end) && - (0 == strcmp (end, - "/challenge")) && - (0 == strcmp (method, - MHD_HTTP_METHOD_POST)) ) + if (0 == strcmp (end, + "/challenge")) { return AH_handler_truth_challenge (connection, hc, @@ -481,14 +477,9 @@ url_handler (void *cls, upload_data, upload_data_size); } - if (0 == strcmp (method, - MHD_HTTP_METHOD_OPTIONS)) - { - return TALER_MHD_reply_cors_preflight (connection); - } - return TMH_MHD_handler_static_response (&h405, - connection); - } + /* should be impossible to get here */ + GNUNET_assert (0); + } /* end of "/truth/" prefix */ path_matched = false; for (unsigned int i = 0; NULL != handlers[i].url; i++) { @@ -531,7 +522,6 @@ do_shutdown (void *cls) { (void) cls; AH_resume_all_bc (); - AH_truth_shutdown (); AH_truth_challenge_shutdown (); AH_truth_solve_shutdown (); AH_truth_upload_shutdown (); @@ -1030,7 +1020,6 @@ main (int argc, "CERTTYPE", "type of the TLS client certificate, defaults to PEM if not specified", &certtype), - GNUNET_GETOPT_OPTION_END }; diff --git a/src/backend/anastasis-httpd_truth-challenge.c b/src/backend/anastasis-httpd_truth-challenge.c index c583403..65dc244 100644 --- a/src/backend/anastasis-httpd_truth-challenge.c +++ b/src/backend/anastasis-httpd_truth-challenge.c @@ -981,33 +981,6 @@ AH_handler_truth_challenge ( gc->connection = connection; gc->truth_uuid = *truth_uuid; gc->hc->cc = &request_done; - { - const char *pay_id; - - pay_id = MHD_lookup_connection_value (connection, - MHD_HEADER_KIND, - ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER); - if (NULL != pay_id) - { - if (GNUNET_OK != - GNUNET_STRINGS_string_to_data ( - pay_id, - strlen (pay_id), - &gc->payment_identifier, - sizeof (struct ANASTASIS_PaymentSecretP))) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER); - } - gc->payment_identifier_provided = true; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Client provided payment identifier `%s'\n", - pay_id); - } - } { const char *long_poll_timeout_ms; @@ -1104,6 +1077,9 @@ AH_handler_truth_challenge ( struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_fixed_auto ("truth_decryption_key", &gc->truth_key), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("payment_secret", + &gc->payment_identifier)), GNUNET_JSON_spec_end () }; enum GNUNET_GenericReturnValue res; @@ -1121,6 +1097,12 @@ AH_handler_truth_challenge ( GNUNET_break_op (0); return MHD_YES; /* failure */ } + gc->payment_identifier_provided + = ! GNUNET_is_zero (&gc->payment_identifier); + if (gc->payment_identifier_provided) + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Client provided payment identifier `%s'\n", + TALER_B2S (&gc->payment_identifier)); } } diff --git a/src/backend/anastasis-httpd_truth-solve.c b/src/backend/anastasis-httpd_truth-solve.c index 577ec50..a452d8e 100644 --- a/src/backend/anastasis-httpd_truth-solve.c +++ b/src/backend/anastasis-httpd_truth-solve.c @@ -1049,33 +1049,6 @@ AH_handler_truth_solve ( gc->connection = connection; gc->truth_uuid = *truth_uuid; gc->hc->cc = &request_done; - { - const char *pay_id; - - pay_id = MHD_lookup_connection_value (connection, - MHD_HEADER_KIND, - ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER); - if (NULL != pay_id) - { - if (GNUNET_OK != - GNUNET_STRINGS_string_to_data ( - pay_id, - strlen (pay_id), - &gc->payment_identifier, - sizeof (struct ANASTASIS_PaymentSecretP))) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER); - } - gc->payment_identifier_provided = true; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Client provided payment identifier `%s'\n", - pay_id); - } - } { const char *long_poll_timeout_ms; @@ -1174,6 +1147,9 @@ AH_handler_truth_solve ( &gc->truth_key), GNUNET_JSON_spec_fixed_auto ("h_response", &gc->challenge_response), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("payment_secret", + &gc->payment_identifier)), GNUNET_JSON_spec_end () }; enum GNUNET_GenericReturnValue res; @@ -1191,6 +1167,12 @@ AH_handler_truth_solve ( GNUNET_break_op (0); return MHD_YES; /* failure */ } + gc->payment_identifier_provided + = ! GNUNET_is_zero (&gc->payment_identifier); + if (gc->payment_identifier_provided) + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Client provided payment identifier `%s'\n", + TALER_B2S (&gc->payment_identifier)); } } diff --git a/src/backend/anastasis-httpd_truth.c b/src/backend/anastasis-httpd_truth.c deleted file mode 100644 index ba6837a..0000000 --- a/src/backend/anastasis-httpd_truth.c +++ /dev/null @@ -1,1702 +0,0 @@ -/* - This file is part of Anastasis - Copyright (C) 2019, 2021 Anastasis SARL - - Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Affero 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. If not, see <http://www.gnu.org/licenses/> -*/ -/** - * @file anastasis-httpd_truth.c - * @brief functions to handle incoming requests on /truth - * @author Dennis Neufeld - * @author Dominik Meister - * @author Christian Grothoff - */ -#include "platform.h" -#include "anastasis-httpd.h" -#include "anastasis_service.h" -#include "anastasis-httpd_truth.h" -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_rest_lib.h> -#include "anastasis_authorization_lib.h" -#include <taler/taler_merchant_service.h> -#include <taler/taler_json_lib.h> - -/** - * What is the maximum frequency at which we allow - * clients to attempt to answer security questions? - */ -#define MAX_QUESTION_FREQ GNUNET_TIME_relative_multiply ( \ - GNUNET_TIME_UNIT_SECONDS, 30) - -/** - * How long should the wallet check for auto-refunds before giving up? - */ -#define AUTO_REFUND_TIMEOUT GNUNET_TIME_relative_multiply ( \ - GNUNET_TIME_UNIT_MINUTES, 2) - - -/** - * How many retries do we allow per code? - */ -#define INITIAL_RETRY_COUNTER 3 - - -struct GetContext -{ - - /** - * Payment Identifier - */ - struct ANASTASIS_PaymentSecretP payment_identifier; - - /** - * Public key of the challenge which is solved. - */ - struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid; - - /** - * Key to decrypt the truth. - */ - struct ANASTASIS_CRYPTO_TruthKeyP truth_key; - - /** - * Cost for paying the challenge. - */ - struct TALER_Amount challenge_cost; - - /** - * Our handler context. - */ - struct TM_HandlerContext *hc; - - /** - * Kept in DLL for shutdown handling while suspended. - */ - struct GetContext *next; - - /** - * Kept in DLL for shutdown handling while suspended. - */ - struct GetContext *prev; - - /** - * Connection handle for closing or resuming - */ - struct MHD_Connection *connection; - - /** - * Reference to the authorization plugin which was loaded - */ - struct ANASTASIS_AuthorizationPlugin *authorization; - - /** - * Status of the authorization - */ - struct ANASTASIS_AUTHORIZATION_State *as; - - /** - * Used while we are awaiting proposal creation. - */ - struct TALER_MERCHANT_PostOrdersHandle *po; - - /** - * Used while we are waiting payment. - */ - struct TALER_MERCHANT_OrderMerchantGetHandle *cpo; - - /** - * HTTP response code to use on resume, if non-NULL. - */ - struct MHD_Response *resp; - - /** - * Our entry in the #to_heap, or NULL. - */ - struct GNUNET_CONTAINER_HeapNode *hn; - - /** - * Challenge response we got from the request. - */ - struct GNUNET_HashCode challenge_response; - - /** - * How long do we wait at most for payment or - * authorization? - */ - struct GNUNET_TIME_Absolute timeout; - - /** - * Random authorization code we are using. - */ - uint64_t code; - - /** - * HTTP response code to use on resume, if resp is set. - */ - unsigned int response_code; - - /** - * true if client provided a payment secret / order ID? - */ - bool payment_identifier_provided; - - /** - * True if this entry is in the #gc_head DLL. - */ - bool in_list; - - /** - * True if this entry is currently suspended. - */ - bool suspended; - - /** - * Did the request include a response? - */ - bool have_response; - -}; - -/** - * Information we track for refunds. - */ -struct RefundEntry -{ - /** - * Kept in a DLL. - */ - struct RefundEntry *next; - - /** - * Kept in a DLL. - */ - struct RefundEntry *prev; - - /** - * Operation handle. - */ - struct TALER_MERCHANT_OrderRefundHandle *ro; - - /** - * Which order is being refunded. - */ - char *order_id; - - /** - * Payment Identifier - */ - struct ANASTASIS_PaymentSecretP payment_identifier; - - /** - * Public key of the challenge which is solved. - */ - struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid; -}; - - -/** - * Head of linked list of active refund operations. - */ -static struct RefundEntry *re_head; - -/** - * Tail of linked list of active refund operations. - */ -static struct RefundEntry *re_tail; - -/** - * Head of linked list over all authorization processes - */ -static struct GetContext *gc_head; - -/** - * Tail of linked list over all authorization processes - */ -static struct GetContext *gc_tail; - -/** - * Task running #do_timeout(). - */ -static struct GNUNET_SCHEDULER_Task *to_task; - - -/** - * Generate a response telling the client that answering this - * challenge failed because the rate limit has been exceeded. - * - * @param gc request to answer for - * @return MHD status code - */ -static MHD_RESULT -reply_rate_limited (const struct GetContext *gc) -{ - return TALER_MHD_REPLY_JSON_PACK ( - gc->connection, - MHD_HTTP_TOO_MANY_REQUESTS, - TALER_MHD_PACK_EC (TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED), - GNUNET_JSON_pack_uint64 ("request_limit", - gc->authorization->retry_counter), - GNUNET_JSON_pack_time_rel ("request_frequency", - gc->authorization->code_rotation_period)); -} - - -/** - * Timeout requests that are past their due date. - * - * @param cls NULL - */ -static void -do_timeout (void *cls) -{ - struct GetContext *gc; - - (void) cls; - to_task = NULL; - while (NULL != - (gc = GNUNET_CONTAINER_heap_peek (AH_to_heap))) - { - if (GNUNET_TIME_absolute_is_future (gc->timeout)) - break; - if (gc->suspended) - { - /* Test needed as we may have a "concurrent" - wakeup from another task that did not clear - this entry from the heap before the - response process concluded. */ - gc->suspended = false; - MHD_resume_connection (gc->connection); - } - GNUNET_assert (NULL != gc->hn); - gc->hn = NULL; - GNUNET_assert (gc == - GNUNET_CONTAINER_heap_remove_root (AH_to_heap)); - } - if (NULL == gc) - return; - to_task = GNUNET_SCHEDULER_add_at (gc->timeout, - &do_timeout, - NULL); -} - - -void -AH_truth_shutdown (void) -{ - struct GetContext *gc; - struct RefundEntry *re; - - while (NULL != (re = re_head)) - { - GNUNET_CONTAINER_DLL_remove (re_head, - re_tail, - re); - if (NULL != re->ro) - { - TALER_MERCHANT_post_order_refund_cancel (re->ro); - re->ro = NULL; - } - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Refund `%s' failed due to shutdown\n", - re->order_id); - GNUNET_free (re->order_id); - GNUNET_free (re); - } - - while (NULL != (gc = gc_head)) - { - GNUNET_CONTAINER_DLL_remove (gc_head, - gc_tail, - gc); - gc->in_list = false; - if (NULL != gc->cpo) - { - TALER_MERCHANT_merchant_order_get_cancel (gc->cpo); - gc->cpo = NULL; - } - if (NULL != gc->po) - { - TALER_MERCHANT_orders_post_cancel (gc->po); - gc->po = NULL; - } - if (gc->suspended) - { - gc->suspended = false; - MHD_resume_connection (gc->connection); - } - if (NULL != gc->as) - { - gc->authorization->cleanup (gc->as); - gc->as = NULL; - gc->authorization = NULL; - } - } - ANASTASIS_authorization_plugin_shutdown (); - if (NULL != to_task) - { - GNUNET_SCHEDULER_cancel (to_task); - to_task = NULL; - } -} - - -/** - * Callback to process a POST /orders/ID/refund request - * - * @param cls closure with a `struct RefundEntry *` - * @param hr HTTP response details - * @param taler_refund_uri the refund uri offered to the wallet - * @param h_contract hash of the contract a Browser may need to authorize - * obtaining the HTTP response. - */ -static void -refund_cb ( - void *cls, - const struct TALER_MERCHANT_HttpResponse *hr, - const char *taler_refund_uri, - const struct TALER_PrivateContractHashP *h_contract) -{ - struct RefundEntry *re = cls; - - re->ro = NULL; - switch (hr->http_status) - { - case MHD_HTTP_OK: - { - enum GNUNET_DB_QueryStatus qs; - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Refund `%s' succeeded\n", - re->order_id); - qs = db->record_challenge_refund (db->cls, - &re->truth_uuid, - &re->payment_identifier); - switch (qs) - { - case GNUNET_DB_STATUS_HARD_ERROR: - GNUNET_break (0); - break; - case GNUNET_DB_STATUS_SOFT_ERROR: - GNUNET_break (0); - break; - case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - GNUNET_break (0); - break; - case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: - break; - } - } - break; - default: - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Refund `%s' failed with HTTP status %u: %s (#%u)\n", - re->order_id, - hr->http_status, - hr->hint, - (unsigned int) hr->ec); - break; - } - GNUNET_CONTAINER_DLL_remove (re_head, - re_tail, - re); - GNUNET_free (re->order_id); - GNUNET_free (re); -} - - -/** - * Start to give a refund for the challenge created by @a gc. - * - * @param gc request where we failed and should now grant a refund for - */ -static void -begin_refund (const struct GetContext *gc) -{ - struct RefundEntry *re; - - re = GNUNET_new (struct RefundEntry); - re->order_id = GNUNET_STRINGS_data_to_string_alloc ( - &gc->payment_identifier, - sizeof (gc->payment_identifier)); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Challenge execution failed, triggering refund for order `%s'\n", - re->order_id); - re->payment_identifier = gc->payment_identifier; - re->truth_uuid = gc->truth_uuid; - re->ro = TALER_MERCHANT_post_order_refund (AH_ctx, - AH_backend_url, - re->order_id, - &gc->challenge_cost, - "failed to issue challenge", - &refund_cb, - re); - if (NULL == re->ro) - { - GNUNET_break (0); - GNUNET_free (re->order_id); - GNUNET_free (re); - return; - } - GNUNET_CONTAINER_DLL_insert (re_head, - re_tail, - re); -} - - -/** - * Callback used to notify the application about completed requests. - * Cleans up the requests data structures. - * - * @param hc - */ -static void -request_done (struct TM_HandlerContext *hc) -{ - struct GetContext *gc = hc->ctx; - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Request completed\n"); - if (NULL == gc) - return; - hc->cc = NULL; - GNUNET_assert (! gc->suspended); - if (gc->in_list) - { - GNUNET_CONTAINER_DLL_remove (gc_head, - gc_tail, - gc); - gc->in_list = false; - } - if (NULL != gc->hn) - { - GNUNET_assert (gc == - GNUNET_CONTAINER_heap_remove_node (gc->hn)); - gc->hn = NULL; - } - if (NULL != gc->as) - { - gc->authorization->cleanup (gc->as); - gc->authorization = NULL; - gc->as = NULL; - } - if (NULL != gc->cpo) - { - TALER_MERCHANT_merchant_order_get_cancel (gc->cpo); - gc->cpo = NULL; - } - if (NULL != gc->po) - { - TALER_MERCHANT_orders_post_cancel (gc->po); - gc->po = NULL; - } - GNUNET_free (gc); - hc->ctx = NULL; -} - - -/** - * Transmit a payment request for @a order_id on @a connection - * - * @param gc context to make payment request for - */ -static void -make_payment_request (struct GetContext *gc) -{ - struct MHD_Response *resp; - - resp = MHD_create_response_from_buffer (0, - NULL, - MHD_RESPMEM_PERSISTENT); - GNUNET_assert (NULL != resp); - TALER_MHD_add_global_headers (resp); - { - char *hdr; - char *order_id; - const char *pfx; - const char *hn; - - if (0 == strncasecmp ("https://", - AH_backend_url, - strlen ("https://"))) - { - pfx = "taler://"; - hn = &AH_backend_url[strlen ("https://")]; - } - else if (0 == strncasecmp ("http://", - AH_backend_url, - strlen ("http://"))) - { - pfx = "taler+http://"; - hn = &AH_backend_url[strlen ("http://")]; - } - else - { - /* This invariant holds as per check in anastasis-httpd.c */ - GNUNET_assert (0); - } - /* This invariant holds as per check in anastasis-httpd.c */ - GNUNET_assert (0 != strlen (hn)); - - order_id = GNUNET_STRINGS_data_to_string_alloc ( - &gc->payment_identifier, - sizeof (gc->payment_identifier)); - GNUNET_asprintf (&hdr, - "%spay/%s%s/", - pfx, - hn, - order_id); - GNUNET_free (order_id); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Sending payment request `%s'\n", - hdr); - GNUNET_break (MHD_YES == - MHD_add_response_header (resp, - ANASTASIS_HTTP_HEADER_TALER, - hdr)); - GNUNET_free (hdr); - } - gc->resp = resp; - gc->response_code = MHD_HTTP_PAYMENT_REQUIRED; -} - - -/** - * Callbacks of this type are used to serve the result of submitting a - * /contract request to a merchant. - * - * @param cls our `struct GetContext` - * @param por response details - */ -static void -proposal_cb (void *cls, - const struct TALER_MERCHANT_PostOrdersReply *por) -{ - struct GetContext *gc = cls; - enum GNUNET_DB_QueryStatus qs; - - gc->po = NULL; - GNUNET_assert (gc->in_list); - GNUNET_CONTAINER_DLL_remove (gc_head, - gc_tail, - gc); - gc->in_list = false; - GNUNET_assert (gc->suspended); - gc->suspended = false; - MHD_resume_connection (gc->connection); - AH_trigger_daemon (NULL); - if (MHD_HTTP_OK != por->hr.http_status) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Backend returned status %u/%d\n", - por->hr.http_status, - (int) por->hr.ec); - GNUNET_break (0); - gc->resp = TALER_MHD_MAKE_JSON_PACK ( - GNUNET_JSON_pack_uint64 ("code", - TALER_EC_ANASTASIS_TRUTH_PAYMENT_CREATE_BACKEND_ERROR), - GNUNET_JSON_pack_string ("hint", - "Failed to setup order with merchant backend"), - GNUNET_JSON_pack_uint64 ("backend-ec", - por->hr.ec), - GNUNET_JSON_pack_uint64 ("backend-http-status", - por->hr.http_status), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_object_steal ("backend-reply", - (json_t *) por->hr.reply))); - gc->response_code = MHD_HTTP_BAD_GATEWAY; - return; - } - qs = db->record_challenge_payment (db->cls, - &gc->truth_uuid, - &gc->payment_identifier, - &gc->challenge_cost); - if (0 >= qs) - { - GNUNET_break (0); - gc->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED, - "record challenge payment"); - gc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; - return; - } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Setup fresh order, creating payment request\n"); - make_payment_request (gc); -} - - -/** - * Callback to process a GET /check-payment request - * - * @param cls our `struct GetContext` - * @param hr HTTP response details - * @param osr order status - */ -static void -check_payment_cb (void *cls, - const struct TALER_MERCHANT_HttpResponse *hr, - const struct TALER_MERCHANT_OrderStatusResponse *osr) - -{ - struct GetContext *gc = cls; - - gc->cpo = NULL; - GNUNET_assert (gc->in_list); - GNUNET_CONTAINER_DLL_remove (gc_head, - gc_tail, - gc); - gc->in_list = false; - GNUNET_assert (gc->suspended); - gc->suspended = false; - MHD_resume_connection (gc->connection); - AH_trigger_daemon (NULL); - - switch (hr->http_status) - { - case MHD_HTTP_OK: - GNUNET_assert (NULL != osr); - break; - case MHD_HTTP_NOT_FOUND: - /* We created this order before, how can it be not found now? */ - GNUNET_break (0); - gc->resp = TALER_MHD_make_error (TALER_EC_ANASTASIS_TRUTH_ORDER_DISAPPEARED, - NULL); - gc->response_code = MHD_HTTP_BAD_GATEWAY; - return; - case MHD_HTTP_BAD_GATEWAY: - gc->resp = TALER_MHD_make_error ( - TALER_EC_ANASTASIS_TRUTH_BACKEND_EXCHANGE_BAD, - NULL); - gc->response_code = MHD_HTTP_BAD_GATEWAY; - return; - case MHD_HTTP_GATEWAY_TIMEOUT: - gc->resp = TALER_MHD_make_error (TALER_EC_ANASTASIS_GENERIC_BACKEND_TIMEOUT, - "Timeout check payment status"); - GNUNET_assert (NULL != gc->resp); - gc->response_code = MHD_HTTP_GATEWAY_TIMEOUT; - return; - default: - { - char status[14]; - - GNUNET_snprintf (status, - sizeof (status), - "%u", - hr->http_status); - gc->resp = TALER_MHD_make_error ( - TALER_EC_ANASTASIS_TRUTH_UNEXPECTED_PAYMENT_STATUS, - status); - GNUNET_assert (NULL != gc->resp); - gc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; - return; - } - } - - switch (osr->status) - { - case TALER_MERCHANT_OSC_PAID: - { - enum GNUNET_DB_QueryStatus qs; - - qs = db->update_challenge_payment (db->cls, - &gc->truth_uuid, - &gc->payment_identifier); - if (0 <= qs) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Order has been paid, continuing with request processing\n"); - return; /* continue as planned */ - } - GNUNET_break (0); - gc->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED, - "update challenge payment"); - gc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; - return; /* continue as planned */ - } - case TALER_MERCHANT_OSC_CLAIMED: - case TALER_MERCHANT_OSC_UNPAID: - /* repeat payment request */ - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Order remains unpaid, sending payment request again\n"); - make_payment_request (gc); - return; - } - /* should never get here */ - GNUNET_break (0); -} - - -/** - * Helper function used to ask our backend to begin processing a - * payment for the user's account. May perform asynchronous - * operations by suspending the connection if required. - * - * @param gc context to begin payment for. - * @return MHD status code - */ -static MHD_RESULT -begin_payment (struct GetContext *gc) -{ - enum GNUNET_DB_QueryStatus qs; - char *order_id; - - qs = db->lookup_challenge_payment (db->cls, - &gc->truth_uuid, - &gc->payment_identifier); - if (qs < 0) - { - GNUNET_break (0); - return TALER_MHD_reply_with_error (gc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "lookup challenge payment"); - } - GNUNET_assert (! gc->in_list); - gc->in_list = true; - GNUNET_CONTAINER_DLL_insert (gc_tail, - gc_head, - gc); - GNUNET_assert (! gc->suspended); - gc->suspended = true; - MHD_suspend_connection (gc->connection); - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) - { - /* We already created the order, check if it was paid */ - struct GNUNET_TIME_Relative timeout; - - order_id = GNUNET_STRINGS_data_to_string_alloc ( - &gc->payment_identifier, - sizeof (gc->payment_identifier)); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Order exists, checking payment status for order `%s'\n", - order_id); - timeout = GNUNET_TIME_absolute_get_remaining (gc->timeout); - gc->cpo = TALER_MERCHANT_merchant_order_get (AH_ctx, - AH_backend_url, - order_id, - NULL /* NOT session-bound */, - false, - timeout, - &check_payment_cb, - gc); - } - else - { - /* Create a fresh order */ - json_t *order; - struct GNUNET_TIME_Timestamp pay_deadline; - - GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, - &gc->payment_identifier, - sizeof (struct ANASTASIS_PaymentSecretP)); - order_id = GNUNET_STRINGS_data_to_string_alloc ( - &gc->payment_identifier, - sizeof (gc->payment_identifier)); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Creating fresh order `%s'\n", - order_id); - pay_deadline = GNUNET_TIME_relative_to_timestamp ( - ANASTASIS_CHALLENGE_OFFER_LIFETIME); - order = GNUNET_JSON_PACK ( - TALER_JSON_pack_amount ("amount", - &gc->challenge_cost), - GNUNET_JSON_pack_string ("summary", - "challenge fee for anastasis service"), - GNUNET_JSON_pack_string ("order_id", - order_id), - GNUNET_JSON_pack_time_rel ("auto_refund", - AUTO_REFUND_TIMEOUT), - GNUNET_JSON_pack_timestamp ("pay_deadline", - pay_deadline)); - gc->po = TALER_MERCHANT_orders_post2 (AH_ctx, - AH_backend_url, - order, - AUTO_REFUND_TIMEOUT, - NULL, /* no payment target */ - 0, - NULL, /* no inventory products */ - 0, - NULL, /* no uuids */ - false, /* do NOT require claim token */ - &proposal_cb, - gc); - json_decref (order); - } - GNUNET_free (order_id); - AH_trigger_curl (); - return MHD_YES; -} - - -/** - * Load encrypted keyshare from db and return it to the client. - * - * @param truth_uuid UUID to the truth for the looup - * @param connection the connection to respond upon - * @return MHD status code - */ -static MHD_RESULT -return_key_share ( - const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, - struct MHD_Connection *connection) -{ - struct ANASTASIS_CRYPTO_EncryptedKeyShareP encrypted_keyshare; - - { - enum GNUNET_DB_QueryStatus qs; - - qs = db->get_key_share (db->cls, - truth_uuid, - &encrypted_keyshare); - switch (qs) - { - case GNUNET_DB_STATUS_HARD_ERROR: - case GNUNET_DB_STATUS_SOFT_ERROR: - GNUNET_break (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "get key share"); - case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_ANASTASIS_TRUTH_KEY_SHARE_GONE, - NULL); - case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: - break; - } - } - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Returning key share\n"); - { - struct MHD_Response *resp; - MHD_RESULT ret; - - resp = MHD_create_response_from_buffer (sizeof (encrypted_keyshare), - &encrypted_keyshare, - MHD_RESPMEM_MUST_COPY); - TALER_MHD_add_global_headers (resp); - ret = MHD_queue_response (connection, - MHD_HTTP_OK, - resp); - MHD_destroy_response (resp); - return ret; - } -} - - -/** - * Mark @a gc as suspended and update the respective - * data structures and jobs. - * - * @param[in,out] gc context of the suspended operation - */ -static void -gc_suspended (struct GetContext *gc) -{ - gc->suspended = true; - if (NULL == AH_to_heap) - AH_to_heap = GNUNET_CONTAINER_heap_create ( - GNUNET_CONTAINER_HEAP_ORDER_MIN); - gc->hn = GNUNET_CONTAINER_heap_insert (AH_to_heap, - gc, - gc->timeout.abs_value_us); - if (NULL != to_task) - { - GNUNET_SCHEDULER_cancel (to_task); - to_task = NULL; - } - { - struct GetContext *rn; - - rn = GNUNET_CONTAINER_heap_peek (AH_to_heap); - to_task = GNUNET_SCHEDULER_add_at (rn->timeout, - &do_timeout, - NULL); - } -} - - -/** - * Run the authorization method-specific 'process' function and continue - * based on its result with generating an HTTP response. - * - * @param connection the connection we are handling - * @param gc our overall handler context - */ -static MHD_RESULT -run_authorization_process (struct MHD_Connection *connection, - struct GetContext *gc) -{ - enum ANASTASIS_AUTHORIZATION_Result ret; - enum GNUNET_DB_QueryStatus qs; - - GNUNET_assert (! gc->suspended); - ret = gc->authorization->process (gc->as, - gc->timeout, - connection); - switch (ret) - { - case ANASTASIS_AUTHORIZATION_RES_SUCCESS: - /* Challenge sent successfully */ - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Authorization request sent successfully\n"); - qs = db->mark_challenge_sent (db->cls, - &gc->payment_identifier, - &gc->truth_uuid, - gc->code); - GNUNET_break (0 < qs); - gc->authorization->cleanup (gc->as); - gc->as = NULL; - return MHD_YES; - case ANASTASIS_AUTHORIZATION_RES_FAILED: - if (gc->payment_identifier_provided) - { - begin_refund (gc); - } - gc->authorization->cleanup (gc->as); - gc->as = NULL; - return MHD_YES; - case ANASTASIS_AUTHORIZATION_RES_SUSPENDED: - /* connection was suspended */ - gc_suspended (gc); - return MHD_YES; - case ANASTASIS_AUTHORIZATION_RES_SUCCESS_REPLY_FAILED: - /* Challenge sent successfully */ - qs = db->mark_challenge_sent (db->cls, - &gc->payment_identifier, - &gc->truth_uuid, - gc->code); - GNUNET_break (0 < qs); - gc->authorization->cleanup (gc->as); - gc->as = NULL; - return MHD_NO; - case ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED: - gc->authorization->cleanup (gc->as); - gc->as = NULL; - return MHD_NO; - case ANASTASIS_AUTHORIZATION_RES_FINISHED: - GNUNET_assert (! gc->suspended); - gc->authorization->cleanup (gc->as); - gc->as = NULL; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Resuming with authorization successful!\n"); - if (gc->in_list) - { - GNUNET_CONTAINER_DLL_remove (gc_head, - gc_tail, - gc); - gc->in_list = false; - } - return MHD_YES; - } - GNUNET_break (0); - return MHD_NO; -} - - -/** - * Use the database to rate-limit queries to the authentication - * procedure, but without actually storing 'real' challenge codes. - * - * @param[in,out] gc context to rate limit requests for - * @return #GNUNET_OK if rate-limiting passes, - * #GNUNET_NO if a reply was sent (rate limited) - * #GNUNET_SYSERR if we failed and no reply - * was queued - */ -static enum GNUNET_GenericReturnValue -rate_limit (struct GetContext *gc) -{ - enum GNUNET_DB_QueryStatus qs; - struct GNUNET_TIME_Timestamp rt; - uint64_t code; - enum ANASTASIS_DB_CodeStatus cs; - struct GNUNET_HashCode hc; - bool satisfied; - uint64_t dummy; - - rt = GNUNET_TIME_UNIT_FOREVER_TS; - qs = db->create_challenge_code (db->cls, - &gc->truth_uuid, - MAX_QUESTION_FREQ, - GNUNET_TIME_UNIT_HOURS, - INITIAL_RETRY_COUNTER, - &rt, - &code); - if (0 > qs) - { - GNUNET_break (0 < qs); - return (MHD_YES == - TALER_MHD_reply_with_error (gc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "create_challenge_code (for rate limiting)")) - ? GNUNET_NO - : GNUNET_SYSERR; - } - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) - { - return (MHD_YES == - reply_rate_limited (gc)) - ? GNUNET_NO - : GNUNET_SYSERR; - } - /* decrement trial counter */ - ANASTASIS_hash_answer (code + 1, /* always use wrong answer */ - &hc); - cs = db->verify_challenge_code (db->cls, - &gc->truth_uuid, - &hc, - &dummy, - &satisfied); - switch (cs) - { - case ANASTASIS_DB_CODE_STATUS_CHALLENGE_CODE_MISMATCH: - /* good, what we wanted */ - return GNUNET_OK; - case ANASTASIS_DB_CODE_STATUS_HARD_ERROR: - case ANASTASIS_DB_CODE_STATUS_SOFT_ERROR: - GNUNET_break (0); - return (MHD_YES == - TALER_MHD_reply_with_error (gc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "verify_challenge_code")) - ? GNUNET_NO - : GNUNET_SYSERR; - case ANASTASIS_DB_CODE_STATUS_NO_RESULTS: - return (MHD_YES == - reply_rate_limited (gc)) - ? GNUNET_NO - : GNUNET_SYSERR; - case ANASTASIS_DB_CODE_STATUS_VALID_CODE_STORED: - /* this should be impossible, we used code+1 */ - GNUNET_assert (0); - } - return GNUNET_SYSERR; -} - - -/** - * Handle special case of a security question where we do not - * generate a code. Rate limits answers against brute forcing. - * - * @param[in,out] gc request to handle - * @param decrypted_truth hash to check against - * @param decrypted_truth_size number of bytes in @a decrypted_truth - * @return MHD status code - */ -static MHD_RESULT -handle_security_question (struct GetContext *gc, - const void *decrypted_truth, - size_t decrypted_truth_size) -{ - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Handling security question challenge\n"); - if (! gc->have_response) - { - return TALER_MHD_reply_with_error (gc->connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED, - NULL); - } - /* rate limit */ - { - enum GNUNET_GenericReturnValue ret; - - ret = rate_limit (gc); - if (GNUNET_OK != ret) - return (GNUNET_NO == ret) ? MHD_YES : MHD_NO; - } - /* check reply matches truth */ - if ( (decrypted_truth_size != sizeof (struct GNUNET_HashCode)) || - (0 != memcmp (&gc->challenge_response, - decrypted_truth, - decrypted_truth_size)) ) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Wrong answer provided to secure question had %u bytes, wanted %u\n", - (unsigned int) decrypted_truth_size, - (unsigned int) sizeof (struct GNUNET_HashCode)); - return TALER_MHD_reply_with_error (gc->connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_ANASTASIS_TRUTH_CHALLENGE_FAILED, - NULL); - } - /* good, return the key share */ - return return_key_share (&gc->truth_uuid, - gc->connection); -} - - -/** - * Handle special case of an answer being directly checked by the - * plugin and not by our database. Rate limits answers against brute - * forcing. - * - * @param[in,out] gc request to handle - * @param decrypted_truth hash to check against - * @param decrypted_truth_size number of bytes in @a decrypted_truth - * @return MHD status code - */ -static MHD_RESULT -direct_validation (struct GetContext *gc, - const void *decrypted_truth, - size_t decrypted_truth_size) -{ - /* Non-random code, call plugin directly! */ - enum ANASTASIS_AUTHORIZATION_Result aar; - enum GNUNET_GenericReturnValue res; - - res = rate_limit (gc); - if (GNUNET_OK != res) - return (GNUNET_NO == res) ? MHD_YES : MHD_NO; - gc->as = gc->authorization->start (gc->authorization->cls, - &AH_trigger_daemon, - NULL, - &gc->truth_uuid, - 0LLU, - decrypted_truth, - decrypted_truth_size); - if (NULL == gc->as) - { - GNUNET_break (0); - return TALER_MHD_reply_with_error (gc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_START_FAILED, - NULL); - } - aar = gc->authorization->process (gc->as, - GNUNET_TIME_UNIT_ZERO_ABS, - gc->connection); - switch (aar) - { - case ANASTASIS_AUTHORIZATION_RES_SUCCESS: - GNUNET_break (0); - return MHD_YES; - case ANASTASIS_AUTHORIZATION_RES_FAILED: - return MHD_YES; - case ANASTASIS_AUTHORIZATION_RES_SUSPENDED: - gc_suspended (gc); - return MHD_YES; - case ANASTASIS_AUTHORIZATION_RES_SUCCESS_REPLY_FAILED: - GNUNET_break (0); - return MHD_NO; - case ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED: - return MHD_NO; - case ANASTASIS_AUTHORIZATION_RES_FINISHED: - return return_key_share (&gc->truth_uuid, - gc->connection); - } - GNUNET_break (0); - return MHD_NO; -} - - -MHD_RESULT -AH_handler_truth_get ( - struct MHD_Connection *connection, - const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, - struct TM_HandlerContext *hc) -{ - struct GetContext *gc = hc->ctx; - void *encrypted_truth; - size_t encrypted_truth_size; - void *decrypted_truth; - size_t decrypted_truth_size; - char *truth_mime = NULL; - bool is_question; - - if (NULL == gc) - { - /* Fresh request, do initial setup */ - gc = GNUNET_new (struct GetContext); - gc->hc = hc; - hc->ctx = gc; - gc->connection = connection; - gc->truth_uuid = *truth_uuid; - gc->hc->cc = &request_done; - { - const char *pay_id; - - pay_id = MHD_lookup_connection_value (connection, - MHD_HEADER_KIND, - ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER); - if (NULL != pay_id) - { - if (GNUNET_OK != - GNUNET_STRINGS_string_to_data ( - pay_id, - strlen (pay_id), - &gc->payment_identifier, - sizeof (struct ANASTASIS_PaymentSecretP))) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER); - } - gc->payment_identifier_provided = true; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Client provided payment identifier `%s'\n", - pay_id); - } - } - - { - /* check if header contains Truth-Decryption-Key */ - const char *tdk; - - tdk = MHD_lookup_connection_value (connection, - MHD_HEADER_KIND, - ANASTASIS_HTTP_HEADER_TRUTH_DECRYPTION_KEY); - if (NULL == tdk) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MISSING, - ANASTASIS_HTTP_HEADER_TRUTH_DECRYPTION_KEY); - } - - if (GNUNET_OK != - GNUNET_STRINGS_string_to_data ( - tdk, - strlen (tdk), - &gc->truth_key, - sizeof (struct ANASTASIS_CRYPTO_TruthKeyP))) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - ANASTASIS_HTTP_HEADER_TRUTH_DECRYPTION_KEY); - } - } - - { - const char *challenge_response_s; - - challenge_response_s = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "response"); - if ( (NULL != challenge_response_s) && - (GNUNET_OK != - GNUNET_CRYPTO_hash_from_string (challenge_response_s, - &gc->challenge_response)) ) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "response"); - } - gc->have_response = (NULL != challenge_response_s); - } - - { - const char *long_poll_timeout_ms; - - long_poll_timeout_ms = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "timeout_ms"); - if (NULL != long_poll_timeout_ms) - { - unsigned int timeout; - char dummy; - - if (1 != sscanf (long_poll_timeout_ms, - "%u%c", - &timeout, - &dummy)) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "timeout_ms (must be non-negative number)"); - } - gc->timeout - = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_relative_multiply ( - GNUNET_TIME_UNIT_MILLISECONDS, - timeout)); - } - else - { - gc->timeout = GNUNET_TIME_relative_to_absolute ( - GNUNET_TIME_UNIT_SECONDS); - } - } - } /* end of first-time initialization (if NULL == gc) */ - else - { - /* might have been woken up by authorization plugin, - so clear the flag. MDH called us, so we are - clearly no longer suspended */ - gc->suspended = false; - if (NULL != gc->resp) - { - MHD_RESULT ret; - - /* We generated a response asynchronously, queue that */ - ret = MHD_queue_response (connection, - gc->response_code, - gc->resp); - GNUNET_break (MHD_YES == ret); - MHD_destroy_response (gc->resp); - gc->resp = NULL; - return ret; - } - if (NULL != gc->as) - { - /* Authorization process is "running", check what is going on */ - GNUNET_assert (NULL != gc->authorization); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Continuing with running the authorization process\n"); - GNUNET_assert (! gc->suspended); - return run_authorization_process (connection, - gc); - - } - /* We get here if the async check for payment said this request - was indeed paid! */ - } - - { - /* load encrypted truth from DB */ - enum GNUNET_DB_QueryStatus qs; - char *method; - - qs = db->get_escrow_challenge (db->cls, - &gc->truth_uuid, - &encrypted_truth, - &encrypted_truth_size, - &truth_mime, - &method); - switch (qs) - { - case GNUNET_DB_STATUS_HARD_ERROR: - case GNUNET_DB_STATUS_SOFT_ERROR: - GNUNET_break (0); - return TALER_MHD_reply_with_error (gc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "get escrow challenge"); - case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_ANASTASIS_TRUTH_UNKNOWN, - NULL); - case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: - break; - } - is_question = (0 == strcmp ("question", - method)); - if (! is_question) - { - gc->authorization - = ANASTASIS_authorization_plugin_load (method, - db, - AH_cfg); - if (NULL == gc->authorization) - { - MHD_RESULT ret; - - ret = TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_METHOD_NO_LONGER_SUPPORTED, - method); - GNUNET_free (encrypted_truth); - GNUNET_free (truth_mime); - GNUNET_free (method); - return ret; - } - gc->challenge_cost = gc->authorization->cost; - } - else - { - gc->challenge_cost = AH_question_cost; - } - GNUNET_free (method); - } - - if ( (is_question) || - (! gc->authorization->payment_plugin_managed) ) - { - if (! TALER_amount_is_zero (&gc->challenge_cost)) - { - /* Check database to see if the transaction is paid for */ - enum GNUNET_DB_QueryStatus qs; - bool paid; - - if (! gc->payment_identifier_provided) - { - GNUNET_free (truth_mime); - GNUNET_free (encrypted_truth); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Beginning payment, client did not provide payment identifier\n"); - return begin_payment (gc); - } - qs = db->check_challenge_payment (db->cls, - &gc->payment_identifier, - &gc->truth_uuid, - &paid); - switch (qs) - { - case GNUNET_DB_STATUS_HARD_ERROR: - case GNUNET_DB_STATUS_SOFT_ERROR: - GNUNET_break (0); - GNUNET_free (truth_mime); - GNUNET_free (encrypted_truth); - return TALER_MHD_reply_with_error (gc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "check challenge payment"); - case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - /* Create fresh payment identifier (cannot trust client) */ - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Client-provided payment identifier is unknown.\n"); - GNUNET_free (truth_mime); - GNUNET_free (encrypted_truth); - return begin_payment (gc); - case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: - if (! paid) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Payment identifier known. Checking payment with client's payment identifier\n"); - GNUNET_free (truth_mime); - GNUNET_free (encrypted_truth); - return begin_payment (gc); - } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Payment confirmed\n"); - break; - } - } - else - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Request is free of charge\n"); - } - } - - /* We've been paid, now validate response */ - { - /* decrypt encrypted_truth */ - ANASTASIS_CRYPTO_truth_decrypt (&gc->truth_key, - encrypted_truth, - encrypted_truth_size, - &decrypted_truth, - &decrypted_truth_size); - GNUNET_free (encrypted_truth); - } - if (NULL == decrypted_truth) - { - GNUNET_free (truth_mime); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_EXPECTATION_FAILED, - TALER_EC_ANASTASIS_TRUTH_DECRYPTION_FAILED, - NULL); - } - - /* Special case for secure question: we do not generate a numeric challenge, - but check that the hash matches */ - if (is_question) - { - MHD_RESULT ret; - - ret = handle_security_question (gc, - decrypted_truth, - decrypted_truth_size); - GNUNET_free (truth_mime); - GNUNET_free (decrypted_truth); - return ret; - } - - /* Not security question, check for answer in DB */ - if (gc->have_response) - { - enum ANASTASIS_DB_CodeStatus cs; - bool satisfied; - uint64_t code; - - GNUNET_free (truth_mime); - if (gc->authorization->user_provided_code) - { - MHD_RESULT res; - - res = direct_validation (gc, - decrypted_truth, - decrypted_truth_size); - GNUNET_free (decrypted_truth); - return res; - } - - /* random code, check against database */ - cs = db->verify_challenge_code (db->cls, - &gc->truth_uuid, - &gc->challenge_response, - &code, - &satisfied); - switch (cs) - { - case ANASTASIS_DB_CODE_STATUS_CHALLENGE_CODE_MISMATCH: - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Provided response does not match our stored challenge\n"); - GNUNET_free (decrypted_truth); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_ANASTASIS_TRUTH_CHALLENGE_FAILED, - NULL); - case ANASTASIS_DB_CODE_STATUS_HARD_ERROR: - case ANASTASIS_DB_CODE_STATUS_SOFT_ERROR: - GNUNET_break (0); - GNUNET_free (decrypted_truth); - return TALER_MHD_reply_with_error (gc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "verify_challenge_code"); - case ANASTASIS_DB_CODE_STATUS_NO_RESULTS: - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Response code unknown (possibly expired). Testing if we may provide a new one.\n"); - gc->have_response = false; - break; - case ANASTASIS_DB_CODE_STATUS_VALID_CODE_STORED: - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Response code valid (%s)\n", - satisfied ? "satisfied" : "unsatisfied"); - if (satisfied) - { - GNUNET_free (decrypted_truth); - return return_key_share (&gc->truth_uuid, - connection); - } - /* continue with authorization plugin below */ - gc->code = code; - break; - default: - GNUNET_break (0); - return MHD_NO; - } - } - if (! gc->have_response) - { - /* Not security question and no answer: use plugin to check if - decrypted truth is a valid challenge! */ - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "No challenge provided, creating fresh challenge\n"); - { - enum GNUNET_GenericReturnValue ret; - - ret = gc->authorization->validate (gc->authorization->cls, - connection, - truth_mime, - decrypted_truth, - decrypted_truth_size); - GNUNET_free (truth_mime); - switch (ret) - { - case GNUNET_OK: - /* data valid, continued below */ - break; - case GNUNET_NO: - /* data invalid, reply was queued */ - GNUNET_free (decrypted_truth); - return MHD_YES; - case GNUNET_SYSERR: - /* data invalid, reply was NOT queued */ - GNUNET_free (decrypted_truth); - return MHD_NO; - } - } - - /* Setup challenge and begin authorization process */ - { - struct GNUNET_TIME_Timestamp transmission_date; - enum GNUNET_DB_QueryStatus qs; - - qs = db->create_challenge_code (db->cls, - &gc->truth_uuid, - gc->authorization->code_rotation_period, - gc->authorization->code_validity_period, - gc->authorization->retry_counter, - &transmission_date, - &gc->code); - switch (qs) - { - case GNUNET_DB_STATUS_HARD_ERROR: - case GNUNET_DB_STATUS_SOFT_ERROR: - GNUNET_break (0); - GNUNET_free (decrypted_truth); - return TALER_MHD_reply_with_error (gc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "create_challenge_code"); - case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - /* 0 == retry_counter of existing challenge => rate limit exceeded */ - GNUNET_free (decrypted_truth); - return reply_rate_limited (gc); - case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: - /* challenge code was stored successfully*/ - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Created fresh challenge\n"); - break; - } - - if (GNUNET_TIME_relative_cmp ( - GNUNET_TIME_absolute_get_duration ( - transmission_date.abs_time), - <, - gc->authorization->code_retransmission_frequency) ) - { - /* Too early for a retransmission! */ - GNUNET_free (decrypted_truth); - return TALER_MHD_reply_with_error (gc->connection, - MHD_HTTP_ALREADY_REPORTED, - TALER_EC_ANASTASIS_TRUTH_CHALLENGE_ACTIVE, - NULL); - } - } - } - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Beginning authorization process\n"); - gc->as = gc->authorization->start (gc->authorization->cls, - &AH_trigger_daemon, - NULL, - &gc->truth_uuid, - gc->code, - decrypted_truth, - decrypted_truth_size); - GNUNET_free (decrypted_truth); - if (NULL == gc->as) - { - GNUNET_break (0); - return TALER_MHD_reply_with_error (gc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_START_FAILED, - NULL); - } - if (! gc->in_list) - { - gc->in_list = true; - GNUNET_CONTAINER_DLL_insert (gc_head, - gc_tail, - gc); - } - GNUNET_assert (! gc->suspended); - return run_authorization_process (connection, - gc); -} diff --git a/src/backend/anastasis-httpd_truth.h b/src/backend/anastasis-httpd_truth.h index d0851ba..a436394 100644 --- a/src/backend/anastasis-httpd_truth.h +++ b/src/backend/anastasis-httpd_truth.h @@ -1,6 +1,6 @@ /* This file is part of Anastasis - Copyright (C) 2014, 2015, 2016, 2021 Anastasis SARL + Copyright (C) 2020-2022 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software @@ -24,14 +24,6 @@ #define ANASTASIS_HTTPD_TRUTH_H #include <microhttpd.h> - -/** - * Prepare all active GET truth requests for system shutdown. - */ -void -AH_truth_shutdown (void); - - /** * Prepare all active POST truth solve requests for system shutdown. */ @@ -53,21 +45,6 @@ AH_truth_upload_shutdown (void); /** - * Handle a GET to /truth/$UUID - * - * @param[in,out] connection the MHD connection to handle - * @param truth_uuid the truth UUID - * @param[in,out] hc connection context - * @return MHD result code - */ -MHD_RESULT -AH_handler_truth_get ( - struct MHD_Connection *connection, - const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, - struct TM_HandlerContext *hc); - - -/** * Handle a POST to /truth/$UUID. * * @param[in,out] connection the MHD connection to handle diff --git a/src/include/anastasis.h b/src/include/anastasis.h index 92c0745..90f3f5d 100644 --- a/src/include/anastasis.h +++ b/src/include/anastasis.h @@ -97,61 +97,38 @@ ANASTASIS_challenge_get_details (struct ANASTASIS_Challenge *challenge); /** * Possible outcomes of trying to start a challenge operation. */ -enum ANASTASIS_ChallengeStatus +enum ANASTASIS_ChallengeStartStatus { /** - * The challenge has been solved. - */ - ANASTASIS_CHALLENGE_STATUS_SOLVED, - - /** - * Instructions for how to solve the challenge are provided. Also - * used if the answer we provided was wrong (or if no answer was - * provided, but one is needed). - */ - ANASTASIS_CHALLENGE_STATUS_INSTRUCTIONS, - - /** - * A redirection URL needed to solve the challenge is provided. Also - * used if the answer we provided was wrong (or if no answer was - * provided, but one is needed). + * We encountered an error talking to the Anastasis service. */ - ANASTASIS_CHALLENGE_STATUS_REDIRECT_FOR_AUTHENTICATION, + ANASTASIS_CHALLENGE_START_STATUS_SERVER_FAILURE, /** * Payment is required before the challenge can be answered. */ - ANASTASIS_CHALLENGE_STATUS_PAYMENT_REQUIRED, - - /** - * We encountered an error talking to the Anastasis service. - */ - ANASTASIS_CHALLENGE_STATUS_SERVER_FAILURE, + ANASTASIS_CHALLENGE_START_STATUS_PAYMENT_REQUIRED, /** * The server does not know this truth. */ - ANASTASIS_CHALLENGE_STATUS_TRUTH_UNKNOWN, + ANASTASIS_CHALLENGE_START_STATUS_TRUTH_UNKNOWN, /** - * The rate limit for solving the challenge was exceeded. + * A filename with the TAN has been provided. */ - ANASTASIS_CHALLENGE_STATUS_RATE_LIMIT_EXCEEDED, + ANASTASIS_CHALLENGE_START_STATUS_FILENAME_PROVIDED, /** - * The user did not satisfy the (external) authentication - * challenge in time. The request should be repeated - * later and may then succeed. + * A TAN has been send, address hint is provided. */ - ANASTASIS_CHALLENGE_STATUS_AUTH_TIMEOUT, + ANASTASIS_CHALLENGE_START_STATUS_TAN_SENT_HINT_PROVIDED, /** - * Plugin-specific ("external") instructions for how to solve the - * challenge are provided. + * Wire transfer required, banking details provided. */ - ANASTASIS_CHALLENGE_STATUS_EXTERNAL_INSTRUCTIONS - + ANASTASIS_CHALLENGE_START_STATUS_BANK_TRANSFER_REQUIRED }; @@ -161,10 +138,21 @@ enum ANASTASIS_ChallengeStatus */ struct ANASTASIS_ChallengeStartResponse { + + /** + * HTTP status returned by the server. + */ + unsigned int http_status; + + /** + * Taler-specific error code. + */ + enum TALER_ErrorCode ec; + /** * What is our status on satisfying this challenge. Determines @e details. */ - enum ANASTASIS_ChallengeStatus cs; + enum ANASTASIS_ChallengeStartStatus cs; /** * Which challenge is this about? @@ -179,36 +167,159 @@ struct ANASTASIS_ChallengeStartResponse /** * Challenge details provided if - * @e cs is #ANASTASIS_CHALLENGE_STATUS_INSTRUCTIONS + * @e cs is #ANASTASIS_CHALLENGE_START_STATUS_FILENAME_PROVIDED. + */ + const char *tan_filename; + + /** + * Challenge details provided if + * @e cs is #ANASTASIS_CHALLENGE_START_STATUS_TAN_SENT_HINT_PROVIDED. + */ + const char *tan_address_hint; + + /** + * Challenge details provided if + * @e cs is #ANASTASIS_CHALLENGE_START_STATUS_BANK_TRANSFER_REQUIRED. + */ + struct ANASTASIS_WireFundsDetails bank_transfer_required; + + /** + * Response with instructions for how to pay, if + * @e cs is #ANASTASIS_CHALLENGE_START_STATUS_PAYMENT_REQUIRED. */ struct { /** - * Response with server-side instructions for the user. + * "taler://pay" URI with details how to pay for the challenge. */ - const void *body; + const char *taler_pay_uri; /** - * Mime type of the data in @e body. + * Payment secret from @e taler_pay_uri. */ - const char *content_type; + struct ANASTASIS_PaymentSecretP payment_secret; - /** - * Number of bytes in @e body - */ - size_t body_size; + } payment_required; + + } details; +}; - /** - * HTTP status returned by the server. #MHD_HTTP_ALREADY_REPORTED - * if the server did already send the challenge to the user, - * #MHD_HTTP_FORBIDDEN if the answer was wrong (or missing). - */ - unsigned int http_status; - } open_challenge; + +/** + * Defines a callback for the response status for a challenge start + * operation. + * + * @param cls closure + * @param csr response details + */ +typedef void +(*ANASTASIS_ChallengeStartFeedback)( + void *cls, + const struct ANASTASIS_ChallengeStartResponse *csr); + + +/** + * User starts a challenge which reponds out of bounds (E-Mail, SMS, + * Postal..) If the challenge is zero cost, the challenge + * instructions will be sent to the client. If the challenge needs + * payment a payment link is sent to the client. After payment the + * challenge start method has to be called again. + * + * @param c reference to the escrow challenge which is started + * @param psp payment secret, NULL if no payment was yet made + * @param af reference to the answerfeedback which is passed back to the user + * @param af_cls closure for @a af + * @return #GNUNET_OK if the challenge was successfully started + */ +enum GNUNET_GenericReturnValue +ANASTASIS_challenge_start (struct ANASTASIS_Challenge *c, + const struct ANASTASIS_PaymentSecretP *psp, + ANASTASIS_ChallengeStartFeedback af, + void *af_cls); + + +/** + * Possible outcomes of trying to start a challenge operation. + */ +enum ANASTASIS_ChallengeAnswerStatus +{ + + /** + * The challenge has been solved. + */ + ANASTASIS_CHALLENGE_ANSWER_STATUS_SOLVED, + + /** + * Payment is required before the challenge can be answered. + */ + ANASTASIS_CHALLENGE_ANSWER_STATUS_PAYMENT_REQUIRED, + + /** + * We encountered an error talking to the Anastasis service. + */ + ANASTASIS_CHALLENGE_ANSWER_STATUS_SERVER_FAILURE, + + /** + * The server does not know this truth. + */ + ANASTASIS_CHALLENGE_ANSWER_STATUS_TRUTH_UNKNOWN, + + /** + * The answer was wrong. + */ + ANASTASIS_CHALLENGE_ANSWER_STATUS_INVALID_ANSWER, + + /** + * The rate limit for solving the challenge was exceeded. + */ + ANASTASIS_CHALLENGE_ANSWER_STATUS_RATE_LIMIT_EXCEEDED, + + /** + * The user did not satisfy the (external) authentication + * challenge in time. The request should be repeated + * later and may then succeed. + */ + ANASTASIS_CHALLENGE_ANSWER_STATUS_AUTH_TIMEOUT + + +}; + + +/** + * Response from an #ANASTASIS_challenge_start() operation. + */ +struct ANASTASIS_ChallengeAnswerResponse +{ + + /** + * HTTP status returned by the server. + */ + unsigned int http_status; + + /** + * Taler-specific error code. + */ + enum TALER_ErrorCode ec; + + /** + * What is our status on satisfying this challenge. Determines @e details. + */ + enum ANASTASIS_ChallengeAnswerStatus cs; + + /** + * Which challenge is this about? + */ + struct ANASTASIS_Challenge *challenge; + + /** + * Details depending on @e cs + */ + union + { /** - * Details for #ANASTASIS_CHALLENGE_STATUS_RATE_LIMIT_EXCEEDED. + * Details for #ANASTASIS_CHALLENGE_ANSWER_STATUS_RATE_LIMIT_EXCEEDED. */ struct { @@ -227,20 +338,8 @@ struct ANASTASIS_ChallengeStartResponse } rate_limit_exceeded; /** - * Response with details if - * @e cs is #ANASTASIS_CHALLENGE_STATUS_EXTERNAL_INSTRUCTIONS. - */ - const json_t *external_challenge; - - /** - * Response with URL to redirect the user to, if - * @e cs is #ANASTASIS_CHALLENGE_STATUS_REDIRECT_FOR_AUTHENTICATION. - */ - const char *redirect_url; - - /** * Response with instructions for how to pay, if - * @e cs is #ANASTASIS_CHALLENGE_STATUS_PAYMENT_REQUIRED. + * @e cs is #ANASTASIS_CHALLENGE_ANSWER_STATUS_PAYMENT_REQUIRED. */ struct { @@ -257,26 +356,6 @@ struct ANASTASIS_ChallengeStartResponse } payment_required; - - /** - * Response with details about a server-side failure, if - * @e cs is #ANASTASIS_CHALLENGE_STATUS_SERVER_FAILURE. - */ - struct - { - - /** - * HTTP status returned by the server. - */ - unsigned int http_status; - - /** - * Taler-specific error code. - */ - enum TALER_ErrorCode ec; - - } server_failure; - } details; }; @@ -286,36 +365,12 @@ struct ANASTASIS_ChallengeStartResponse * operation. * * @param cls closure - * @param csr response details + * @param car response details */ typedef void (*ANASTASIS_AnswerFeedback)( void *cls, - const struct ANASTASIS_ChallengeStartResponse *csr); - - -/** - * User starts a challenge which reponds out of bounds (E-Mail, SMS, - * Postal..) If the challenge is zero cost, the challenge - * instructions will be sent to the client. If the challenge needs - * payment a payment link is sent to the client. After payment the - * challenge start method has to be called again. - * - * @param c reference to the escrow challenge which is started - * @param psp payment secret, NULL if no payment was yet made - * @param timeout how long to wait for payment - * @param hashed_answer answer to the challenge, NULL if we have none yet - * @param af reference to the answerfeedback which is passed back to the user - * @param af_cls closure for @a af - * @return #GNUNET_OK if the challenge was successfully started - */ -enum GNUNET_GenericReturnValue -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); + const struct ANASTASIS_ChallengeAnswerResponse *car); /** @@ -328,8 +383,8 @@ ANASTASIS_challenge_start (struct ANASTASIS_Challenge *c, * @param psp information about payment made for the recovery * @param timeout how long to wait for payment * @param answer user input instruction defines which input is needed - * @param af reference to the answerfeedback which is passed back to the user - * @param af_cls closure for @a af + * @param csf function to call with the result + * @param csf_cls closure for @a csf * @return #GNUNET_OK on success */ enum GNUNET_GenericReturnValue @@ -337,8 +392,8 @@ ANASTASIS_challenge_answer (struct ANASTASIS_Challenge *c, const struct ANASTASIS_PaymentSecretP *psp, struct GNUNET_TIME_Relative timeout, const char *answer, - ANASTASIS_AnswerFeedback af, - void *af_cls); + ANASTASIS_AnswerFeedback csf, + void *csf_cls); /** @@ -365,6 +420,30 @@ ANASTASIS_challenge_answer2 (struct ANASTASIS_Challenge *c, /** + * User starts a challenge which reponds out of bounds (E-Mail, SMS, + * Postal..) If the challenge is zero cost, the challenge + * instructions will be sent to the client. If the challenge needs + * payment a payment link is sent to the client. After payment the + * challenge start method has to be called again. + * + * @param c reference to the escrow challenge which is started + * @param psp payment secret, NULL if no payment was yet made + * @param timeout how long to wait for payment + * @param hashed_answer answer to the challenge + * @param af reference to the answerfeedback which is passed back to the user + * @param af_cls closure for @a af + * @return #GNUNET_OK if the challenge was successfully started + */ +enum GNUNET_GenericReturnValue +ANASTASIS_challenge_answer3 (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); + + +/** * Abort answering challenge. * * @param c reference to the escrow challenge which was started diff --git a/src/include/anastasis_service.h b/src/include/anastasis_service.h index c21cde5..e88b7e0 100644 --- a/src/include/anastasis_service.h +++ b/src/include/anastasis_service.h @@ -517,258 +517,6 @@ ANASTASIS_policy_store_cancel ( /** - * Operational status. - */ -enum ANASTASIS_KeyShareDownloadStatus -{ - /** - * We got the encrypted key share. - */ - ANASTASIS_KSD_SUCCESS = 0, - - /** - * Payment is needed to proceed with the recovery. - */ - ANASTASIS_KSD_PAYMENT_REQUIRED, - - /** - * The provided answer was wrong or missing. Instructions for - * getting a good answer may be provided. - */ - ANASTASIS_KSD_INVALID_ANSWER, - - /** - * To answer the challenge, the client should be redirected to - * the given URL. - */ - ANASTASIS_KSD_REDIRECT_FOR_AUTHENTICATION, - - /** - * The provider had an error. - */ - ANASTASIS_KSD_SERVER_ERROR, - - /** - * The provider claims we made an error. - */ - ANASTASIS_KSD_CLIENT_FAILURE, - - /** - * The provider does not know this truth. - */ - ANASTASIS_KSD_TRUTH_UNKNOWN, - - /** - * Too many attempts to solve the challenge were made in a short - * time. Try again later. - */ - ANASTASIS_KSD_RATE_LIMIT_EXCEEDED, - - /** - * The user did not satisfy the (external) - * authentication check until the request timeout - * was reached. The client should try again later. - */ - ANASTASIS_KSD_AUTHENTICATION_TIMEOUT, - - /** - * The plugin provided external challenge instructions - * that should be followed. They are method-specific. - */ - ANASTASIS_KSD_EXTERNAL_CHALLENGE_INSTRUCTIONS - -}; - - -/** - * Detailed results from the successful download. - */ -struct ANASTASIS_KeyShareDownloadDetails -{ - - /** - * Operational status. - */ - enum ANASTASIS_KeyShareDownloadStatus status; - - /** - * Anastasis URL that returned the @e status. - */ - const char *server_url; - - /** - * Details depending on @e status. - */ - union - { - - /** - * The encrypted key share (if @e status is #ANASTASIS_KSD_SUCCESS). - */ - struct ANASTASIS_CRYPTO_EncryptedKeyShareP eks; - - /** - * Response if the challenge still needs to be answered, and the - * instructions are provided inline (no redirection). - */ - struct - { - - /** - * HTTP status returned by the server. #MHD_HTTP_ALREADY_REPORTED - * if the server did already send the challenge to the user, - * #MHD_HTTP_FORBIDDEN if the answer was wrong (or missing). - */ - unsigned int http_status; - - /** - * Response with server-side reply containing instructions for the user - */ - const char *body; - - /** - * Content-type: mime type of @e body, NULL if server did not provide any. - */ - const char *content_type; - - /** - * Number of bytes in @e body. - */ - size_t body_size; - - } open_challenge; - - /** - * URL with instructions for the user to satisfy the challenge, if - * @e status is #ANASTASIS_KSD_REDIRECT_FOR_AUTHENTICATION. - */ - const char *redirect_url; - - /** - * Response with instructions for how to pay, if - * @e status is #ANASTASIS_KSD_PAYMENT_REQUIRED. - */ - struct - { - - /** - * "taler://pay" URL with details how to pay for the challenge. - */ - const char *taler_pay_uri; - - /** - * The order ID from @e taler_pay_uri. - */ - struct ANASTASIS_PaymentSecretP payment_secret; - - } payment_required; - - - struct - { - - /** - * How many requests are allowed at most per @e request_frequency? - */ - uint32_t request_limit; - - /** - * Frequency at which requests are allowed / new challenges are - * created. - */ - struct GNUNET_TIME_Relative request_frequency; - - } rate_limit_exceeded; - - - /** - * Response with details about a server-side failure, if - * @e status is #ANASTASIS_KSD_SERVER_ERROR, - * #ANASTASIS_KSD_CLIENT_FAILURE or #ANASTASIS_KSD_TRUTH_UNKNOWN. - */ - struct - { - - /** - * HTTP status returned by the server. - */ - unsigned int http_status; - - /** - * Taler-specific error code. - */ - enum TALER_ErrorCode ec; - - } server_failure; - - /** - * External challenge instructions, if @e status is - * #ANASTASIS_KSD_EXTERNAL_CHALLENGE_INSTRUCTIONS. - */ - const json_t *external_challenge; - - } details; -}; - - -/** - * Handle for a GET /truth operation. - */ -struct ANASTASIS_KeyShareLookupOperation; - - -/** - * Callback to process a GET /truth request - * - * @param cls closure - * @param http_status HTTP status code for this request - * @param kdd details about the key share - */ -typedef void -(*ANASTASIS_KeyShareLookupCallback) ( - void *cls, - const struct ANASTASIS_KeyShareDownloadDetails *kdd); - - -/** - * Does a GET /truth. - * - * @param ctx execution context - * @param backend_url base URL of the merchant backend - * @param truth_uuid identification of the Truth - * @param truth_key Key used to Decrypt the Truth on the Server - * @param payment_secret secret from the previously done payment NULL to trigger payment - * @param timeout how long to wait for the payment, use - * #GNUNET_TIME_UNIT_ZERO to let the server pick - * @param hashed_answer hashed answer to the challenge - * @param cb callback which will work the response gotten from the backend - * @param cb_cls closure to pass to the callback - * @return handle for this operation, NULL upon errors - */ -struct ANASTASIS_KeyShareLookupOperation * -ANASTASIS_keyshare_lookup ( - struct GNUNET_CURL_Context *ctx, - const char *backend_url, - const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, - const struct ANASTASIS_CRYPTO_TruthKeyP *truth_key, - const struct ANASTASIS_PaymentSecretP *payment_secret, - struct GNUNET_TIME_Relative timeout, - const struct GNUNET_HashCode *hashed_answer, - ANASTASIS_KeyShareLookupCallback cb, - void *cb_cls); - - -/** - * Cancel a GET /truth request. - * - * @param kslo cancel the key share lookup operation - */ -void -ANASTASIS_keyshare_lookup_cancel ( - struct ANASTASIS_KeyShareLookupOperation *kslo); - - -/** * Handle for a POST /truth operation. */ struct ANASTASIS_TruthStoreOperation; @@ -836,6 +584,12 @@ enum ANASTASIS_ChallengeDetailType { /** + * A challenge TAN was written to a file. + * The name of the file is provided. + */ + ANASTASIS_CS_FILE_WRITTEN, + + /** * A challenge TAN was sent to the customer. * A hint may be provided as to the address used. */ @@ -851,6 +605,42 @@ enum ANASTASIS_ChallengeDetailType /** + * This structure contains information about where to wire the funds + * to authenticate as well as a hint as to which bank account to send + * the funds from. + */ +struct ANASTASIS_WireFundsDetails +{ + + /** + * Answer code expected. + */ + uint64_t answer_code; + + /** + * How much should be sent. + */ + struct TALER_Amount amount; + + /** + * IBAN where to send the funds. + */ + const char *target_iban; + + /** + * Name of the business receiving the funds. + */ + const char *target_business_name; + + /** + * Wire transfer subject to use. + */ + const char *wire_transfer_subject; + +}; + + +/** * Information returned for a POST /truth/$TID/challenge request. */ struct ANASTASIS_TruthChallengeDetails @@ -894,6 +684,12 @@ struct ANASTASIS_TruthChallengeDetails { /** + * If @e cs is #ANASTASIS_CS_FILE_WRITTEN, this + * is the filename with the challenge code. + */ + const char *challenge_filename; + + /** * If @e cs is #ANASTASIS_CS_TAN_SENT, this * is human-readable information as to where * the TAN was sent. @@ -907,28 +703,7 @@ struct ANASTASIS_TruthChallengeDetails * as a hint as to which bank account to send * the funds from. */ - struct - { - - /** - * How much should be sent. - */ - struct TALER_Amount amount; - - /** - * payto:// URI with the target account number. - */ - const char *target_payto; - - /** - * Human-readable hint about which sender bank - * account must be used. - */ - const char *sender_hint; - - // FIXME: more? Wire transfer subject? - - } wire_funds; + struct ANASTASIS_WireFundsDetails wire_funds; } details; diff --git a/src/include/anastasis_testing_lib.h b/src/include/anastasis_testing_lib.h index ba1b8a3..0066939 100644 --- a/src/include/anastasis_testing_lib.h +++ b/src/include/anastasis_testing_lib.h @@ -374,7 +374,28 @@ ANASTASIS_TESTING_cmd_truth_question ( /** - * Make the "keyshare lookup" command. + * Make a "truth challenge" command. + * + * @param label command label + * @param anastasis_url base URL of the ANASTASIS serving + * the keyshare lookup request. + * @param answer (response to challenge) + * @param payment_ref reference to the payment request + * @param upload_ref reference to upload command + * @param http_status expected HTTP status + * @return the command + */ +struct TALER_TESTING_Command +ANASTASIS_TESTING_cmd_truth_challenge ( + const char *label, + const char *anastasis_url, + const char *payment_ref, + const char *upload_ref, + unsigned int http_status); + + +/** + * Make a "truth solve" command. * * @param label command label * @param anastasis_url base URL of the ANASTASIS serving @@ -384,18 +405,18 @@ ANASTASIS_TESTING_cmd_truth_question ( * @param upload_ref reference to upload command * @param lookup_mode 0 for security question, 1 for * code-based - * @param ksdd expected status + * @param http_status expected HTTP status * @return the command */ struct TALER_TESTING_Command -ANASTASIS_TESTING_cmd_keyshare_lookup ( +ANASTASIS_TESTING_cmd_truth_solve ( const char *label, const char *anastasis_url, const char *answer, const char *payment_ref, const char *upload_ref, int lookup_mode, - enum ANASTASIS_KeyShareDownloadStatus ksdd); + unsigned int http_status); /** @@ -633,7 +654,7 @@ ANASTASIS_TESTING_cmd_challenge_start ( const char *payment_ref, const char *challenge_ref, unsigned int challenge_index, - enum ANASTASIS_ChallengeStatus expected_cs); + enum ANASTASIS_ChallengeStartStatus expected_cs); /** @@ -657,7 +678,7 @@ ANASTASIS_TESTING_cmd_challenge_answer ( unsigned int challenge_index, const char *answer, unsigned int mode, - enum ANASTASIS_ChallengeStatus expected_cs); + enum ANASTASIS_ChallengeAnswerStatus expected_cs); #endif diff --git a/src/lib/anastasis_recovery.c b/src/lib/anastasis_recovery.c index 9e5d1ca..a450392 100644 --- a/src/lib/anastasis_recovery.c +++ b/src/lib/anastasis_recovery.c @@ -63,16 +63,27 @@ struct ANASTASIS_Challenge /** * Callback which gives back the instructions and a status code of - * the request to the user when answering a challenge was initiated. + * the request to the user when answering a challenge. */ ANASTASIS_AnswerFeedback af; /** - * Closure for the challenge callback + * Closure for @e af. */ void *af_cls; /** + * Callback which gives back the instructions and a status code of + * the request to the user when initiating a challenge. + */ + ANASTASIS_ChallengeStartFeedback csf; + + /** + * Closure for @e csf. + */ + void *csf_cls; + + /** * Defines the base URL of the Anastasis provider used for the challenge. */ char *url; @@ -99,9 +110,14 @@ struct ANASTASIS_Challenge struct ANASTASIS_Recovery *recovery; /** - * keyshare lookup operation + * Handle for the /truth/$TID/challenge request. + */ + struct ANASTASIS_TruthChallengeOperation *tco; + + /** + * Handle for the /truth/$TID/solve request. */ - struct ANASTASIS_KeyShareLookupOperation *kslo; + struct ANASTASIS_TruthSolveOperation *tso; }; @@ -238,165 +254,137 @@ struct ANASTASIS_Recovery /** - * Function called with the results of a #ANASTASIS_keyshare_lookup(). + * Function called with the results of a #ANASTASIS_challenge_start(). * * @param cls closure * @param dd details about the lookup operation */ static void -keyshare_lookup_cb (void *cls, - const struct ANASTASIS_KeyShareDownloadDetails *dd) +truth_challenge_cb (void *cls, + const struct ANASTASIS_TruthChallengeDetails *tcd) { 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) + struct ANASTASIS_ChallengeStartResponse csr = { + .challenge = c, + .ec = tcd->ec, + .http_status = tcd->http_status + }; + + c->tco = NULL; + switch (tcd->http_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: + case MHD_HTTP_OK: + switch (tcd->details.success.cs) { - struct ANASTASIS_ChallengeStartResponse csr = { - .cs = ANASTASIS_CHALLENGE_STATUS_TRUTH_UNKNOWN, - .challenge = c - }; - - c->af (c->af_cls, - &csr); - return; + case ANASTASIS_CS_FILE_WRITTEN: + csr.cs = ANASTASIS_CHALLENGE_START_STATUS_FILENAME_PROVIDED; + csr.details.tan_filename + = tcd->details.success.details.challenge_filename; + break; + case ANASTASIS_CS_TAN_SENT: + csr.cs = ANASTASIS_CHALLENGE_START_STATUS_TAN_SENT_HINT_PROVIDED; + csr.details.tan_address_hint + = tcd->details.success.details.tan_address_hint; + break; + case ANASTASIS_CS_WIRE_FUNDS: + csr.cs = ANASTASIS_CHALLENGE_START_STATUS_BANK_TRANSFER_REQUIRED; + csr.details.bank_transfer_required + = tcd->details.success.details.wire_funds; + break; } - case ANASTASIS_KSD_RATE_LIMIT_EXCEEDED: - { - struct ANASTASIS_ChallengeStartResponse csr = { - .cs = ANASTASIS_CHALLENGE_STATUS_RATE_LIMIT_EXCEEDED, - .challenge = c, - .details.rate_limit_exceeded.request_limit - = dd->details.rate_limit_exceeded.request_limit, - .details.rate_limit_exceeded.request_frequency - = dd->details.rate_limit_exceeded.request_frequency - }; + break; + case MHD_HTTP_PAYMENT_REQUIRED: + csr.cs = ANASTASIS_CHALLENGE_START_STATUS_PAYMENT_REQUIRED; + csr.details.payment_required.taler_pay_uri + = tcd->details.payment_required.payment_request; + csr.details.payment_required.payment_secret + = tcd->details.payment_required.ps; + break; + case MHD_HTTP_NOT_FOUND: + csr.cs = ANASTASIS_CHALLENGE_START_STATUS_TRUTH_UNKNOWN; + break; + default: + csr.cs = ANASTASIS_CHALLENGE_START_STATUS_SERVER_FAILURE; + break; + } + c->csf (c->csf_cls, + &csr); +} - 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; - } - case ANASTASIS_KSD_AUTHENTICATION_TIMEOUT: - { - struct ANASTASIS_ChallengeStartResponse csr = { - .cs = ANASTASIS_CHALLENGE_STATUS_AUTH_TIMEOUT, - .challenge = c, - .details.server_failure.ec - = dd->details.server_failure.ec, - .details.server_failure.http_status - = dd->details.server_failure.http_status - }; +/** + * Function called with the results of a #ANASTASIS_truth_solve(). + * + * @param cls closure + * @param tsr details about the solution response + */ +static void +truth_solve_cb (void *cls, + const struct ANASTASIS_TruthSolveReply *tsr) +{ + struct ANASTASIS_Challenge *c = cls; + struct ANASTASIS_Recovery *recovery = c->recovery; + struct ANASTASIS_CRYPTO_UserIdentifierP id; + struct DecryptionPolicy *rdps; + struct ANASTASIS_ChallengeAnswerResponse csr = { + .challenge = c, + .ec = tsr->ec, + .http_status = tsr->http_status + }; - c->ci.async = true; - c->af (c->af_cls, - &csr); - return; - } - case ANASTASIS_KSD_EXTERNAL_CHALLENGE_INSTRUCTIONS: - { - struct ANASTASIS_ChallengeStartResponse csr = { - .cs = ANASTASIS_CHALLENGE_STATUS_EXTERNAL_INSTRUCTIONS, - .challenge = c, - .details.external_challenge = dd->details.external_challenge - }; - c->af (c->af_cls, - &csr); - return; - } + c->tso = NULL; + switch (tsr->http_status) + { + case MHD_HTTP_OK: + break; + case MHD_HTTP_PAYMENT_REQUIRED: + csr.cs = ANASTASIS_CHALLENGE_ANSWER_STATUS_PAYMENT_REQUIRED; + csr.details.payment_required.taler_pay_uri + = tsr->details.payment_required.payment_request; + csr.details.payment_required.payment_secret + = tsr->details.payment_required.ps; + c->af (c->af_cls, + &csr); + return; + case MHD_HTTP_FORBIDDEN: + csr.cs = ANASTASIS_CHALLENGE_ANSWER_STATUS_INVALID_ANSWER; + c->af (c->af_cls, + &csr); + return; + case MHD_HTTP_NOT_FOUND: + csr.cs = ANASTASIS_CHALLENGE_ANSWER_STATUS_TRUTH_UNKNOWN; + c->af (c->af_cls, + &csr); + return; + case MHD_HTTP_TOO_MANY_REQUESTS: + csr.cs = ANASTASIS_CHALLENGE_ANSWER_STATUS_RATE_LIMIT_EXCEEDED; + csr.details.rate_limit_exceeded.request_limit + = tsr->details.too_many_requests.request_limit; + csr.details.rate_limit_exceeded.request_frequency + = tsr->details.too_many_requests.request_frequency; + c->af (c->af_cls, + &csr); + return; + default: + csr.cs = ANASTASIS_CHALLENGE_ANSWER_STATUS_SERVER_FAILURE; + 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, + ANASTASIS_CRYPTO_keyshare_decrypt (&tsr->details.success.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); - } - + c->ci.solved = true; + csr.cs = ANASTASIS_CHALLENGE_ANSWER_STATUS_SOLVED; + 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'. */ @@ -477,33 +465,67 @@ ANASTASIS_challenge_get_details (struct ANASTASIS_Challenge *challenge) enum GNUNET_GenericReturnValue 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) + ANASTASIS_ChallengeStartFeedback csf, + void *csf_cls) { if (c->ci.solved) { GNUNET_break (0); return GNUNET_NO; /* already solved */ } - if (NULL != c->kslo) + if (NULL != c->tco) + { + GNUNET_break (0); + return GNUNET_NO; /* already solving */ + } + c->csf = csf; + c->csf_cls = csf_cls; + c->tco = ANASTASIS_truth_challenge (c->recovery->ctx, + c->url, + &c->ci.uuid, + &c->truth_key, + psp, + &truth_challenge_cb, + c); + if (NULL == c->tco) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +enum GNUNET_GenericReturnValue +ANASTASIS_challenge_answer3 (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->tso) { 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) + c->tso = ANASTASIS_truth_solve (c->recovery->ctx, + c->url, + &c->ci.uuid, + &c->truth_key, + psp, + timeout, + hashed_answer, + &truth_solve_cb, + c); + if (NULL == c->tso) { GNUNET_break (0); return GNUNET_SYSERR; @@ -529,12 +551,12 @@ ANASTASIS_challenge_answer ( &c->ci.uuid, &c->salt, &hashed_answer); - return ANASTASIS_challenge_start (c, - psp, - timeout, - &hashed_answer, - af, - af_cls); + return ANASTASIS_challenge_answer3 (c, + psp, + timeout, + &hashed_answer, + af, + af_cls); } @@ -550,25 +572,28 @@ ANASTASIS_challenge_answer2 (struct ANASTASIS_Challenge *c, ANASTASIS_hash_answer (answer, &answer_s); - return ANASTASIS_challenge_start (c, - psp, - timeout, - &answer_s, - af, - af_cls); + return ANASTASIS_challenge_answer3 (c, + psp, + timeout, + &answer_s, + af, + af_cls); } void ANASTASIS_challenge_abort (struct ANASTASIS_Challenge *c) { - if (NULL == c->kslo) + if (NULL != c->tso) { - GNUNET_break (0); - return; + ANASTASIS_truth_solve_cancel (c->tso); + c->tso = NULL; + } + if (NULL != c->tco) + { + ANASTASIS_truth_challenge_cancel (c->tco); + c->tco = NULL; } - ANASTASIS_keyshare_lookup_cancel (c->kslo); - c->kslo = NULL; c->af = NULL; c->af_cls = NULL; } @@ -1461,10 +1486,10 @@ ANASTASIS_recovery_abort (struct ANASTASIS_Recovery *r) { struct ANASTASIS_Challenge *cs = &r->cs[i]; - if (NULL != cs->kslo) + if (NULL != cs->tso) { - ANASTASIS_keyshare_lookup_cancel (cs->kslo); - cs->kslo = NULL; + ANASTASIS_truth_solve_cancel (cs->tso); + cs->tso = NULL; } GNUNET_free (cs->url); GNUNET_free (cs->type); diff --git a/src/reducer/anastasis_api_recovery_redux.c b/src/reducer/anastasis_api_recovery_redux.c index 2be963f..5de278c 100644 --- a/src/reducer/anastasis_api_recovery_redux.c +++ b/src/reducer/anastasis_api_recovery_redux.c @@ -504,7 +504,7 @@ find_challenge_in_cs (json_t *state, * @param csr response details */ static void -answer_feedback_cb ( +start_feedback_cb ( void *cls, const struct ANASTASIS_ChallengeStartResponse *csr) { @@ -533,7 +533,246 @@ answer_feedback_cb ( } switch (csr->cs) { - case ANASTASIS_CHALLENGE_STATUS_SOLVED: + case ANASTASIS_CHALLENGE_START_STATUS_FILENAME_PROVIDED: + { + json_t *instructions; + + instructions = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("state", + "hint"), + GNUNET_JSON_pack_string ("hint", + csr->details.tan_filename), + GNUNET_JSON_pack_uint64 ("http_status", + (json_int_t) csr->http_status)); + GNUNET_assert (0 == + json_object_set_new (feedback, + uuid, + instructions)); + } + set_state (sctx->state, + ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING); + sctx->cb (sctx->cb_cls, + TALER_EC_NONE, + sctx->state); + sctx_free (sctx); + return; + case ANASTASIS_CHALLENGE_START_STATUS_TAN_SENT_HINT_PROVIDED: + { + json_t *instructions; + + instructions = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("state", + "hint"), + GNUNET_JSON_pack_string ("hint", + csr->details.tan_address_hint), + GNUNET_JSON_pack_uint64 ("http_status", + (json_int_t) csr->http_status)); + GNUNET_assert (0 == + json_object_set_new (feedback, + uuid, + instructions)); + } + set_state (sctx->state, + ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING); + sctx->cb (sctx->cb_cls, + TALER_EC_NONE, + sctx->state); + sctx_free (sctx); + return; + case ANASTASIS_CHALLENGE_START_STATUS_PAYMENT_REQUIRED: + { + json_t *pay; + + pay = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("state", + "payment"), + GNUNET_JSON_pack_string ( + "taler_pay_uri", + csr->details.payment_required.taler_pay_uri), + GNUNET_JSON_pack_string ("provider", + cd->provider_url), + GNUNET_JSON_pack_data_auto ( + "payment_secret", + &csr->details.payment_required.payment_secret)); + GNUNET_assert (0 == + json_object_set_new (feedback, + uuid, + pay)); + } + /* Remember payment secret for later (once application claims it paid) */ + { + json_t *challenge = find_challenge_in_ri (sctx->state, + &cd->uuid); + + GNUNET_assert (NULL != challenge); + GNUNET_assert (0 == + json_object_set_new ( + challenge, + "payment_secret", + GNUNET_JSON_from_data_auto ( + &csr->details.payment_required.payment_secret))); + } + set_state (sctx->state, + ANASTASIS_RECOVERY_STATE_CHALLENGE_PAYING); + sctx->cb (sctx->cb_cls, + TALER_EC_NONE, + sctx->state); + sctx_free (sctx); + return; + case ANASTASIS_CHALLENGE_START_STATUS_SERVER_FAILURE: + { + json_t *err; + + err = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("state", + "server-failure"), + GNUNET_JSON_pack_uint64 ("http_status", + csr->http_status), + GNUNET_JSON_pack_uint64 ("error_code", + csr->ec)); + GNUNET_assert (0 == + json_object_set_new (feedback, + uuid, + err)); + } + set_state (sctx->state, + ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING); + sctx->cb (sctx->cb_cls, + csr->ec, + sctx->state); + sctx_free (sctx); + return; + case ANASTASIS_CHALLENGE_START_STATUS_TRUTH_UNKNOWN: + { + json_t *err; + + err = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("state", + "truth-unknown"), + GNUNET_JSON_pack_uint64 ("http_status", + csr->http_status), + GNUNET_JSON_pack_uint64 ("error_code", + TALER_EC_ANASTASIS_TRUTH_UNKNOWN)); + GNUNET_assert (0 == + json_object_set_new (feedback, + uuid, + err)); + } + set_state (sctx->state, + ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING); + sctx->cb (sctx->cb_cls, + TALER_EC_ANASTASIS_TRUTH_UNKNOWN, + sctx->state); + sctx_free (sctx); + return; + case ANASTASIS_CHALLENGE_START_STATUS_BANK_TRANSFER_REQUIRED: + { + json_t *reply; + json_t *c; + + c = find_challenge_in_cs (sctx->state, + &cd->uuid); + if (NULL == c) + { + GNUNET_break (0); + ANASTASIS_redux_fail_ (sctx->cb, + sctx->cb_cls, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + NULL); + sctx_free (sctx); + return; + } + GNUNET_assert (0 == + json_object_set_new (c, + "async", + json_true ())); + GNUNET_assert ( + 0 == + json_object_set_new ( + c, + "answer-pin", + json_integer ( + csr->details.bank_transfer_required.answer_code))); + reply = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("state", + "iban-instructions"), + GNUNET_JSON_pack_string ( + "target_iban", + csr->details.bank_transfer_required.target_iban), + GNUNET_JSON_pack_string ( + "target_business_name", + csr->details.bank_transfer_required.target_business_name), + GNUNET_JSON_pack_string ( + "wire_transfer_subject", + csr->details.bank_transfer_required.wire_transfer_subject), + TALER_JSON_pack_amount ( + "challenge_amount", + &csr->details.bank_transfer_required.amount)); + GNUNET_assert (0 == + json_object_set_new (feedback, + uuid, + reply)); + } + GNUNET_assert (0 == + json_object_set_new (sctx->state, + "selected_challenge_uuid", + GNUNET_JSON_from_data_auto ( + &cd->uuid))); + set_state (sctx->state, + ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING); + sctx->cb (sctx->cb_cls, + TALER_EC_NONE, + sctx->state); + sctx_free (sctx); + return; + } + GNUNET_break (0); + ANASTASIS_redux_fail_ (sctx->cb, + sctx->cb_cls, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + NULL); + sctx_free (sctx); +} + + +/** + * Defines a callback for the response status for a challenge answer + * operation. + * + * @param cls a `struct SelectChallengeContext *` + * @param csr response details + */ +static void +answer_feedback_cb ( + void *cls, + const struct ANASTASIS_ChallengeAnswerResponse *csr) +{ + struct SelectChallengeContext *sctx = cls; + const struct ANASTASIS_ChallengeDetails *cd; + char uuid[sizeof (cd->uuid) * 2]; + char *end; + json_t *feedback; + + cd = ANASTASIS_challenge_get_details (csr->challenge); + end = GNUNET_STRINGS_data_to_string (&cd->uuid, + sizeof (cd->uuid), + uuid, + sizeof (uuid)); + GNUNET_assert (NULL != end); + *end = '\0'; + feedback = json_object_get (sctx->state, + "challenge_feedback"); + if (NULL == feedback) + { + feedback = json_object (); + GNUNET_assert (0 == + json_object_set_new (sctx->state, + "challenge_feedback", + feedback)); + } + switch (csr->cs) + { + case ANASTASIS_CHALLENGE_ANSWER_STATUS_SOLVED: { json_t *rd; @@ -570,77 +809,17 @@ answer_feedback_cb ( sctx->delayed_report = GNUNET_SCHEDULER_add_now (&report_solved, sctx); return; - case ANASTASIS_CHALLENGE_STATUS_INSTRUCTIONS: + case ANASTASIS_CHALLENGE_ANSWER_STATUS_INVALID_ANSWER: { json_t *instructions; - const char *mime; - - mime = csr->details.open_challenge.content_type; - if (NULL != mime) - { - if ( (0 == strcasecmp (mime, - "text/plain")) || - (0 == strcasecmp (mime, - "text/utf8")) ) - { - char *s = GNUNET_strndup (csr->details.open_challenge.body, - csr->details.open_challenge.body_size); - - instructions = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("state", - "hint"), - GNUNET_JSON_pack_string ("hint", - s), - GNUNET_JSON_pack_uint64 ("http_status", - (json_int_t) csr->details.open_challenge. - http_status)); - GNUNET_free (s); - } - else if (0 == strcasecmp (mime, - "application/json")) - { - json_t *body; - body = json_loadb (csr->details.open_challenge.body, - csr->details.open_challenge.body_size, - JSON_REJECT_DUPLICATES, - NULL); - if (NULL == body) - { - GNUNET_break_op (0); - mime = NULL; - } - else - { - instructions = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("state", - "details"), - GNUNET_JSON_pack_object_steal ("details", - body), - GNUNET_JSON_pack_uint64 ("http_status", - csr->details.open_challenge.http_status)); - } - } - else - { - /* unexpected / unsupported mime type */ - mime = NULL; - } - } - if (NULL == mime) - { - instructions = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("state", - "body"), - GNUNET_JSON_pack_data_varsize ("body", - csr->details.open_challenge.body, - csr->details.open_challenge.body_size), - GNUNET_JSON_pack_uint64 ("http_status", - csr->details.open_challenge.http_status), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("mime_type", - mime))); - } + instructions = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("status", + "incorrect-answer"), + GNUNET_JSON_pack_uint64 ("error_code", + csr->ec), + GNUNET_JSON_pack_uint64 ("http_status", + csr->http_status)); GNUNET_assert (0 == json_object_set_new (feedback, uuid, @@ -653,35 +832,13 @@ answer_feedback_cb ( sctx->state); sctx_free (sctx); return; - case ANASTASIS_CHALLENGE_STATUS_REDIRECT_FOR_AUTHENTICATION: - { - json_t *redir; - - redir = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("state", - "redirect"), - GNUNET_JSON_pack_string ("redirect_url", - csr->details.redirect_url)); - GNUNET_assert (NULL != redir); - GNUNET_assert (0 == - json_object_set_new (feedback, - uuid, - redir)); - } - set_state (sctx->state, - ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING); - sctx->cb (sctx->cb_cls, - TALER_EC_NONE, - sctx->state); - sctx_free (sctx); - return; - case ANASTASIS_CHALLENGE_STATUS_PAYMENT_REQUIRED: + case ANASTASIS_CHALLENGE_ANSWER_STATUS_PAYMENT_REQUIRED: { json_t *pay; pay = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("state", - "payment"), + "payment-required"), GNUNET_JSON_pack_string ("taler_pay_uri", csr->details.payment_required. taler_pay_uri), @@ -715,7 +872,7 @@ answer_feedback_cb ( sctx->state); sctx_free (sctx); return; - case ANASTASIS_CHALLENGE_STATUS_SERVER_FAILURE: + case ANASTASIS_CHALLENGE_ANSWER_STATUS_SERVER_FAILURE: { json_t *err; @@ -723,10 +880,9 @@ answer_feedback_cb ( GNUNET_JSON_pack_string ("state", "server-failure"), GNUNET_JSON_pack_uint64 ("http_status", - csr->details.server_failure. - http_status), + csr->http_status), GNUNET_JSON_pack_uint64 ("error_code", - csr->details.server_failure.ec)); + csr->ec)); GNUNET_assert (0 == json_object_set_new (feedback, uuid, @@ -735,11 +891,11 @@ answer_feedback_cb ( set_state (sctx->state, ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING); sctx->cb (sctx->cb_cls, - csr->details.server_failure.ec, + csr->ec, sctx->state); sctx_free (sctx); return; - case ANASTASIS_CHALLENGE_STATUS_TRUTH_UNKNOWN: + case ANASTASIS_CHALLENGE_ANSWER_STATUS_TRUTH_UNKNOWN: { json_t *err; @@ -760,7 +916,7 @@ answer_feedback_cb ( sctx->state); sctx_free (sctx); return; - case ANASTASIS_CHALLENGE_STATUS_RATE_LIMIT_EXCEEDED: + case ANASTASIS_CHALLENGE_ANSWER_STATUS_RATE_LIMIT_EXCEEDED: { json_t *err; @@ -789,7 +945,8 @@ answer_feedback_cb ( sctx->state); sctx_free (sctx); return; - case ANASTASIS_CHALLENGE_STATUS_AUTH_TIMEOUT: + case ANASTASIS_CHALLENGE_ANSWER_STATUS_AUTH_TIMEOUT: + // FIXME: check if this status code is even properly generated! { json_t *err; @@ -811,95 +968,6 @@ answer_feedback_cb ( sctx->state); sctx_free (sctx); return; - - case ANASTASIS_CHALLENGE_STATUS_EXTERNAL_INSTRUCTIONS: - { - const json_t *body = csr->details.external_challenge; - const char *method; - json_t *details; - bool is_async = false; - uint64_t code = 0; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("method", - &method), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_bool ("async", - &is_async)), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_uint64 ("answer_code", - &code)), - GNUNET_JSON_spec_json ("details", - &details), - GNUNET_JSON_spec_end () - }; - json_t *reply; - - if (GNUNET_OK != - GNUNET_JSON_parse (body, - spec, - NULL, NULL)) - { - json_t *err; - - GNUNET_break_op (0); - err = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("state", - "server-failure"), - GNUNET_JSON_pack_uint64 ("error_code", - TALER_EC_GENERIC_REPLY_MALFORMED)); - GNUNET_assert (0 == - json_object_set_new (feedback, - uuid, - err)); - return; - } - if (is_async) - { - json_t *c = find_challenge_in_cs (sctx->state, - &cd->uuid); - - if (NULL == c) - { - GNUNET_break (0); - ANASTASIS_redux_fail_ (sctx->cb, - sctx->cb_cls, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - NULL); - sctx_free (sctx); - return; - } - GNUNET_assert (0 == - json_object_set_new (c, - "async", - json_true ())); - GNUNET_assert (0 == - json_object_set_new (c, - "answer-pin", - json_integer (code))); - } - reply = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("state", - "external-instructions"), - GNUNET_JSON_pack_string ("method", - method), - GNUNET_JSON_pack_object_incref ("details", - details)); - GNUNET_JSON_parse_free (spec); - GNUNET_assert (0 == - json_object_set_new (feedback, - uuid, - reply)); - } - json_object_set_new (sctx->state, - "selected_challenge_uuid", - GNUNET_JSON_from_data_auto (&cd->uuid)); - set_state (sctx->state, - ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING); - sctx->cb (sctx->cb_cls, - TALER_EC_NONE, - sctx->state); - sctx_free (sctx); - return; } GNUNET_break (0); ANASTASIS_redux_fail_ (sctx->cb, @@ -1197,21 +1265,19 @@ solve_challenge_cb (void *cls, sctx_free (sctx); return; } - ret = ANASTASIS_challenge_start (ci, - psp, - timeout, - &hashed_answer, - &answer_feedback_cb, - sctx); + ret = ANASTASIS_challenge_answer3 (ci, + psp, + timeout, + &hashed_answer, + &answer_feedback_cb, + sctx); } else { /* no answer provided */ ret = ANASTASIS_challenge_start (ci, psp, - timeout, - NULL, /* no answer */ - &answer_feedback_cb, + &start_feedback_cb, sctx); } } @@ -1331,9 +1397,7 @@ pay_challenge_cb (void *cls, { ret = ANASTASIS_challenge_start (ci, &sctx->ps, - sctx->timeout, - NULL, /* no answer yet */ - &answer_feedback_cb, + &start_feedback_cb, sctx); } if (GNUNET_OK != ret) @@ -1780,9 +1844,7 @@ select_challenge_cb (void *cls, { ret = ANASTASIS_challenge_start (ci, psp, - timeout, - NULL, /* no answer */ - &answer_feedback_cb, + &start_feedback_cb, sctx); } } diff --git a/src/restclient/Makefile.am b/src/restclient/Makefile.am index b2e9d0b..9bee2dd 100644 --- a/src/restclient/Makefile.am +++ b/src/restclient/Makefile.am @@ -22,7 +22,6 @@ libanastasisrest_la_SOURCES = \ anastasis_api_truth_challenge.c \ anastasis_api_truth_solve.c \ anastasis_api_truth_store.c \ - anastasis_api_keyshare_lookup.c \ anastasis_api_curl_defaults.c anastasis_api_curl_defaults.h libanastasisrest_la_LIBADD = \ -lgnunetcurl \ diff --git a/src/restclient/anastasis_api_curl_defaults.c b/src/restclient/anastasis_api_curl_defaults.c index e052517..33665e0 100644 --- a/src/restclient/anastasis_api_curl_defaults.c +++ b/src/restclient/anastasis_api_curl_defaults.c @@ -42,5 +42,17 @@ ANASTASIS_curl_easy_get_ (const char *url) curl_easy_setopt (eh, CURLOPT_TCP_FASTOPEN, 1L)); + /* limit MAXREDIRS to 5 as a simple security measure against + a potential infinite loop caused by a malicious target */ + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_MAXREDIRS, + 5L)); + /* Enable compression (using whatever curl likes), see + https://curl.se/libcurl/c/CURLOPT_ACCEPT_ENCODING.html */ + GNUNET_break (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_ACCEPT_ENCODING, + "")); return eh; } diff --git a/src/restclient/anastasis_api_truth_challenge.c b/src/restclient/anastasis_api_truth_challenge.c index 92916d6..911eba6 100644 --- a/src/restclient/anastasis_api_truth_challenge.c +++ b/src/restclient/anastasis_api_truth_challenge.c @@ -123,6 +123,7 @@ handle_truth_challenge_finished (void *cls, { const char *ct; const char *tan_hint = NULL; + const char *filename = NULL; json_t *wire_details = NULL; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ( @@ -132,6 +133,9 @@ handle_truth_challenge_finished (void *cls, GNUNET_JSON_spec_string ("tan_address_hint", &tan_hint)), GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("filename", + &filename)), + GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_json ("wire_details", &wire_details)), GNUNET_JSON_spec_end () @@ -152,19 +156,34 @@ handle_truth_challenge_finished (void *cls, { tcd.details.success.cs = ANASTASIS_CS_TAN_SENT; tcd.details.success.details.tan_address_hint = tan_hint; + break; + } + if ( (0 == strcmp (ct, + "FILE_WRITTEN")) && + (NULL != filename) ) + { + tcd.details.success.cs = ANASTASIS_CS_FILE_WRITTEN; + tcd.details.success.details.challenge_filename = filename; + break; } - else if ( (0 == strcmp (ct, - "WIRE_FUNDS")) && - (NULL != wire_details) ) + if ( (0 == strcmp (ct, + "IBAN_WIRE")) && + (NULL != wire_details) ) { struct GNUNET_JSON_Specification ispec[] = { GNUNET_JSON_spec_string ( - "target_account", - &tcd.details.success.details.wire_funds.target_payto), + "credit_iban", + &tcd.details.success.details.wire_funds.target_iban), + GNUNET_JSON_spec_uint64 ( + "answer_code", + &tcd.details.success.details.wire_funds.answer_code), + GNUNET_JSON_spec_string ( + "business_name", + &tcd.details.success.details.wire_funds.target_business_name), GNUNET_JSON_spec_string ( - "sender_hint", - &tcd.details.success.details.wire_funds.sender_hint), - TALER_JSON_spec_amount_any ("amount", + "wire_transfer_subject", + &tcd.details.success.details.wire_funds.wire_transfer_subject), + TALER_JSON_spec_amount_any ("challenge_amount", &tcd.details.success.details.wire_funds. amount), GNUNET_JSON_spec_end () @@ -188,6 +207,12 @@ handle_truth_challenge_finished (void *cls, ANASTASIS_truth_challenge_cancel (tco); return; } + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected challenge type `%s'\n", + ct); + tcd.http_status = 0; + tcd.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; } case MHD_HTTP_BAD_REQUEST: /* This should never happen, either us or the anastasis server is buggy @@ -409,10 +434,11 @@ ANASTASIS_truth_challenge ( curl_easy_setopt (eh, CURLOPT_HEADERDATA, tco)); - tco->job = GNUNET_CURL_job_add (ctx, - eh, - &handle_truth_challenge_finished, - tco); + tco->job = GNUNET_CURL_job_add2 (ctx, + eh, + tco->ctx.headers, + &handle_truth_challenge_finished, + tco); return tco; } diff --git a/src/restclient/anastasis_api_truth_solve.c b/src/restclient/anastasis_api_truth_solve.c index 376fc74..971e917 100644 --- a/src/restclient/anastasis_api_truth_solve.c +++ b/src/restclient/anastasis_api_truth_solve.c @@ -427,7 +427,7 @@ ANASTASIS_truth_solve ( tso)); tso->job = GNUNET_CURL_job_add_raw (ctx, eh, - NULL, + tso->ctx.headers, &handle_truth_solve_finished, tso); return tso; diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am index 9a98530..fec971a 100644 --- a/src/testing/Makefile.am +++ b/src/testing/Makefile.am @@ -14,9 +14,10 @@ libanastasistesting_la_LDFLAGS = \ -no-undefined libanastasistesting_la_SOURCES = \ testing_api_cmd_policy_store.c \ + testing_api_cmd_truth_challenge.c \ + testing_api_cmd_truth_solve.c \ testing_api_cmd_truth_store.c \ testing_api_cmd_policy_lookup.c \ - testing_api_cmd_keyshare_lookup.c \ testing_api_cmd_config.c \ testing_api_helpers.c \ testing_api_traits.c \ diff --git a/src/testing/test_anastasis.c b/src/testing/test_anastasis.c index 6ce6771..7b127ac 100644 --- a/src/testing/test_anastasis.c +++ b/src/testing/test_anastasis.c @@ -280,7 +280,7 @@ run (void *cls, 0, /* challenge index */ "SomeTruth1", 0, /* mode */ - ANASTASIS_CHALLENGE_STATUS_SOLVED), + ANASTASIS_CHALLENGE_ANSWER_STATUS_SOLVED), #if 0 ANASTASIS_TESTING_cmd_challenge_answer ("challenge-answer-2", NULL, /* payment ref */ @@ -288,13 +288,13 @@ run (void *cls, 1, /* challenge index */ "SomeTruth2", 0, /* mode */ - ANASTASIS_CHALLENGE_STATUS_SOLVED), + ANASTASIS_CHALLENGE_ANSWER_STATUS_SOLVED), #endif ANASTASIS_TESTING_cmd_challenge_start ("challenge-start-3-pay", NULL, /* payment ref */ "recover-secret-1", 2, /* challenge index */ - ANASTASIS_CHALLENGE_STATUS_PAYMENT_REQUIRED), + ANASTASIS_CHALLENGE_START_STATUS_PAYMENT_REQUIRED), TALER_TESTING_cmd_merchant_claim_order ("fetch-challenge-pay-proposal", merchant_url, MHD_HTTP_OK, @@ -312,14 +312,14 @@ run (void *cls, "challenge-start-3-pay", /* payment ref */ "recover-secret-1", 2, /* challenge index */ - ANASTASIS_CHALLENGE_STATUS_INSTRUCTIONS), + ANASTASIS_CHALLENGE_START_STATUS_FILENAME_PROVIDED), ANASTASIS_TESTING_cmd_challenge_answer ("challenge-answer-3", "challenge-start-3-pay", /* payment ref */ "recover-secret-1", 2, /* challenge index */ "challenge-start-3-paid", /* answer */ 1, /* mode */ - ANASTASIS_CHALLENGE_STATUS_SOLVED), + ANASTASIS_CHALLENGE_ANSWER_STATUS_SOLVED), ANASTASIS_TESTING_cmd_recover_secret_finish ("recover-finish-1", "recover-secret-1", GNUNET_TIME_UNIT_SECONDS), diff --git a/src/testing/test_anastasis_api.c b/src/testing/test_anastasis_api.c index b8d9608..caa59c8 100644 --- a/src/testing/test_anastasis_api.c +++ b/src/testing/test_anastasis_api.c @@ -209,14 +209,14 @@ run (void *cls, "The-Answer", ANASTASIS_TESTING_TSO_NONE, MHD_HTTP_NO_CONTENT), - ANASTASIS_TESTING_cmd_keyshare_lookup ( + ANASTASIS_TESTING_cmd_truth_solve ( "keyshare-lookup-1", anastasis_url, "The-Answer", NULL, /* payment ref */ "truth-store-1", 0, - ANASTASIS_KSD_SUCCESS), + MHD_HTTP_OK), ANASTASIS_TESTING_cmd_truth_store ( "truth-store-2", anastasis_url, @@ -227,22 +227,20 @@ run (void *cls, file_secret, ANASTASIS_TESTING_TSO_NONE, MHD_HTTP_NO_CONTENT), - ANASTASIS_TESTING_cmd_keyshare_lookup ( + ANASTASIS_TESTING_cmd_truth_solve ( "challenge-fail-1", anastasis_url, "Wrong-Answer", - NULL, - "truth-store-1", - 0, - ANASTASIS_KSD_INVALID_ANSWER), - ANASTASIS_TESTING_cmd_keyshare_lookup ( + NULL, /* payment ref */ + "truth-store-1", /* upload ref */ + 0, /* security question mode */ + MHD_HTTP_FORBIDDEN), + ANASTASIS_TESTING_cmd_truth_challenge ( "file-challenge-run-1", anastasis_url, - NULL, /* no answer */ NULL, /* payment ref */ "truth-store-2", /* upload ref */ - 0, - ANASTASIS_KSD_PAYMENT_REQUIRED), + MHD_HTTP_PAYMENT_REQUIRED), /* what would we have to pay? */ TALER_TESTING_cmd_merchant_claim_order ("fetch-proposal-2", merchant_url, @@ -259,22 +257,20 @@ run (void *cls, "EUR:1", NULL), - ANASTASIS_TESTING_cmd_keyshare_lookup ( + ANASTASIS_TESTING_cmd_truth_challenge ( "file-challenge-run-2", anastasis_url, - NULL, /* no answer */ "file-challenge-run-1", /* payment ref */ "truth-store-2", - 0, - ANASTASIS_KSD_INVALID_ANSWER), - ANASTASIS_TESTING_cmd_keyshare_lookup ( + MHD_HTTP_OK), + ANASTASIS_TESTING_cmd_truth_solve ( "file-challenge-run-3", anastasis_url, "file-challenge-run-2", /* answer */ "file-challenge-run-1", /* payment ref */ "truth-store-2", 1, - ANASTASIS_KSD_SUCCESS), + MHD_HTTP_OK), TALER_TESTING_cmd_end () }; diff --git a/src/testing/testing_api_cmd_truth_challenge.c b/src/testing/testing_api_cmd_truth_challenge.c new file mode 100644 index 0000000..c584d5f --- /dev/null +++ b/src/testing/testing_api_cmd_truth_challenge.c @@ -0,0 +1,372 @@ +/* + This file is part of Anastasis + Copyright (C) 2020, 2022 Anastasis SARL + + Anastasis is free software; you can redistribute it and/or modify it under the + terms of the GNU 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/> +*/ +/** + * @file testing/testing_api_cmd_truth_challenge.c + * @brief Testing of Implementation of the /truth GET + * @author Christian Grothoff + * @author Dennis Neufeld + * @author Dominik Meister + */ + +#include "platform.h" +#include "anastasis_testing_lib.h" +#include <taler/taler_util.h> +#include <taler/taler_testing_lib.h> +#include <taler/taler_merchant_service.h> + + +/** + * State for a "keyshare lookup" CMD. + */ +struct TruthChallengeState +{ + /** + * The interpreter state. + */ + struct TALER_TESTING_Interpreter *is; + + /** + * URL of the anastasis backend. + */ + const char *anastasis_url; + + /** + * Expected HTTP status code. + */ + unsigned int expected_http_status; + + /** + * The /truth GET operation handle. + */ + struct ANASTASIS_TruthChallengeOperation *tco; + + /** + * Reference to upload command we expect to lookup. + */ + const char *upload_reference; + + /** + * Reference to upload command we expect to lookup. + */ + const char *payment_reference; + + /** + * Payment secret requested by the service, if any. + */ + struct ANASTASIS_PaymentSecretP payment_secret_response; + + /** + * Taler-URI with payment request, if any. + */ + char *pay_uri; + + /** + * Order ID for payment request, if any. + */ + char *order_id; + + /** + * "code" returned by service, if any. + */ + char *code; + + /** + * "instructions" for how to solve the challenge as returned by service, if any. + */ + char *instructions; + +}; + + +static void +truth_challenge_cb (void *cls, + const struct ANASTASIS_TruthChallengeDetails *tcd) +{ + struct TruthChallengeState *ksls = cls; + + ksls->tco = NULL; + if (tcd->http_status != ksls->expected_http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u to command %s in %s:%u\n", + tcd->http_status, + ksls->is->commands[ksls->is->ip].label, + __FILE__, + __LINE__); + TALER_TESTING_interpreter_fail (ksls->is); + return; + } + switch (tcd->http_status) + { + case MHD_HTTP_OK: + switch (tcd->details.success.cs) + { + case ANASTASIS_CS_FILE_WRITTEN: + { + FILE *file; + char code[22]; + + file = fopen (tcd->details.success.details.challenge_filename, + "r"); + if (NULL == file) + { + GNUNET_log_strerror_file ( + GNUNET_ERROR_TYPE_ERROR, + "open", + tcd->details.success.details.challenge_filename); + TALER_TESTING_interpreter_fail (ksls->is); + return; + } + if (0 == fscanf (file, + "%21s", + code)) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "fscanf", + tcd->details.success.details. + challenge_filename); + GNUNET_break (0 == fclose (file)); + TALER_TESTING_interpreter_fail (ksls->is); + return; + } + GNUNET_break (0 == fclose (file)); + ksls->code = GNUNET_strdup (code); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Read code `%s'\n", + code); + } + break; + case ANASTASIS_CS_TAN_SENT: + ksls->instructions = GNUNET_strdup ( + tcd->details.success.details.tan_address_hint); + break; + case ANASTASIS_CS_WIRE_FUNDS: + /* FIXME: not implemented */ + GNUNET_break (0); + return; + } + break; + case MHD_HTTP_PAYMENT_REQUIRED: + ksls->pay_uri = GNUNET_strdup ( + tcd->details.payment_required.payment_request); + ksls->payment_secret_response = tcd->details.payment_required.ps; + { + struct TALER_MERCHANT_PayUriData pd; + + if (GNUNET_OK != + TALER_MERCHANT_parse_pay_uri (ksls->pay_uri, + &pd)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (ksls->is); + return; + } + ksls->order_id = GNUNET_strdup (pd.order_id); + TALER_MERCHANT_parse_pay_uri_free (&pd); + } + + break; + default: + break; + } + TALER_TESTING_interpreter_next (ksls->is); +} + + +static void +truth_challenge_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct TruthChallengeState *ksls = cls; + const struct ANASTASIS_CRYPTO_TruthKeyP *truth_key; + const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid; + const struct ANASTASIS_PaymentSecretP *payment_secret; + + ksls->is = is; + if (NULL == ksls->upload_reference) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (ksls->is); + return; + } + { + const struct TALER_TESTING_Command *upload_cmd; + + upload_cmd = TALER_TESTING_interpreter_lookup_command ( + is, + ksls->upload_reference); + if (NULL == upload_cmd) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (ksls->is); + return; + } + if (GNUNET_OK != + ANASTASIS_TESTING_get_trait_truth_uuid (upload_cmd, + &truth_uuid)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (ksls->is); + return; + } + if (NULL == truth_uuid) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (ksls->is); + return; + } + if (GNUNET_OK != + ANASTASIS_TESTING_get_trait_truth_key (upload_cmd, + &truth_key)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (ksls->is); + return; + } + if (NULL == truth_key) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (ksls->is); + return; + } + } + + if (NULL != ksls->payment_reference) + { + const struct TALER_TESTING_Command *payment_cmd; + + payment_cmd = TALER_TESTING_interpreter_lookup_command ( + is, + ksls->payment_reference); + if (GNUNET_OK != + ANASTASIS_TESTING_get_trait_payment_secret (payment_cmd, + &payment_secret)) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (ksls->is); + return; + } + } + else + { + payment_secret = NULL; + } + + ksls->tco = ANASTASIS_truth_challenge (is->ctx, + ksls->anastasis_url, + truth_uuid, + truth_key, + payment_secret, + &truth_challenge_cb, + ksls); + if (NULL == ksls->tco) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (ksls->is); + return; + } +} + + +static void +truth_challenge_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct TruthChallengeState *ksls = cls; + + if (NULL != ksls->tco) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command '%s' did not complete (keyshare lookup)\n", + cmd->label); + ANASTASIS_truth_challenge_cancel (ksls->tco); + ksls->tco = NULL; + } + GNUNET_free (ksls->pay_uri); + GNUNET_free (ksls->order_id); + GNUNET_free (ksls->code); + GNUNET_free (ksls->instructions); + GNUNET_free (ksls); +} + + +/** + * Offer internal data to other commands. + * + * @param cls closure + * @param[out] ret result (could be anything) + * @param[out] trait name of the trait + * @param index index number of the object to extract. + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +truth_challenge_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + struct TruthChallengeState *ksls = cls; + struct TALER_TESTING_Trait traits[] = { + ANASTASIS_TESTING_make_trait_payment_secret ( + &ksls->payment_secret_response), + TALER_TESTING_make_trait_payto_uri ( + (const char **) ksls->pay_uri), + TALER_TESTING_make_trait_order_id ( + (const char **) &ksls->order_id), + ANASTASIS_TESTING_make_trait_code ( + (const char **) &ksls->code), + TALER_TESTING_trait_end () + }; + + return TALER_TESTING_get_trait (traits, + ret, + trait, + index); +} + + +struct TALER_TESTING_Command +ANASTASIS_TESTING_cmd_truth_challenge ( + const char *label, + const char *anastasis_url, + const char *payment_ref, + const char *upload_ref, + unsigned int http_status) +{ + struct TruthChallengeState *ksls; + + GNUNET_assert (NULL != upload_ref); + ksls = GNUNET_new (struct TruthChallengeState); + ksls->expected_http_status = http_status; + ksls->anastasis_url = anastasis_url; + ksls->upload_reference = upload_ref; + ksls->payment_reference = payment_ref; + { + struct TALER_TESTING_Command cmd = { + .cls = ksls, + .label = label, + .run = &truth_challenge_run, + .cleanup = &truth_challenge_cleanup, + .traits = &truth_challenge_traits + }; + + return cmd; + } +} + + +/* end of testing_api_cmd_truth_challenge.c */ diff --git a/src/testing/testing_api_cmd_keyshare_lookup.c b/src/testing/testing_api_cmd_truth_solve.c index 8b06a67..5c12b3f 100644 --- a/src/testing/testing_api_cmd_keyshare_lookup.c +++ b/src/testing/testing_api_cmd_truth_solve.c @@ -1,6 +1,6 @@ /* This file is part of Anastasis - Copyright (C) 2020 Anastasis SARL + Copyright (C) 2020, 2022 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -14,7 +14,7 @@ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** - * @file testing/testing_api_cmd_keyshare_lookup.c + * @file testing/testing_api_cmd_truth_solve.c * @brief Testing of Implementation of the /truth GET * @author Christian Grothoff * @author Dennis Neufeld @@ -31,7 +31,7 @@ /** * State for a "keyshare lookup" CMD. */ -struct KeyShareLookupState +struct TruthSolveState { /** * The interpreter state. @@ -46,12 +46,18 @@ struct KeyShareLookupState /** * Expected status code. */ - enum ANASTASIS_KeyShareDownloadStatus expected_ksdd; + unsigned int expected_http_status; + + /** + * Resulting encrypted key share. + * Note: currently not used. + */ + struct ANASTASIS_CRYPTO_EncryptedKeyShareP eks; /** * The /truth GET operation handle. */ - struct ANASTASIS_KeyShareLookupOperation *kslo; + struct ANASTASIS_TruthSolveOperation *tso; /** * answer to a challenge @@ -113,100 +119,35 @@ struct KeyShareLookupState static void -keyshare_lookup_cb (void *cls, - const struct ANASTASIS_KeyShareDownloadDetails *dd) +truth_solve_cb (void *cls, + const struct ANASTASIS_TruthSolveReply *tsr) { - struct KeyShareLookupState *ksls = cls; + struct TruthSolveState *ksls = cls; - ksls->kslo = NULL; - if (dd->status != ksls->expected_ksdd) + ksls->tso = NULL; + if (tsr->http_status != ksls->expected_http_status) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u to command %s in %s:%u\n", - dd->status, + tsr->http_status, ksls->is->commands[ksls->is->ip].label, __FILE__, __LINE__); TALER_TESTING_interpreter_fail (ksls->is); return; } - switch (dd->status) + switch (tsr->http_status) { - case ANASTASIS_KSD_SUCCESS: - break; - case ANASTASIS_KSD_PAYMENT_REQUIRED: - ksls->pay_uri = GNUNET_strdup (dd->details.payment_required.taler_pay_uri); - ksls->payment_secret_response = dd->details.payment_required.payment_secret; - { - struct TALER_MERCHANT_PayUriData pd; - - if (GNUNET_OK != - TALER_MERCHANT_parse_pay_uri (ksls->pay_uri, - &pd)) - { - GNUNET_break (0); - TALER_TESTING_interpreter_fail (ksls->is); - return; - } - ksls->order_id = GNUNET_strdup (pd.order_id); - TALER_MERCHANT_parse_pay_uri_free (&pd); - } - - break; - case ANASTASIS_KSD_INVALID_ANSWER: - if (ksls->filename) - { - FILE *file; - char code[22]; - - file = fopen (ksls->filename, - "r"); - if (NULL == file) - { - GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, - "open", - ksls->filename); - TALER_TESTING_interpreter_fail (ksls->is); - return; - } - if (0 == fscanf (file, - "%21s", - code)) - { - GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, - "fscanf", - ksls->filename); - GNUNET_break (0 == fclose (file)); - TALER_TESTING_interpreter_fail (ksls->is); - return; - } - GNUNET_break (0 == fclose (file)); - ksls->code = GNUNET_strdup (code); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Read code `%s'\n", - code); - } - else - { - ksls->instructions = GNUNET_strndup ( - dd->details.open_challenge.body, - dd->details.open_challenge.body_size); - } - break; - case ANASTASIS_KSD_REDIRECT_FOR_AUTHENTICATION: - ksls->redirect_uri = GNUNET_strdup (dd->details.redirect_url); + case MHD_HTTP_OK: + ksls->eks = tsr->details.success.eks; break; - case ANASTASIS_KSD_SERVER_ERROR: + case MHD_HTTP_PAYMENT_REQUIRED: + ksls->pay_uri = GNUNET_strdup ( + tsr->details.payment_required.payment_request); + ksls->payment_secret_response = tsr->details.payment_required.ps; + ksls->order_id = GNUNET_strdup (tsr->details.payment_required.pd->order_id); break; - case ANASTASIS_KSD_CLIENT_FAILURE: - break; - case ANASTASIS_KSD_TRUTH_UNKNOWN: - break; - case ANASTASIS_KSD_RATE_LIMIT_EXCEEDED: - break; - case ANASTASIS_KSD_AUTHENTICATION_TIMEOUT: - break; - case ANASTASIS_KSD_EXTERNAL_CHALLENGE_INSTRUCTIONS: + default: break; } TALER_TESTING_interpreter_next (ksls->is); @@ -214,11 +155,11 @@ keyshare_lookup_cb (void *cls, static void -keyshare_lookup_run (void *cls, - const struct TALER_TESTING_Command *cmd, - struct TALER_TESTING_Interpreter *is) +truth_solve_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) { - struct KeyShareLookupState *ksls = cls; + struct TruthSolveState *ksls = cls; const struct ANASTASIS_CRYPTO_TruthKeyP *truth_key; const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid; const struct ANASTASIS_PaymentSecretP *payment_secret; @@ -319,6 +260,12 @@ keyshare_lookup_run (void *cls, /* answer is the answer */ answerp = &ksls->answer; } + if (NULL == answerp) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (ksls->is); + return; + } if (NULL != ksls->payment_reference) { @@ -344,23 +291,20 @@ keyshare_lookup_run (void *cls, { struct GNUNET_HashCode h_answer; - if (NULL != *answerp) - GNUNET_CRYPTO_hash (*answerp, - strlen (*answerp), - &h_answer); - ksls->kslo = ANASTASIS_keyshare_lookup (is->ctx, - ksls->anastasis_url, - truth_uuid, - truth_key, - payment_secret, - GNUNET_TIME_UNIT_ZERO, - (NULL != *answerp) - ? &h_answer - : NULL, - &keyshare_lookup_cb, - ksls); + GNUNET_CRYPTO_hash (*answerp, + strlen (*answerp), + &h_answer); + ksls->tso = ANASTASIS_truth_solve (is->ctx, + ksls->anastasis_url, + truth_uuid, + truth_key, + payment_secret, + GNUNET_TIME_UNIT_ZERO, + &h_answer, + &truth_solve_cb, + ksls); } - if (NULL == ksls->kslo) + if (NULL == ksls->tso) { GNUNET_break (0); TALER_TESTING_interpreter_fail (ksls->is); @@ -370,18 +314,18 @@ keyshare_lookup_run (void *cls, static void -keyshare_lookup_cleanup (void *cls, - const struct TALER_TESTING_Command *cmd) +truth_solve_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) { - struct KeyShareLookupState *ksls = cls; + struct TruthSolveState *ksls = cls; - if (NULL != ksls->kslo) + if (NULL != ksls->tso) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Command '%s' did not complete (keyshare lookup)\n", cmd->label); - ANASTASIS_keyshare_lookup_cancel (ksls->kslo); - ksls->kslo = NULL; + ANASTASIS_truth_solve_cancel (ksls->tso); + ksls->tso = NULL; } GNUNET_free (ksls->pay_uri); GNUNET_free (ksls->order_id); @@ -403,12 +347,12 @@ keyshare_lookup_cleanup (void *cls, * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue -keyshare_lookup_traits (void *cls, - const void **ret, - const char *trait, - unsigned int index) +truth_solve_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) { - struct KeyShareLookupState *ksls = cls; + struct TruthSolveState *ksls = cls; struct TALER_TESTING_Trait traits[] = { ANASTASIS_TESTING_make_trait_payment_secret ( &ksls->payment_secret_response), @@ -429,20 +373,20 @@ keyshare_lookup_traits (void *cls, struct TALER_TESTING_Command -ANASTASIS_TESTING_cmd_keyshare_lookup ( +ANASTASIS_TESTING_cmd_truth_solve ( const char *label, const char *anastasis_url, const char *answer, const char *payment_ref, const char *upload_ref, int lookup_mode, - enum ANASTASIS_KeyShareDownloadStatus ksdd) + unsigned int http_status) { - struct KeyShareLookupState *ksls; + struct TruthSolveState *ksls; GNUNET_assert (NULL != upload_ref); - ksls = GNUNET_new (struct KeyShareLookupState); - ksls->expected_ksdd = ksdd; + ksls = GNUNET_new (struct TruthSolveState); + ksls->expected_http_status = http_status; ksls->anastasis_url = anastasis_url; ksls->upload_reference = upload_ref; ksls->payment_reference = payment_ref; @@ -452,9 +396,9 @@ ANASTASIS_TESTING_cmd_keyshare_lookup ( struct TALER_TESTING_Command cmd = { .cls = ksls, .label = label, - .run = &keyshare_lookup_run, - .cleanup = &keyshare_lookup_cleanup, - .traits = &keyshare_lookup_traits + .run = &truth_solve_run, + .cleanup = &truth_solve_cleanup, + .traits = &truth_solve_traits }; return cmd; @@ -462,4 +406,4 @@ ANASTASIS_TESTING_cmd_keyshare_lookup ( } -/* end of testing_api_cmd_keyshare_lookup.c */ +/* end of testing_api_cmd_truth_solve.c */ diff --git a/src/testing/testing_cmd_challenge_answer.c b/src/testing/testing_cmd_challenge_answer.c index 78f7404..88c4c2f 100644 --- a/src/testing/testing_cmd_challenge_answer.c +++ b/src/testing/testing_cmd_challenge_answer.c @@ -1,6 +1,6 @@ /* This file is part of Anastasis - Copyright (C) 2020, 2021 Anastasis SARL + Copyright (C) 2020, 2021, 2022 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -20,7 +20,6 @@ * @author Dennis Neufeld * @author Dominik Meister */ - #include "platform.h" #include "anastasis_testing_lib.h" #include <taler/taler_util.h> @@ -28,6 +27,8 @@ #include <taler/taler_merchant_service.h> +// FIXME: break up into two files, one for start, one for answer! + /** * State for a "challenge answer" CMD. */ @@ -74,9 +75,14 @@ struct ChallengeState struct ANASTASIS_PaymentSecretP payment_order_req; /** - * Expected status code. + * Expected answer status code. + */ + enum ANASTASIS_ChallengeAnswerStatus expected_acs; + + /** + * Expected start status code. */ - enum ANASTASIS_ChallengeStatus expected_cs; + enum ANASTASIS_ChallengeStartStatus expected_scs; /** * Index of the challenge we are solving @@ -98,97 +104,28 @@ struct ChallengeState static void challenge_answer_cb (void *af_cls, - const struct ANASTASIS_ChallengeStartResponse *csr) + const struct ANASTASIS_ChallengeAnswerResponse *csr) { struct ChallengeState *cs = af_cls; cs->c = NULL; - if (csr->cs != cs->expected_cs) + if (csr->cs != cs->expected_acs) { GNUNET_break (0); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Expected status %u, got %u\n", - cs->expected_cs, + cs->expected_acs, csr->cs); TALER_TESTING_interpreter_fail (cs->is); return; } switch (csr->cs) { - case ANASTASIS_CHALLENGE_STATUS_SOLVED: + case ANASTASIS_CHALLENGE_ANSWER_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; - } - cs->code = GNUNET_malloc (22); - 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: + case ANASTASIS_CHALLENGE_ANSWER_STATUS_INVALID_ANSWER: + break; + case ANASTASIS_CHALLENGE_ANSWER_STATUS_PAYMENT_REQUIRED: if (0 != strncmp (csr->details.payment_required.taler_pay_uri, "taler+http://pay/", strlen ("taler+http://pay/"))) @@ -228,19 +165,15 @@ challenge_answer_cb (void *af_cls, } TALER_TESTING_interpreter_next (cs->is); return; - case ANASTASIS_CHALLENGE_STATUS_TRUTH_UNKNOWN: - break; - case ANASTASIS_CHALLENGE_STATUS_REDIRECT_FOR_AUTHENTICATION: + case ANASTASIS_CHALLENGE_ANSWER_STATUS_TRUTH_UNKNOWN: break; - case ANASTASIS_CHALLENGE_STATUS_SERVER_FAILURE: + case ANASTASIS_CHALLENGE_ANSWER_STATUS_SERVER_FAILURE: GNUNET_break (0); TALER_TESTING_interpreter_fail (cs->is); return; - case ANASTASIS_CHALLENGE_STATUS_RATE_LIMIT_EXCEEDED: + case ANASTASIS_CHALLENGE_ANSWER_STATUS_RATE_LIMIT_EXCEEDED: break; - case ANASTASIS_CHALLENGE_STATUS_AUTH_TIMEOUT: - break; - case ANASTASIS_CHALLENGE_STATUS_EXTERNAL_INSTRUCTIONS: + case ANASTASIS_CHALLENGE_ANSWER_STATUS_AUTH_TIMEOUT: break; } TALER_TESTING_interpreter_next (cs->is); @@ -382,6 +315,115 @@ challenge_answer_run (void *cls, } +static void +challenge_start_cb (void *af_cls, + const struct ANASTASIS_ChallengeStartResponse *csr) +{ + struct ChallengeState *cs = af_cls; + + cs->c = NULL; + if (csr->cs != cs->expected_scs) + { + GNUNET_break (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Expected status %u, got %u\n", + cs->expected_scs, + csr->cs); + TALER_TESTING_interpreter_fail (cs->is); + return; + } + switch (csr->cs) + { + case ANASTASIS_CHALLENGE_START_STATUS_FILENAME_PROVIDED: + { + FILE *file; + char code[22]; + + file = fopen (csr->details.tan_filename, + "r"); + if (NULL == file) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "open", + csr->details.tan_filename); + TALER_TESTING_interpreter_fail (cs->is); + return; + } + if (0 == fscanf (file, + "%21s", + code)) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "fscanf", + csr->details.tan_filename); + GNUNET_break (0 == fclose (file)); + TALER_TESTING_interpreter_fail (cs->is); + return; + } + GNUNET_break (0 == fclose (file)); + cs->code = GNUNET_strdup (code); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Read code `%s'\n", + code); + } + break; + case ANASTASIS_CHALLENGE_START_STATUS_TAN_SENT_HINT_PROVIDED: + GNUNET_break (0); /* FIXME: not implemented */ + break; + case ANASTASIS_CHALLENGE_START_STATUS_BANK_TRANSFER_REQUIRED: + GNUNET_break (0); /* FIXME: not implemented */ + break; + case ANASTASIS_CHALLENGE_START_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_START_STATUS_TRUTH_UNKNOWN: + break; + case ANASTASIS_CHALLENGE_START_STATUS_SERVER_FAILURE: + GNUNET_break (0); + TALER_TESTING_interpreter_fail (cs->is); + return; + } + TALER_TESTING_interpreter_next (cs->is); +} + + /** * Run a "recover secret" CMD. * @@ -446,9 +488,7 @@ challenge_start_run (void *cls, if (GNUNET_OK != ANASTASIS_challenge_start ((struct ANASTASIS_Challenge *) *c, ps, - GNUNET_TIME_UNIT_ZERO, - NULL, - &challenge_answer_cb, + &challenge_start_cb, cs)) { GNUNET_break (0); @@ -527,12 +567,12 @@ ANASTASIS_TESTING_cmd_challenge_start ( const char *payment_ref, const char *challenge_ref, unsigned int challenge_index, - enum ANASTASIS_ChallengeStatus expected_cs) + enum ANASTASIS_ChallengeStartStatus expected_cs) { struct ChallengeState *cs; cs = GNUNET_new (struct ChallengeState); - cs->expected_cs = expected_cs; + cs->expected_scs = expected_cs; cs->challenge_ref = challenge_ref; cs->payment_ref = payment_ref; cs->challenge_index = challenge_index; @@ -558,12 +598,12 @@ ANASTASIS_TESTING_cmd_challenge_answer ( unsigned int challenge_index, const char *answer, unsigned int mode, - enum ANASTASIS_ChallengeStatus expected_cs) + enum ANASTASIS_ChallengeAnswerStatus expected_cs) { struct ChallengeState *cs; cs = GNUNET_new (struct ChallengeState); - cs->expected_cs = expected_cs; + cs->expected_acs = expected_cs; cs->challenge_ref = challenge_ref; cs->payment_ref = payment_ref; cs->answer = answer; |