From d153d8036ea14503f812717b8994a4a845ab643e Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 23 Aug 2021 18:22:06 +0200 Subject: -more work on iban logic --- .../anastasis_authorization_plugin_iban.c | 14 ++- src/backend/anastasis-httpd_truth.c | 29 ++++- src/cli/resources/03-backup.json | 12 +- src/cli/resources/04-backup.json | 12 +- src/cli/resources/05-backup.json | 12 +- src/cli/resources/06-backup.json | 12 +- src/cli/test_iban.sh | 2 +- src/include/anastasis_database_plugin.h | 26 ++++ src/reducer/anastasis_api_recovery_redux.c | 104 +++++++++++++--- src/stasis/plugin_anastasis_postgres.c | 135 ++++++++++++++++++++- 10 files changed, 322 insertions(+), 36 deletions(-) (limited to 'src') diff --git a/src/authorization/anastasis_authorization_plugin_iban.c b/src/authorization/anastasis_authorization_plugin_iban.c index 3c72e16..49c2ad5 100644 --- a/src/authorization/anastasis_authorization_plugin_iban.c +++ b/src/authorization/anastasis_authorization_plugin_iban.c @@ -328,9 +328,19 @@ respond_with_challenge (struct ANASTASIS_AUTHORIZATION_State *as, if (TALER_MHD_xmime_matches (mime, "application/json")) { + char subject[64]; + + GNUNET_snprintf (subject, + sizeof (subject), + "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_uint64 ("answer_code", + as->code), GNUNET_JSON_pack_object_steal ( "details", GNUNET_JSON_PACK ( @@ -340,8 +350,8 @@ respond_with_challenge (struct ANASTASIS_AUTHORIZATION_State *as, ctx->business_iban), GNUNET_JSON_pack_string ("business_name", ctx->business_name), - GNUNET_JSON_pack_uint64 ("wire_transfer_subject", - as->code)))); + GNUNET_JSON_pack_string ("wire_transfer_subject", + subject)))); } else { diff --git a/src/backend/anastasis-httpd_truth.c b/src/backend/anastasis-httpd_truth.c index 2c757db..3b853dc 100644 --- a/src/backend/anastasis-httpd_truth.c +++ b/src/backend/anastasis-httpd_truth.c @@ -1435,6 +1435,33 @@ AH_handler_truth_get ( connection); } /* continue with authorization plugin below */ + { + enum GNUNET_DB_QueryStatus qs; + + qs = db->get_unlimited_challenge_code ( + db->cls, + &gc->truth_uuid, + gc->authorization->code_rotation_period, + gc->authorization->code_validity_period, + &gc->code); + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + case GNUNET_DB_STATUS_SOFT_ERROR: + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + 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_ONE_RESULT: + /* challenge code was stored successfully*/ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Got challenge code\n"); + break; + } + } break; default: GNUNET_break (0); @@ -1491,7 +1518,7 @@ AH_handler_truth_get ( return TALER_MHD_reply_with_error (gc->connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_FETCH_FAILED, - "store_challenge_code"); + "create_challenge_code"); case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: /* 0 == retry_counter of existing challenge => rate limit exceeded */ GNUNET_free (decrypted_truth); diff --git a/src/cli/resources/03-backup.json b/src/cli/resources/03-backup.json index 9d599d7..4dd5368 100644 --- a/src/cli/resources/03-backup.json +++ b/src/cli/resources/03-backup.json @@ -55,7 +55,8 @@ "currency": "TESTKUDOS", "business_name": "Data loss #1 Inc.", "storage_limit_in_megabytes": 1, - "salt": "F0HEYJQW81ZAZ3VYMZHFG8T1Z0" + "salt": "F0HEYJQW81ZAZ3VYMZHFG8T1Z0", + "http_status": 200 }, "http://localhost:8087/": { "methods": [ @@ -73,7 +74,8 @@ "currency": "TESTKUDOS", "business_name": "Data loss #2 Inc.", "storage_limit_in_megabytes": 1, - "salt": "D378FWXHJB8JHPQFQRZGGV9PWG" + "salt": "D378FWXHJB8JHPQFQRZGGV9PWG", + "http_status": 200 }, "http://localhost:8088/": { "methods": [ @@ -91,7 +93,8 @@ "currency": "TESTKUDOS", "business_name": "Data loss #3 Inc.", "storage_limit_in_megabytes": 1, - "salt": "7W9W4A4TTWSWRPJ76RNDPJHSPR" + "salt": "7W9W4A4TTWSWRPJ76RNDPJHSPR", + "http_status": 200 }, "http://localhost:8089/": { "methods": [ @@ -109,7 +112,8 @@ "currency": "TESTKUDOS", "business_name": "Data loss #4 Inc.", "storage_limit_in_megabytes": 1, - "salt": "PN0VJF6KDSBYN40SGRCEXPB07M" + "salt": "PN0VJF6KDSBYN40SGRCEXPB07M", + "http_status": 200 } }, "selected_country": "xx", diff --git a/src/cli/resources/04-backup.json b/src/cli/resources/04-backup.json index 15c329a..db51f5a 100644 --- a/src/cli/resources/04-backup.json +++ b/src/cli/resources/04-backup.json @@ -55,7 +55,8 @@ "currency": "TESTKUDOS", "business_name": "Data loss #1 Inc.", "storage_limit_in_megabytes": 1, - "salt": "F0HEYJQW81ZAZ3VYMZHFG8T1Z0" + "salt": "F0HEYJQW81ZAZ3VYMZHFG8T1Z0", + "http_status": 200 }, "http://localhost:8087/": { "methods": [ @@ -73,7 +74,8 @@ "currency": "TESTKUDOS", "business_name": "Data loss #2 Inc.", "storage_limit_in_megabytes": 1, - "salt": "D378FWXHJB8JHPQFQRZGGV9PWG" + "salt": "D378FWXHJB8JHPQFQRZGGV9PWG", + "http_status": 200 }, "http://localhost:8088/": { "methods": [ @@ -91,7 +93,8 @@ "currency": "TESTKUDOS", "business_name": "Data loss #3 Inc.", "storage_limit_in_megabytes": 1, - "salt": "7W9W4A4TTWSWRPJ76RNDPJHSPR" + "salt": "7W9W4A4TTWSWRPJ76RNDPJHSPR", + "http_status": 200 }, "http://localhost:8089/": { "methods": [ @@ -109,7 +112,8 @@ "currency": "TESTKUDOS", "business_name": "Data loss #4 Inc.", "storage_limit_in_megabytes": 1, - "salt": "PN0VJF6KDSBYN40SGRCEXPB07M" + "salt": "PN0VJF6KDSBYN40SGRCEXPB07M", + "http_status": 200 } }, "selected_country": "xx", diff --git a/src/cli/resources/05-backup.json b/src/cli/resources/05-backup.json index c0ce8ae..143d9e3 100644 --- a/src/cli/resources/05-backup.json +++ b/src/cli/resources/05-backup.json @@ -55,7 +55,8 @@ "currency": "TESTKUDOS", "business_name": "Data loss #1 Inc.", "storage_limit_in_megabytes": 1, - "salt": "F0HEYJQW81ZAZ3VYMZHFG8T1Z0" + "salt": "F0HEYJQW81ZAZ3VYMZHFG8T1Z0", + "http_status": 200 }, "http://localhost:8087/": { "methods": [ @@ -73,7 +74,8 @@ "currency": "TESTKUDOS", "business_name": "Data loss #2 Inc.", "storage_limit_in_megabytes": 1, - "salt": "D378FWXHJB8JHPQFQRZGGV9PWG" + "salt": "D378FWXHJB8JHPQFQRZGGV9PWG", + "http_status": 200 }, "http://localhost:8088/": { "methods": [ @@ -91,7 +93,8 @@ "currency": "TESTKUDOS", "business_name": "Data loss #3 Inc.", "storage_limit_in_megabytes": 1, - "salt": "7W9W4A4TTWSWRPJ76RNDPJHSPR" + "salt": "7W9W4A4TTWSWRPJ76RNDPJHSPR", + "http_status": 200 }, "http://localhost:8089/": { "methods": [ @@ -109,7 +112,8 @@ "currency": "TESTKUDOS", "business_name": "Data loss #4 Inc.", "storage_limit_in_megabytes": 1, - "salt": "PN0VJF6KDSBYN40SGRCEXPB07M" + "salt": "PN0VJF6KDSBYN40SGRCEXPB07M", + "http_status": 200 } }, "selected_country": "xx", diff --git a/src/cli/resources/06-backup.json b/src/cli/resources/06-backup.json index d1f0b9e..9944a17 100644 --- a/src/cli/resources/06-backup.json +++ b/src/cli/resources/06-backup.json @@ -55,7 +55,8 @@ "currency": "TESTKUDOS", "business_name": "Data loss #1 Inc.", "storage_limit_in_megabytes": 1, - "salt": "F0HEYJQW81ZAZ3VYMZHFG8T1Z0" + "salt": "F0HEYJQW81ZAZ3VYMZHFG8T1Z0", + "http_status": 200 }, "http://localhost:8087/": { "methods": [ @@ -73,7 +74,8 @@ "currency": "TESTKUDOS", "business_name": "Data loss #2 Inc.", "storage_limit_in_megabytes": 1, - "salt": "D378FWXHJB8JHPQFQRZGGV9PWG" + "salt": "D378FWXHJB8JHPQFQRZGGV9PWG", + "http_status": 200 }, "http://localhost:8088/": { "methods": [ @@ -91,7 +93,8 @@ "currency": "TESTKUDOS", "business_name": "Data loss #3 Inc.", "storage_limit_in_megabytes": 1, - "salt": "7W9W4A4TTWSWRPJ76RNDPJHSPR" + "salt": "7W9W4A4TTWSWRPJ76RNDPJHSPR", + "http_status": 200 }, "http://localhost:8089/": { "methods": [ @@ -109,7 +112,8 @@ "currency": "TESTKUDOS", "business_name": "Data loss #4 Inc.", "storage_limit_in_megabytes": 1, - "salt": "PN0VJF6KDSBYN40SGRCEXPB07M" + "salt": "PN0VJF6KDSBYN40SGRCEXPB07M", + "http_status": 200 } }, "selected_country": "xx", diff --git a/src/cli/test_iban.sh b/src/cli/test_iban.sh index 777bcfb..8045db3 100755 --- a/src/cli/test_iban.sh +++ b/src/cli/test_iban.sh @@ -429,7 +429,7 @@ SUBJECT="Anastasis ${NUMBER}" # FIXME-MS: must do wire transfer using $SUBJECT and $AMOUNT here! -#bash +bash echo "TEST INCOMPLETE --- BAILING for now" diff --git a/src/include/anastasis_database_plugin.h b/src/include/anastasis_database_plugin.h index 1265f7b..079201d 100644 --- a/src/include/anastasis_database_plugin.h +++ b/src/include/anastasis_database_plugin.h @@ -660,6 +660,32 @@ struct ANASTASIS_DatabasePlugin uint64_t *code); + /** + * Setup challenge code for a given challenge identified by the + * challenge public key. The function will first check if there is + * already a valid code for this challenge present and won't insert + * a new one in this case. This variant is not rate-limited, will + * return the existing challenge if it has not yet expired and will + * simply create new challenges when the old ones need to be + * rotated. + * + * @param cls closure + * @param truth_uuid the identifier for the challenge + * @param rotation_period for how long is the code available + * @param validity_period for how long is the code available + * @param[out] code set to the code which will be checked for later + * @return transaction status, + * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if @a code is now in the DB + */ + enum GNUNET_DB_QueryStatus + (*get_unlimited_challenge_code)( + void *cls, + const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, + struct GNUNET_TIME_Relative rotation_period, + struct GNUNET_TIME_Relative validity_period, + uint64_t *code); + + /** * Remember in the database that we successfully sent a challenge. * diff --git a/src/reducer/anastasis_api_recovery_redux.c b/src/reducer/anastasis_api_recovery_redux.c index c29a08a..6b85a75 100644 --- a/src/reducer/anastasis_api_recovery_redux.c +++ b/src/reducer/anastasis_api_recovery_redux.c @@ -398,6 +398,51 @@ find_challenge_in_ri (json_t *state, } +/** + * Find challenge of @a uuid in @a state under "cs". + * + * @param state the state to search + * @param uuid the UUID to search for + * @return NULL on error, otherwise challenge entry; RC is NOT incremented + */ +static json_t * +find_challenge_in_cs (json_t *state, + const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid) +{ + json_t *rd = json_object_get (state, + "recovery_document"); + json_t *cs = json_object_get (rd, + "cs"); + json_t *c; + size_t off; + + json_array_foreach (cs, off, c) + { + struct ANASTASIS_CRYPTO_TruthUUIDP u; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("uuid", + &u), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (c, + spec, + NULL, NULL)) + { + GNUNET_break (0); + continue; + } + if (0 != + GNUNET_memcmp (uuid, + &u)) + continue; + return c; + } + return NULL; +} + + /** * Defines a callback for the response status for a challenge start * operation. @@ -713,9 +758,17 @@ answer_feedback_cb ( 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 () @@ -741,7 +794,31 @@ answer_feedback_cb ( err)); return; } + if (is_async) + { + json_t *c = find_challenge_in_cs (sctx->state, + &cd->uuid); + if (NULL == c) + { + GNUNET_break (0); + set_state (sctx->state, + ANASTASIS_RECOVERY_STATE_ERROR); + sctx->cb (sctx->cb_cls, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + sctx->state); + 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"), @@ -815,10 +892,11 @@ solve_challenge_cb (void *cls, return; } - if (GNUNET_OK != - GNUNET_JSON_parse (sctx->args, - tspec, - NULL, NULL)) + if ( (NULL != sctx->args) && + (GNUNET_OK != + GNUNET_JSON_parse (sctx->args, + tspec, + NULL, NULL)) ) { GNUNET_break_op (0); ANASTASIS_redux_fail_ (sctx->cb, @@ -845,7 +923,7 @@ solve_challenge_cb (void *cls, (! cd->async) ) continue; - challenge = find_challenge_in_ri (sctx->state, + challenge = find_challenge_in_cs (sctx->state, &cd->uuid); if (NULL == challenge) { @@ -963,6 +1041,7 @@ solve_challenge_cb (void *cls, struct ANASTASIS_Challenge *ci = ri->cs[i]; const struct ANASTASIS_ChallengeDetails *cd; int ret; + json_t *c; cd = ANASTASIS_challenge_get_details (ci); if (cd->async) @@ -980,6 +1059,9 @@ solve_challenge_cb (void *cls, sctx_free (sctx); return; } + c = find_challenge_in_cs (sctx->state, + &cd->uuid); + GNUNET_assert (NULL != c); if (0 == strcmp ("question", cd->type)) { @@ -999,7 +1081,7 @@ solve_challenge_cb (void *cls, } /* persist answer, in case payment is required */ GNUNET_assert (0 == - json_object_set (challenge, + json_object_set (c, "answer", janswer)); ret = ANASTASIS_challenge_answer (ci, @@ -1023,7 +1105,7 @@ solve_challenge_cb (void *cls, /* persist answer, in case async processing happens via poll */ GNUNET_assert (0 == - json_object_set (challenge, + json_object_set (c, "answer-pin", pin)); ret = ANASTASIS_challenge_answer2 (ci, @@ -1316,14 +1398,6 @@ poll_challenges (json_t *state, = GNUNET_new (struct SelectChallengeContext); json_t *rd; - if (NULL == arguments) - { - ANASTASIS_redux_fail_ (cb, - cb_cls, - TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID, - "arguments missing"); - return NULL; - } rd = json_object_get (state, "recovery_document"); if (NULL == rd) diff --git a/src/stasis/plugin_anastasis_postgres.c b/src/stasis/plugin_anastasis_postgres.c index 8c06243..506c304 100644 --- a/src/stasis/plugin_anastasis_postgres.c +++ b/src/stasis/plugin_anastasis_postgres.c @@ -38,6 +38,12 @@ */ #define MAX_RETRIES 3 +/** + * Maximum value allowed for nonces. Limited to 2^52 to ensure the + * numeric value survives a conversion to float by JavaScript. + */ +#define NONCE_MAX_VALUE (1LLU << 52) + /** * Type of the "cls" argument given to each of the functions in @@ -2125,7 +2131,7 @@ postgres_create_challenge_code ( } *code = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE, - INT64_MAX); + NONCE_MAX_VALUE); *retransmission_date = GNUNET_TIME_UNIT_ZERO_ABS; { struct GNUNET_PQ_QueryParam params[] = { @@ -2168,6 +2174,132 @@ retry: } +/** + * Setup challenge code for a given challenge identified by the + * challenge public key. The function will first check if there is + * already a valid code for this challenge present and won't insert + * a new one in this case. This variant is not rate-limited, will + * return the existing challenge if it has not yet expired and will + * simply create new challenges when the old ones need to be + * rotated. + * + * @param cls closure + * @param truth_uuid the identifier for the challenge + * @param rotation_period for how long is the code available + * @param validity_period for how long is the code available + * @param[out] code set to the code which will be checked for later + * @return transaction status, + * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if @a code is now in the DB + */ +static enum GNUNET_DB_QueryStatus +postgres_get_unlimited_challenge_code ( + void *cls, + const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, + struct GNUNET_TIME_Relative rotation_period, + struct GNUNET_TIME_Relative validity_period, + uint64_t *code) +{ + struct PostgresClosure *pg = cls; + enum GNUNET_DB_QueryStatus qs; + struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); + struct GNUNET_TIME_Absolute expiration_date; + struct GNUNET_TIME_Absolute ex_rot; + + check_connection (pg); + GNUNET_TIME_round_abs (&now); + expiration_date = GNUNET_TIME_absolute_add (now, + validity_period); + ex_rot = GNUNET_TIME_absolute_subtract (now, + rotation_period); + for (unsigned int retries = 0; retriesconn, + "challengecode_select_meta", + params, + rs); + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + GNUNET_break (0); + rollback (pg); + return qs; + case GNUNET_DB_STATUS_SOFT_ERROR: + goto retry; + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + /* no active challenge, create fresh one (below) */ + break; + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + rollback (pg); + return qs; + } + } + + *code = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE, + NONCE_MAX_VALUE); + { + uint32_t retry_counter = UINT32_MAX; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (truth_uuid), + GNUNET_PQ_query_param_uint64 (code), + TALER_PQ_query_param_absolute_time (&now), + TALER_PQ_query_param_absolute_time (&expiration_date), + GNUNET_PQ_query_param_uint32 (&retry_counter), + GNUNET_PQ_query_param_end + }; + + qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, + "challengecode_insert", + params); + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + rollback (pg); + return GNUNET_DB_STATUS_HARD_ERROR; + case GNUNET_DB_STATUS_SOFT_ERROR: + goto retry; + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + GNUNET_break (0); + rollback (pg); + return GNUNET_DB_STATUS_HARD_ERROR; + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + break; + } + } + qs = commit_transaction (pg); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + goto retry; + if (qs < 0) + return qs; + return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; +retry: + rollback (pg); + } + return GNUNET_DB_STATUS_SOFT_ERROR; +} + + /** * Remember in the database that we successfully sent a challenge. * @@ -2675,6 +2807,7 @@ libanastasis_plugin_db_postgres_init (void *cls) plugin->test_challenge_code_satisfied = &postgres_test_challenge_code_satisfied; plugin->create_challenge_code = &postgres_create_challenge_code; + plugin->get_unlimited_challenge_code = &postgres_get_unlimited_challenge_code; plugin->mark_challenge_sent = &postgres_mark_challenge_sent; plugin->challenge_gc = &postgres_challenge_gc; plugin->record_truth_upload_payment = &postgres_record_truth_upload_payment; -- cgit v1.2.3