From 92ac6dd11a35f3c484a54ecb47ef3ca66cf30d0f Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Wed, 8 Jul 2020 21:24:10 +0200 Subject: implement new behavior in exchange httpd and libtalerexchange (for #6416) --- src/exchange/taler-exchange-httpd_responses.c | 20 +++- src/include/taler_exchange_service.h | 2 + src/lib/exchange_api_common.c | 155 +++++++++++++++++++++++++- src/lib/exchange_api_deposit.c | 57 +++++++--- src/lib/exchange_api_melt.c | 56 +++++++--- src/lib/exchange_api_recoup.c | 40 +++++++ src/testing/testing_api_cmd_insert_deposit.c | 13 ++- 7 files changed, 294 insertions(+), 49 deletions(-) (limited to 'src') diff --git a/src/exchange/taler-exchange-httpd_responses.c b/src/exchange/taler-exchange-httpd_responses.c index 979832958..328d52bd9 100644 --- a/src/exchange/taler-exchange-httpd_responses.c +++ b/src/exchange/taler-exchange-httpd_responses.c @@ -273,7 +273,7 @@ TEH_RESPONSE_compile_transaction_history ( if (0 != json_array_append_new ( history, - json_pack ("{s:s, s:o, s:o, s:o, s:o, s:o, s:o}", + json_pack ("{s:s, s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:o}", "type", "OLD-COIN-RECOUP", "amount", @@ -284,8 +284,8 @@ TEH_RESPONSE_compile_transaction_history ( GNUNET_JSON_from_data_auto (&epub), "coin_pub", GNUNET_JSON_from_data_auto (&pr->coin.coin_pub), - "h_denom_pub", - GNUNET_JSON_from_data_auto (&pr->coin.denom_pub_hash), + "coin_sig", + GNUNET_JSON_from_data_auto (&pr->coin_sig), "timestamp", GNUNET_JSON_from_time_abs (pr->timestamp)))) { @@ -323,7 +323,7 @@ TEH_RESPONSE_compile_transaction_history ( if (0 != json_array_append_new ( history, - json_pack ("{s:s, s:o, s:o, s:o, s:o, s:o, s:o}", + json_pack ("{s:s, s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:o}", "type", "RECOUP", "amount", @@ -336,6 +336,12 @@ TEH_RESPONSE_compile_transaction_history ( GNUNET_JSON_from_data_auto (&recoup->reserve_pub), "h_denom_pub", GNUNET_JSON_from_data_auto (&recoup->h_denom_pub), + "coin_sig", + GNUNET_JSON_from_data_auto (&recoup->coin_sig), + "coin_blind", + GNUNET_JSON_from_data_auto (&recoup->coin_blind), + "reserve_pub", + GNUNET_JSON_from_data_auto (&recoup->reserve_pub), "timestamp", GNUNET_JSON_from_time_abs (recoup->timestamp)))) { @@ -379,7 +385,7 @@ TEH_RESPONSE_compile_transaction_history ( if (0 != json_array_append_new ( history, - json_pack ("{s:s, s:o, s:o, s:o, s:o, s:o, s:o}", + json_pack ("{s:s, s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:o}", "type", "RECOUP-REFRESH", "amount", @@ -392,6 +398,10 @@ TEH_RESPONSE_compile_transaction_history ( GNUNET_JSON_from_data_auto (&pr->old_coin_pub), "h_denom_pub", GNUNET_JSON_from_data_auto (&pr->coin.denom_pub_hash), + "coin_sig", + GNUNET_JSON_from_data_auto (&pr->coin_sig), + "coin_blind", + GNUNET_JSON_from_data_auto (&pr->coin_blind), "timestamp", GNUNET_JSON_from_time_abs (pr->timestamp)))) { diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h index b12edb05c..4d8adcc81 100644 --- a/src/include/taler_exchange_service.h +++ b/src/include/taler_exchange_service.h @@ -1811,6 +1811,7 @@ TALER_EXCHANGE_deposits_get_cancel ( * @param currency expected currency for the coin * @param coin_pub public key of the coin * @param history history of the coin in json encoding + * @param[out] h_denom_pub set to the hash of the coin's denomination (if available) * @param[out] total how much of the coin has been spent according to @a history * @return #GNUNET_OK if @a history is valid, #GNUNET_SYSERR if not */ @@ -1820,6 +1821,7 @@ TALER_EXCHANGE_verify_coin_history ( const char *currency, const struct TALER_CoinSpendPublicKeyP *coin_pub, json_t *history, + struct GNUNET_HashCode *h_denom_pub, struct TALER_Amount *total); diff --git a/src/lib/exchange_api_common.c b/src/lib/exchange_api_common.c index 17be0d51b..feaef7b3d 100644 --- a/src/lib/exchange_api_common.c +++ b/src/lib/exchange_api_common.c @@ -447,6 +447,7 @@ TALER_EXCHANGE_free_reserve_history ( * @param currency expected currency for the coin * @param coin_pub public key of the coin * @param history history of the coin in json encoding + * @param[out] h_denom_pub set to the hash of the coin's denomination (if available) * @param[out] total how much of the coin has been spent according to @a history * @return #GNUNET_OK if @a history is valid, #GNUNET_SYSERR if not */ @@ -456,6 +457,7 @@ TALER_EXCHANGE_verify_coin_history ( const char *currency, const struct TALER_CoinSpendPublicKeyP *coin_pub, json_t *history, + struct GNUNET_HashCode *h_denom_pub, struct TALER_Amount *total) { size_t len; @@ -558,6 +560,7 @@ TALER_EXCHANGE_verify_coin_history ( GNUNET_break_op (0); return GNUNET_SYSERR; } + *h_denom_pub = dr.h_denom_pub; if (NULL != dk) { /* check that deposit fee matches our expectations from /keys! */ @@ -615,6 +618,7 @@ TALER_EXCHANGE_verify_coin_history ( GNUNET_break_op (0); return GNUNET_SYSERR; } + *h_denom_pub = rm.h_denom_pub; if (NULL != dk) { /* check that melt fee matches our expectations from /keys! */ @@ -703,16 +707,159 @@ TALER_EXCHANGE_verify_coin_history ( else if (0 == strcasecmp (type, "RECOUP")) { - struct TALER_RecoupConfirmationPS pc; + struct TALER_RecoupConfirmationPS pc = { + .purpose.size = htonl (sizeof (pc)), + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP), + .coin_pub = *coin_pub + }; + struct TALER_RecoupRequestPS rr = { + .purpose.size = htonl (sizeof (pc)), + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_RECOUP), + .coin_pub = *coin_pub + }; struct TALER_ExchangePublicKeyP exchange_pub; struct TALER_ExchangeSignatureP exchange_sig; + struct TALER_CoinSpendSignatureP coin_sig; struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount_nbo ("amount", + &pc.recoup_amount), GNUNET_JSON_spec_fixed_auto ("exchange_sig", &exchange_sig), GNUNET_JSON_spec_fixed_auto ("exchange_pub", &exchange_pub), GNUNET_JSON_spec_fixed_auto ("reserve_pub", &pc.reserve_pub), + GNUNET_JSON_spec_fixed_auto ("coin_sig", + &coin_sig), + GNUNET_JSON_spec_fixed_auto ("coin_blind", + &rr.coin_blind), + GNUNET_JSON_spec_fixed_auto ("h_denom_pub", + &rr.h_denom_pub), + TALER_JSON_spec_absolute_time_nbo ("timestamp", + &pc.timestamp), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + TALER_amount_hton (&pc.recoup_amount, + &amount); + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP, + &pc, + &exchange_sig.eddsa_signature, + &exchange_pub.eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_RECOUP, + &rr, + &coin_sig.eddsa_signature, + &coin_pub->eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + *h_denom_pub = rr.h_denom_pub; + add = GNUNET_YES; + } + else if (0 == strcasecmp (type, + "RECOUP-REFRESH")) + { + struct TALER_RecoupRefreshConfirmationPS pc = { + .purpose.size = htonl (sizeof (pc)), + .purpose.purpose = htonl ( + TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_REFRESH), + .coin_pub = *coin_pub + }; + struct TALER_RecoupRequestPS rr = { + .purpose.size = htonl (sizeof (pc)), + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_RECOUP), + .coin_pub = *coin_pub + }; + struct TALER_ExchangePublicKeyP exchange_pub; + struct TALER_ExchangeSignatureP exchange_sig; + struct TALER_CoinSpendSignatureP coin_sig; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount_nbo ("amount", + &pc.recoup_amount), + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &exchange_pub), + GNUNET_JSON_spec_fixed_auto ("coin_sig", + &coin_sig), + GNUNET_JSON_spec_fixed_auto ("old_coin_pub", + &pc.old_coin_pub), + GNUNET_JSON_spec_fixed_auto ("coin_blind", + &rr.coin_blind), + GNUNET_JSON_spec_fixed_auto ("h_denom_pub", + &rr.h_denom_pub), + TALER_JSON_spec_absolute_time_nbo ("timestamp", + &pc.timestamp), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + TALER_amount_hton (&pc.recoup_amount, + &amount); + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify ( + TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_REFRESH, + &pc, + &exchange_sig.eddsa_signature, + &exchange_pub.eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_RECOUP, + &rr, + &coin_sig.eddsa_signature, + &coin_pub->eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + *h_denom_pub = rr.h_denom_pub; + add = GNUNET_YES; + } + else if (0 == strcasecmp (type, + "OLD-COIN-RECOUP")) + { + struct TALER_RecoupRefreshConfirmationPS pc = { + .purpose.size = htonl (sizeof (pc)), + .purpose.purpose = htonl ( + TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_REFRESH), + .old_coin_pub = *coin_pub + }; + struct TALER_ExchangePublicKeyP exchange_pub; + struct TALER_ExchangeSignatureP exchange_sig; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount_nbo ("amount", + &pc.recoup_amount), + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &exchange_pub), + GNUNET_JSON_spec_fixed_auto ("coin_pub", + &pc.coin_pub), TALER_JSON_spec_absolute_time_nbo ("timestamp", &pc.timestamp), GNUNET_JSON_spec_end () @@ -726,9 +873,6 @@ TALER_EXCHANGE_verify_coin_history ( GNUNET_break_op (0); return GNUNET_SYSERR; } - pc.purpose.size = htonl (sizeof (pc)); - pc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP); - pc.coin_pub = *coin_pub; TALER_amount_hton (&pc.recoup_amount, &amount); if (GNUNET_OK != @@ -749,6 +893,7 @@ TALER_EXCHANGE_verify_coin_history ( GNUNET_assert (GNUNET_SYSERR == add); return GNUNET_SYSERR; } + if (GNUNET_YES == add) { /* This amount should be added to the total */ @@ -779,9 +924,11 @@ TALER_EXCHANGE_verify_coin_history ( GNUNET_break_op (0); return GNUNET_SYSERR; } + } } + /* Finally, subtract 'rtotal' from total to handle the subtractions */ if (0 > TALER_amount_subtract (total, diff --git a/src/lib/exchange_api_deposit.c b/src/lib/exchange_api_deposit.c index ba3ba48e0..51783dbb4 100644 --- a/src/lib/exchange_api_deposit.c +++ b/src/lib/exchange_api_deposit.c @@ -246,13 +246,18 @@ verify_deposit_signature_ok (struct TALER_EXCHANGE_DepositHandle *dh, * @return #GNUNET_OK if the signature(s) is valid, #GNUNET_SYSERR if not */ static int -verify_deposit_signature_forbidden ( +verify_deposit_signature_conflict ( const struct TALER_EXCHANGE_DepositHandle *dh, const json_t *json) { json_t *history; struct TALER_Amount total; + enum TALER_ErrorCode ec; + struct GNUNET_HashCode h_denom_pub; + memset (&h_denom_pub, + 0, + sizeof (h_denom_pub)); history = json_object_get (json, "history"); if (GNUNET_OK != @@ -260,30 +265,46 @@ verify_deposit_signature_forbidden ( dh->dki.value.currency, &dh->depconf.coin_pub, history, + &h_denom_pub, &total)) { GNUNET_break_op (0); return GNUNET_SYSERR; } - if (0 > - TALER_amount_add (&total, - &total, - &dh->amount_with_fee)) + ec = TALER_JSON_get_error_code (json); + switch (ec) { - /* clearly not OK if our transaction would have caused - the overflow... */ - return GNUNET_OK; - } + case TALER_EC_DEPOSIT_INSUFFICIENT_FUNDS: + if (0 > + TALER_amount_add (&total, + &total, + &dh->amount_with_fee)) + { + /* clearly not OK if our transaction would have caused + the overflow... */ + return GNUNET_OK; + } - if (0 >= TALER_amount_cmp (&total, - &dh->dki.value)) - { - /* transaction should have still fit */ - GNUNET_break (0); + if (0 >= TALER_amount_cmp (&total, + &dh->dki.value)) + { + /* transaction should have still fit */ + GNUNET_break (0); + return GNUNET_SYSERR; + } + /* everything OK, proof of double-spending was provided */ + return GNUNET_OK; + case TALER_EC_COIN_CONFLICTING_DENOMINATION_KEY: + if (0 != GNUNET_memcmp (&dh->dki.h_key, + &h_denom_pub)) + return GNUNET_OK; /* indeed, proof with different denomination key provided */ + /* invalid proof provided */ + return GNUNET_SYSERR; + default: + /* unexpected error code */ + GNUNET_break_op (0); return GNUNET_SYSERR; } - /* everything OK, proof of double-spending was provided */ - return GNUNET_OK; } @@ -343,8 +364,8 @@ handle_deposit_finished (void *cls, case MHD_HTTP_CONFLICT: /* Double spending; check signatures on transaction history */ if (GNUNET_OK != - verify_deposit_signature_forbidden (dh, - j)) + verify_deposit_signature_conflict (dh, + j)) { GNUNET_break_op (0); hr.http_status = 0; diff --git a/src/lib/exchange_api_melt.c b/src/lib/exchange_api_melt.c index 1ef83bf73..2c1e85d7b 100644 --- a/src/lib/exchange_api_melt.c +++ b/src/lib/exchange_api_melt.c @@ -178,6 +178,8 @@ verify_melt_signature_conflict (struct TALER_EXCHANGE_MeltHandle *mh, GNUNET_JSON_spec_end () }; const struct MeltedCoin *mc; + enum TALER_ErrorCode ec; + struct GNUNET_HashCode h_denom_pub; /* parse JSON reply */ if (GNUNET_OK != @@ -211,6 +213,9 @@ verify_melt_signature_conflict (struct TALER_EXCHANGE_MeltHandle *mh, } /* verify coin history */ + memset (&h_denom_pub, + 0, + sizeof (h_denom_pub)); history = json_object_get (json, "history"); if (GNUNET_OK != @@ -218,6 +223,7 @@ verify_melt_signature_conflict (struct TALER_EXCHANGE_MeltHandle *mh, original_value.currency, &coin_pub, history, + &h_denom_pub, &total)) { GNUNET_break_op (0); @@ -226,27 +232,43 @@ verify_melt_signature_conflict (struct TALER_EXCHANGE_MeltHandle *mh, } json_decref (history); - /* check if melt operation was really too expensive given history */ - if (0 > - TALER_amount_add (&total, - &total, - &melt_value_with_fee)) + ec = TALER_JSON_get_error_code (json); + switch (ec) { - /* clearly not OK if our transaction would have caused - the overflow... */ - return GNUNET_OK; - } + case TALER_EC_MELT_INSUFFICIENT_FUNDS: + /* check if melt operation was really too expensive given history */ + if (0 > + TALER_amount_add (&total, + &total, + &melt_value_with_fee)) + { + /* clearly not OK if our transaction would have caused + the overflow... */ + return GNUNET_OK; + } - if (0 >= TALER_amount_cmp (&total, - &original_value)) - { - /* transaction should have still fit */ - GNUNET_break (0); + if (0 >= TALER_amount_cmp (&total, + &original_value)) + { + /* transaction should have still fit */ + GNUNET_break (0); + return GNUNET_SYSERR; + } + + /* everything OK, valid proof of double-spending was provided */ + return GNUNET_OK; + case TALER_EC_COIN_CONFLICTING_DENOMINATION_KEY: + if (0 != GNUNET_memcmp (&mh->dki.h_key, + &h_denom_pub)) + return GNUNET_OK; /* indeed, proof with different denomination key provided */ + /* invalid proof provided */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + default: + /* unexpected error code */ + GNUNET_break_op (0); return GNUNET_SYSERR; } - - /* everything OK, valid proof of double-spending was provided */ - return GNUNET_OK; } diff --git a/src/lib/exchange_api_recoup.c b/src/lib/exchange_api_recoup.c index 723b41789..d8c59827c 100644 --- a/src/lib/exchange_api_recoup.c +++ b/src/lib/exchange_api_recoup.c @@ -187,7 +187,9 @@ handle_recoup_finished (void *cls, /* Insufficient funds, proof attached */ json_t *history; struct TALER_Amount total; + struct GNUNET_HashCode h_denom_pub; const struct TALER_EXCHANGE_DenomPublicKey *dki; + enum TALER_ErrorCode ec; dki = &ph->pk; history = json_object_get (j, @@ -197,6 +199,7 @@ handle_recoup_finished (void *cls, dki->fee_deposit.currency, &ph->coin_pub, history, + &h_denom_pub, &total)) { GNUNET_break_op (0); @@ -208,6 +211,43 @@ handle_recoup_finished (void *cls, hr.ec = TALER_JSON_get_error_code (j); hr.hint = TALER_JSON_get_error_hint (j); } + ec = TALER_JSON_get_error_code (j); + switch (ec) + { + case TALER_EC_RECOUP_COIN_BALANCE_ZERO: + if (0 > TALER_amount_cmp (&total, + &dki->value)) + { + /* recoup MAY have still been possible */ + /* FIXME: This code may falsely complain, as we do not + know that the smallest denomination offered by the + exchange is here. We should look at the key + structure of ph->exchange, and find the smallest + _currently withdrawable_ denomination and check + if the value remaining would suffice... */GNUNET_break_op (0); + hr.http_status = 0; + hr.ec = TALER_EC_RECOUP_REPLY_MALFORMED; + break; + } + break; + case TALER_EC_COIN_CONFLICTING_DENOMINATION_KEY: + if (0 == GNUNET_memcmp (&ph->pk.h_key, + &h_denom_pub)) + { + /* invalid proof provided */ + GNUNET_break_op (0); + hr.http_status = 0; + hr.ec = TALER_EC_RECOUP_REPLY_MALFORMED; + break; + } + /* valid error from exchange */ + break; + default: + GNUNET_break_op (0); + hr.http_status = 0; + hr.ec = TALER_EC_RECOUP_REPLY_MALFORMED; + break; + } ph->cb (ph->cb_cls, &hr, NULL, diff --git a/src/testing/testing_api_cmd_insert_deposit.c b/src/testing/testing_api_cmd_insert_deposit.c index cff2884aa..06f2dbe0e 100644 --- a/src/testing/testing_api_cmd_insert_deposit.c +++ b/src/testing/testing_api_cmd_insert_deposit.c @@ -175,7 +175,9 @@ insert_deposit_run (void *cls, GNUNET_CRYPTO_rsa_public_key_hash (dpk.rsa_public_key, &deposit.coin.denom_pub_hash); - + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + &deposit.coin.coin_pub, + sizeof (deposit.coin.coin_pub)); GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_WEAK, &hc); deposit.coin.denom_sig.rsa_signature = GNUNET_CRYPTO_rsa_sign_fdh (denom_priv, @@ -198,10 +200,9 @@ insert_deposit_run (void *cls, deposit.receiver_wire_account, &deposit.h_wire)); deposit.timestamp = GNUNET_TIME_absolute_get (); - GNUNET_TIME_round_abs (&deposit.timestamp); - deposit.wire_deadline = GNUNET_TIME_relative_to_absolute ( - ids->wire_deadline); - GNUNET_TIME_round_abs (&deposit.wire_deadline); + (void) GNUNET_TIME_round_abs (&deposit.timestamp); + deposit.wire_deadline = GNUNET_TIME_relative_to_absolute (ids->wire_deadline); + (void) GNUNET_TIME_round_abs (&deposit.wire_deadline); /* finally, actually perform the DB operation */ if ( (GNUNET_OK != @@ -222,6 +223,8 @@ insert_deposit_run (void *cls, ids->dbc->session)) ) { GNUNET_break (0); + ids->dbc->plugin->rollback (ids->dbc->plugin->cls, + ids->dbc->session); TALER_TESTING_interpreter_fail (is); } -- cgit v1.2.3