diff options
Diffstat (limited to 'src/lib/merchant_api_post_order_pay.c')
-rw-r--r-- | src/lib/merchant_api_post_order_pay.c | 285 |
1 files changed, 96 insertions, 189 deletions
diff --git a/src/lib/merchant_api_post_order_pay.c b/src/lib/merchant_api_post_order_pay.c index c246a1d4..57c85565 100644 --- a/src/lib/merchant_api_post_order_pay.c +++ b/src/lib/merchant_api_post_order_pay.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2021 Taler Systems SA + Copyright (C) 2014-2023 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as @@ -30,6 +30,7 @@ #include <gnunet/gnunet_util_lib.h> #include <gnunet/gnunet_curl_lib.h> #include "taler_merchant_service.h" +#include "merchant_api_common.h" #include "merchant_api_curl_defaults.h" #include <taler/taler_json_lib.h> #include <taler/taler_signatures.h> @@ -91,6 +92,23 @@ struct TALER_MERCHANT_OrderPayHandle struct TALER_MerchantPublicKeyP merchant_pub; /** + * JSON with the full reply, used during async + * processing. + */ + json_t *full_reply; + + /** + * Pointer into @e coins array for the coin that + * created a conflict (that we are checking). + */ + const struct TALER_MERCHANT_PaidCoin *error_pc; + + /** + * Coin history that proves a conflict. + */ + json_t *error_history; + + /** * Number of @e coins we are paying with. */ unsigned int num_coins; @@ -105,132 +123,6 @@ struct TALER_MERCHANT_OrderPayHandle /** - * We got a 409 response back from the exchange (or the merchant). - * Now we need to check the provided cryptograophic proof that the - * coin was actually already spent! - * - * @param pc handle of the original coin we paid with - * @param json cryptograophic proof of coin's transaction - * history as was returned by the exchange/merchant - * @return #GNUNET_OK if proof checks out - */ -static int -check_coin_history (const struct TALER_MERCHANT_PaidCoin *pc, - json_t *json) -{ - struct TALER_Amount spent; - struct TALER_Amount spent_plus_contrib; - struct TALER_DenominationHashP h_denom_pub; - struct TALER_DenominationHashP h_denom_pub_pc; - - if (GNUNET_OK != - TALER_EXCHANGE_verify_coin_history (NULL, /* do not verify fees */ - pc->amount_with_fee.currency, - &pc->coin_pub, - json, - &h_denom_pub, - &spent)) - { - /* Exchange's history fails to verify */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (0 > - TALER_amount_add (&spent_plus_contrib, - &spent, - &pc->amount_with_fee)) - { - /* We got an integer overflow? Bad application! */ - GNUNET_break (0); - return GNUNET_SYSERR; - } - TALER_denom_pub_hash (&pc->denom_pub, - &h_denom_pub_pc); - if ( (-1 != TALER_amount_cmp (&pc->denom_value, - &spent_plus_contrib)) && - (0 != GNUNET_memcmp (&h_denom_pub, - &h_denom_pub_pc)) ) - { - /* according to our calculations, the transaction should - have still worked, AND we did not get any proof of - coin public key re-use; hence: exchange error! */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Accepting proof of double-spending (or coin public key re-use)\n"); - return GNUNET_OK; -} - - -/** - * We got a 409 response back from the exchange (or the merchant). - * Now we need to check the provided cryptograophic proof that the - * coin was actually already spent! - * - * @param oph handle of the original pay operation - * @param json cryptograophic proof returned by the - * exchange/merchant - * @return #GNUNET_OK if proof checks out - */ -static enum GNUNET_GenericReturnValue -check_conflict (struct TALER_MERCHANT_OrderPayHandle *oph, - const json_t *json) -{ - json_t *history; - json_t *ereply; - struct TALER_CoinSpendPublicKeyP coin_pub; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_json ("exchange_reply", &ereply), - GNUNET_JSON_spec_fixed_auto ("coin_pub", &coin_pub), - GNUNET_JSON_spec_end () - }; - struct GNUNET_JSON_Specification hspec[] = { - GNUNET_JSON_spec_json ("history", &history), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - GNUNET_JSON_parse (ereply, - hspec, - NULL, NULL)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; - } - GNUNET_JSON_parse_free (spec); - - for (unsigned int i = 0; i<oph->num_coins; i++) - { - if (0 == memcmp (&oph->coins[i].coin_pub, - &coin_pub, - sizeof (struct TALER_CoinSpendPublicKeyP))) - { - int ret; - - ret = check_coin_history (&oph->coins[i], - history); - GNUNET_JSON_parse_free (hspec); - return ret; - } - } - GNUNET_break_op (0); /* complaint is not about any of the coins - that we actually paid with... */ - GNUNET_JSON_parse_free (hspec); - return GNUNET_SYSERR; -} - - -/** * Function called when we're done processing the * HTTP /pay request. * @@ -245,12 +137,10 @@ handle_pay_finished (void *cls, { struct TALER_MERCHANT_OrderPayHandle *oph = cls; const json_t *json = response; - struct TALER_MERCHANT_HttpResponse hr = { - .http_status = (unsigned int) response_code, - .reply = json + struct TALER_MERCHANT_PayResponse pr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json }; - struct TALER_MerchantSignatureP *merchant_sigp = NULL; - struct TALER_MerchantSignatureP merchant_sig; oph->job = NULL; GNUNET_log (GNUNET_ERROR_TYPE_INFO, @@ -259,15 +149,21 @@ handle_pay_finished (void *cls, switch (response_code) { case 0: - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; case MHD_HTTP_OK: if (oph->am_wallet) { /* Here we can (and should) verify the merchant's signature */ struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("sig", - &merchant_sig), + GNUNET_JSON_spec_fixed_auto ( + "sig", + &pr.details.ok.merchant_sig), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ( + "pos_confirmation", + &pr.details.ok.pos_confirmation), + NULL), GNUNET_JSON_spec_end () }; @@ -277,94 +173,90 @@ handle_pay_finished (void *cls, NULL, NULL)) { GNUNET_break_op (0); - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - hr.http_status = 0; - hr.hint = "sig field missing in response"; + pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + pr.hr.http_status = 0; + pr.hr.hint = "sig field missing in response"; break; } if (GNUNET_OK != TALER_merchant_pay_verify (&oph->h_contract_terms, &oph->merchant_pub, - &merchant_sig)) + &pr.details.ok.merchant_sig)) { GNUNET_break_op (0); - hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - hr.http_status = 0; - hr.hint = "signature invalid"; + pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + pr.hr.http_status = 0; + pr.hr.hint = "signature invalid"; } - merchant_sigp = &merchant_sig; } break; /* Tolerating Not Acceptable because sometimes * - especially in tests - we might want to POST * coins one at a time. */ case MHD_HTTP_NOT_ACCEPTABLE: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + pr.hr.ec = TALER_JSON_get_error_code (json); + pr.hr.hint = TALER_JSON_get_error_hint (json); break; case MHD_HTTP_BAD_REQUEST: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + pr.hr.ec = TALER_JSON_get_error_code (json); + pr.hr.hint = TALER_JSON_get_error_hint (json); /* This should never happen, either us * or the merchant is buggy (or API version conflict); * just pass JSON reply to the application */ break; case MHD_HTTP_PAYMENT_REQUIRED: /* was originally paid, but then refunded */ - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + pr.hr.ec = TALER_JSON_get_error_code (json); + pr.hr.hint = TALER_JSON_get_error_hint (json); break; case MHD_HTTP_FORBIDDEN: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + pr.hr.ec = TALER_JSON_get_error_code (json); + pr.hr.hint = TALER_JSON_get_error_hint (json); /* Nothing really to verify, merchant says we tried to abort the payment * after it was successful. We should pass the JSON reply to the * application */ break; case MHD_HTTP_NOT_FOUND: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + pr.hr.ec = TALER_JSON_get_error_code (json); + pr.hr.hint = TALER_JSON_get_error_hint (json); /* Nothing really to verify, this should never happen, we should pass the JSON reply to the application */ break; - case MHD_HTTP_PRECONDITION_FAILED: - TALER_MERCHANT_parse_error_details_ (json, - response_code, - &hr); - /* Nothing really to verify, the merchant is blaming us for failing to - satisfy some constraint (likely it does not like our exchange because - of some disagreement on the PKI). We should pass the JSON reply to the - application */ - break; case MHD_HTTP_REQUEST_TIMEOUT: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + pr.hr.ec = TALER_JSON_get_error_code (json); + pr.hr.hint = TALER_JSON_get_error_hint (json); /* The merchant couldn't generate a timely response, likely because it itself waited too long on the exchange. Pass on to application. */ break; case MHD_HTTP_CONFLICT: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); - if (GNUNET_OK != check_conflict (oph, - json)) - { - GNUNET_break_op (0); - response_code = 0; - } + TALER_MERCHANT_parse_error_details_ (json, + MHD_HTTP_CONFLICT, + &pr.hr); break; case MHD_HTTP_GONE: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + TALER_MERCHANT_parse_error_details_ (json, + response_code, + &pr.hr); /* The merchant says we are too late, the offer has expired or some denomination key of a coin involved has expired. Might be a disagreement in timestamps? Still, pass on to application. */ break; + case MHD_HTTP_PRECONDITION_FAILED: + TALER_MERCHANT_parse_error_details_ (json, + response_code, + &pr.hr); + /* Nothing really to verify, the merchant is blaming us for failing to + satisfy some constraint (likely it does not like our exchange because + of some disagreement on the PKI). We should pass the JSON reply to the + application */ + break; case MHD_HTTP_INTERNAL_SERVER_ERROR: - hr.ec = TALER_JSON_get_error_code (json); - hr.hint = TALER_JSON_get_error_hint (json); + TALER_MERCHANT_parse_error_details_ (json, + response_code, + &pr.hr); /* Server had an internal issue; we should retry, but this API leaves this to the application */ break; @@ -373,37 +265,36 @@ handle_pay_finished (void *cls, We should pass the JSON reply to the application */ TALER_MERCHANT_parse_error_details_ (json, response_code, - &hr); + &pr.hr); break; case MHD_HTTP_SERVICE_UNAVAILABLE: TALER_MERCHANT_parse_error_details_ (json, response_code, - &hr); + &pr.hr); /* Exchange couldn't respond properly; the retry is left to the application */ break; case MHD_HTTP_GATEWAY_TIMEOUT: TALER_MERCHANT_parse_error_details_ (json, response_code, - &hr); + &pr.hr); /* Exchange couldn't respond in a timely fashion; the retry is left to the application */ break; default: TALER_MERCHANT_parse_error_details_ (json, response_code, - &hr); + &pr.hr); /* unexpected response code */ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u/%d\n", (unsigned int) response_code, - (int) hr.ec); + (int) pr.hr.ec); GNUNET_break_op (0); break; } oph->pay_cb (oph->pay_cb_cls, - &hr, - merchant_sigp); + &pr); TALER_MERCHANT_order_pay_cancel (oph); } @@ -414,8 +305,9 @@ TALER_MERCHANT_order_pay_frontend ( const char *merchant_url, const char *order_id, const char *session_id, + const json_t *wallet_data, unsigned int num_coins, - const struct TALER_MERCHANT_PaidCoin coins[], + const struct TALER_MERCHANT_PaidCoin coins[static num_coins], TALER_MERCHANT_OrderPayCallback pay_cb, void *pay_cb_cls) { @@ -432,6 +324,7 @@ TALER_MERCHANT_order_pay_frontend ( return NULL; } j_coins = json_array (); + GNUNET_assert (NULL != j_coins); for (unsigned int i = 0; i<num_coins; i++) { json_t *j_coin; @@ -503,6 +396,9 @@ TALER_MERCHANT_order_pay_frontend ( GNUNET_JSON_pack_array_steal ("coins", j_coins), GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_incref ("wallet_data", + (json_t *) wallet_data)), + GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("session_id", session_id))); @@ -532,9 +428,9 @@ TALER_MERCHANT_order_pay_frontend ( oph->num_coins = num_coins; oph->coins = GNUNET_new_array (num_coins, struct TALER_MERCHANT_PaidCoin); - memcpy (oph->coins, - coins, - num_coins * sizeof (struct TALER_MERCHANT_PaidCoin)); + GNUNET_memcpy (oph->coins, + coins, + num_coins * sizeof (struct TALER_MERCHANT_PaidCoin)); eh = TALER_MERCHANT_curl_easy_get_ (oph->url); if (GNUNET_OK != @@ -565,6 +461,7 @@ TALER_MERCHANT_order_pay ( const char *merchant_url, const char *session_id, const struct TALER_PrivateContractHashP *h_contract_terms, + const json_t *wallet_data, const struct TALER_Amount *amount, const struct TALER_Amount *max_fee, const struct TALER_MerchantPublicKeyP *merchant_pub, @@ -575,10 +472,12 @@ TALER_MERCHANT_order_pay ( const struct TALER_MerchantWireHashP *h_wire, const char *order_id, unsigned int num_coins, - const struct TALER_MERCHANT_PayCoin coins[], + const struct TALER_MERCHANT_PayCoin coins[static num_coins], TALER_MERCHANT_OrderPayCallback pay_cb, void *pay_cb_cls) { + struct GNUNET_HashCode wallet_data_hash; + if (GNUNET_YES != TALER_amount_cmp_currency (amount, max_fee)) @@ -586,7 +485,9 @@ TALER_MERCHANT_order_pay ( GNUNET_break (0); return NULL; } - + if (NULL != wallet_data) + TALER_json_hash (wallet_data, + &wallet_data_hash); { struct TALER_MERCHANT_PaidCoin pc[num_coins]; @@ -613,6 +514,9 @@ TALER_MERCHANT_order_pay ( &fee, h_wire, h_contract_terms, + (NULL != wallet_data) + ? &wallet_data_hash + : NULL, coin->h_age_commitment, NULL /* h_extensions! */, &h_denom_pub, @@ -637,6 +541,7 @@ TALER_MERCHANT_order_pay ( merchant_url, order_id, session_id, + wallet_data, num_coins, pc, pay_cb, @@ -661,6 +566,8 @@ TALER_MERCHANT_order_pay_cancel (struct TALER_MERCHANT_OrderPayHandle *oph) oph->job = NULL; } TALER_curl_easy_post_finished (&oph->post_ctx); + json_decref (oph->error_history); + json_decref (oph->full_reply); GNUNET_free (oph->coins); GNUNET_free (oph->url); GNUNET_free (oph); |