exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

commit d6507a9a86198a4b2f1f884d6fd630aa4a3b921b
parent ca846b09671c742b2f8275bbb9f1a975efdebc0f
Author: Christian Grothoff <grothoff@gnu.org>
Date:   Sun, 22 Feb 2026 21:46:14 +0100

rename src/lib/ files to match exchange_api_$METHOD-$ENDPOINT convention

Rename all exchange API source files in src/lib/ to follow the naming
convention established by the headers in src/include/taler/taler-exchange/,
using the pattern exchange_api_$METHOD-$ENDPOINT.c. Update @file comments
and Makefile.am accordingly.

Diffstat:
Msrc/lib/Makefile.am | 102++++++++++++++++++++++++++++++++++++++++----------------------------------------
Dsrc/lib/exchange_api_add_aml_decision.c | 350-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_aml_legitimizations_get.c | 510-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_auditor_add_denomination.c | 239-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_batch_deposit.c | 820-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_blinding_prepare.c | 422-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_coins_history.c | 1235-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_contracts_get.c | 262-------------------------------------------------------------------------------
Asrc/lib/exchange_api_delete-purses-PURSE_PUB.c | 243+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/lib/exchange_api_deposits_get.c | 411-------------------------------------------------------------------------------
Asrc/lib/exchange_api_get-aml-OFFICER_PUB-attributes-H_NORMALIZED_PAYTO.c | 386+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_get-aml-OFFICER_PUB-decisions.c | 626+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_get-aml-OFFICER_PUB-kyc-statistics-NAMES.c | 317+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_get-aml-OFFICER_PUB-legitimizations.c | 510+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_get-aml-OFFICER_PUB-measures.c | 650+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_get-coins-COIN_PUB-history.c | 1235+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_get-contracts-CONTRACT_PUB.c | 262+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_get-deposits-H_WIRE-MERCHANT_PUB-H_CONTRACT_TERMS-COIN_PUB.c | 411+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_get-kyc-check-H_NORMALIZED_PAYTO.c | 415+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_get-kyc-info-ACCESS_TOKEN.c | 392+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_get-kyc-proof-PROVIDER_NAME.c | 217+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_get-management-keys.c | 427+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_get-purses-PURSE_PUB-merge.c | 300+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_get-reserves-RESERVE_PUB-history.c | 1258+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_get-reserves-RESERVE_PUB.c | 279+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_get-reserves-attest-RESERVE_PUB.c | 276+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_get-transfers-WTID.c | 400+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/lib/exchange_api_get_aml_measures.c | 650-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_get_kyc_statistics.c | 317-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_kyc_check.c | 415-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_kyc_info.c | 392-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_kyc_proof.c | 217-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_kyc_start.c | 220-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_kyc_wallet.c | 257-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_lookup_aml_decisions.c | 626-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_lookup_kyc_attributes.c | 386-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_management_add_partner.c | 219-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_management_auditor_disable.c | 220-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_management_auditor_enable.c | 227-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_management_drain_profits.c | 214-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_management_get_keys.c | 427-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_management_post_extensions.c | 213-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_management_post_keys.c | 238-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_management_revoke_denomination_key.c | 223-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_management_revoke_signing_key.c | 213-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_management_set_global_fee.c | 237-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_management_set_wire_fee.c | 229-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_management_wire_disable.c | 222-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_management_wire_enable.c | 254-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_melt.c | 645-------------------------------------------------------------------------------
Asrc/lib/exchange_api_post-aml-OFFICER_PUB-decision.c | 350+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_post-auditors-AUDITOR_PUB-H_DENOM_PUB.c | 239+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_post-batch-deposit.c | 820+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_post-blinding-prepare.c | 422+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_post-coins-COIN_PUB-refund.c | 484+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_post-kyc-start-ID.c | 220+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_post-kyc-wallet.c | 257+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_post-management-auditors-AUDITOR_PUB-disable.c | 220+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_post-management-auditors.c | 227+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_post-management-denominations-H_DENOM_PUB-revoke.c | 223+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_post-management-drain.c | 214+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_post-management-extensions.c | 213+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_post-management-global-fees.c | 237+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_post-management-keys.c | 238+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_post-management-partners.c | 219+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_post-management-signkeys-EXCHANGE_PUB-revoke.c | 213+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_post-management-wire-disable.c | 222+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_post-management-wire-fee.c | 229+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_post-management-wire.c | 254+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_post-melt.c | 645+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_post-purses-PURSE_PUB-create.c | 656+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_post-purses-PURSE_PUB-deposit.c | 520+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_post-purses-PURSE_PUB-merge.c | 454+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_post-recoup-refresh.c | 361+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_post-recoup-withdraw.c | 382+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_post-reserves-RESERVE_PUB-close.c | 373+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_post-reserves-RESERVE_PUB-open.c | 567+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_post-reserves-RESERVE_PUB-purse.c | 580+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_post-reserves-attest-RESERVE_PUB.c | 365+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_post-reveal-melt.c | 416+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_post-reveal-withdraw.c | 366+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/exchange_api_post-withdraw.c | 1928+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/lib/exchange_api_purse_create_with_deposit.c | 656-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_purse_create_with_merge.c | 580-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_purse_delete.c | 243-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_purse_deposit.c | 520-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_purse_merge.c | 454-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_purses_get.c | 300-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_recoup.c | 382-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_recoup_refresh.c | 361-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_refund.c | 484-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_reserves_attest.c | 365-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_reserves_close.c | 373-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_reserves_get.c | 279-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_reserves_get_attestable.c | 276-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_reserves_history.c | 1258-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_reserves_open.c | 567-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_reveal_melt.c | 416-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_reveal_withdraw.c | 366-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_transfers_get.c | 400-------------------------------------------------------------------------------
Dsrc/lib/exchange_api_withdraw.c | 1928-------------------------------------------------------------------------------
101 files changed, 21769 insertions(+), 21769 deletions(-)

diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am @@ -21,63 +21,63 @@ libtalerexchange_la_LDFLAGS = \ -version-info 18:0:0 \ -no-undefined libtalerexchange_la_SOURCES = \ - exchange_api_add_aml_decision.c \ - exchange_api_aml_legitimizations_get.c \ - exchange_api_auditor_add_denomination.c \ - exchange_api_batch_deposit.c \ - exchange_api_blinding_prepare.c \ - exchange_api_coins_history.c \ + exchange_api_delete-purses-PURSE_PUB.c \ + exchange_api_get-aml-OFFICER_PUB-attributes-H_NORMALIZED_PAYTO.c \ + exchange_api_get-aml-OFFICER_PUB-decisions.c \ + exchange_api_get-aml-OFFICER_PUB-kyc-statistics-NAMES.c \ + exchange_api_get-aml-OFFICER_PUB-legitimizations.c \ + exchange_api_get-aml-OFFICER_PUB-measures.c \ + exchange_api_get-coins-COIN_PUB-history.c \ + exchange_api_get-contracts-CONTRACT_PUB.c \ + exchange_api_get-deposits-H_WIRE-MERCHANT_PUB-H_CONTRACT_TERMS-COIN_PUB.c \ + exchange_api_get-kyc-check-H_NORMALIZED_PAYTO.c \ + exchange_api_get-kyc-info-ACCESS_TOKEN.c \ + exchange_api_get-kyc-proof-PROVIDER_NAME.c \ + exchange_api_get-management-keys.c \ + exchange_api_get-purses-PURSE_PUB-merge.c \ + exchange_api_get-reserves-attest-RESERVE_PUB.c \ + exchange_api_get-reserves-RESERVE_PUB.c \ + exchange_api_get-reserves-RESERVE_PUB-history.c \ + exchange_api_get-transfers-WTID.c \ exchange_api_common.c exchange_api_common.h \ - exchange_api_contracts_get.c \ exchange_api_curl_defaults.c exchange_api_curl_defaults.h \ - exchange_api_deposits_get.c \ - exchange_api_get_aml_measures.c \ - exchange_api_get_kyc_statistics.c \ exchange_api_handle.c exchange_api_handle.h \ - exchange_api_kyc_check.c \ - exchange_api_kyc_info.c \ - exchange_api_kyc_proof.c \ - exchange_api_kyc_start.c \ - exchange_api_kyc_wallet.c \ - exchange_api_lookup_aml_decisions.c \ - exchange_api_lookup_kyc_attributes.c \ - exchange_api_management_add_partner.c \ - exchange_api_management_auditor_disable.c \ - exchange_api_management_auditor_enable.c \ - exchange_api_management_drain_profits.c \ - exchange_api_management_get_keys.c \ - exchange_api_management_post_extensions.c \ - exchange_api_management_post_keys.c \ - exchange_api_management_revoke_denomination_key.c \ - exchange_api_management_revoke_signing_key.c \ - exchange_api_management_set_global_fee.c \ - exchange_api_management_set_wire_fee.c \ + exchange_api_post-aml-OFFICER_PUB-decision.c \ + exchange_api_post-auditors-AUDITOR_PUB-H_DENOM_PUB.c \ + exchange_api_post-batch-deposit.c \ + exchange_api_post-blinding-prepare.c \ + exchange_api_post-coins-COIN_PUB-refund.c \ + exchange_api_post-kyc-start-ID.c \ + exchange_api_post-kyc-wallet.c \ exchange_api_post-management-aml-officers.c \ - exchange_api_management_wire_disable.c \ - exchange_api_management_wire_enable.c \ - exchange_api_melt.c \ - exchange_api_purse_create_with_deposit.c \ - exchange_api_purse_create_with_merge.c \ - exchange_api_purse_delete.c \ - exchange_api_purse_deposit.c \ - exchange_api_purse_merge.c \ - exchange_api_purses_get.c \ - exchange_api_recoup.c \ - exchange_api_recoup_refresh.c \ + exchange_api_post-management-auditors-AUDITOR_PUB-disable.c \ + exchange_api_post-management-auditors.c \ + exchange_api_post-management-denominations-H_DENOM_PUB-revoke.c \ + exchange_api_post-management-drain.c \ + exchange_api_post-management-extensions.c \ + exchange_api_post-management-global-fees.c \ + exchange_api_post-management-keys.c \ + exchange_api_post-management-partners.c \ + exchange_api_post-management-signkeys-EXCHANGE_PUB-revoke.c \ + exchange_api_post-management-wire-disable.c \ + exchange_api_post-management-wire-fee.c \ + exchange_api_post-management-wire.c \ + exchange_api_post-melt.c \ + exchange_api_post-purses-PURSE_PUB-create.c \ + exchange_api_post-purses-PURSE_PUB-deposit.c \ + exchange_api_post-purses-PURSE_PUB-merge.c \ + exchange_api_post-recoup-refresh.c \ + exchange_api_post-recoup-withdraw.c \ + exchange_api_post-reserves-attest-RESERVE_PUB.c \ + exchange_api_post-reserves-RESERVE_PUB-close.c \ + exchange_api_post-reserves-RESERVE_PUB-open.c \ + exchange_api_post-reserves-RESERVE_PUB-purse.c \ + exchange_api_post-reveal-melt.c \ + exchange_api_post-reveal-withdraw.c \ + exchange_api_post-withdraw.c \ exchange_api_refresh_common.c exchange_api_refresh_common.h \ - exchange_api_refund.c \ - exchange_api_reserves_attest.c \ - exchange_api_reserves_close.c \ - exchange_api_reserves_get.c \ - exchange_api_reserves_get_attestable.c \ - exchange_api_reserves_history.c \ - exchange_api_reserves_open.c \ exchange_api_restrictions.c \ - exchange_api_reveal_withdraw.c \ - exchange_api_reveal_melt.c \ - exchange_api_stefan.c \ - exchange_api_transfers_get.c \ - exchange_api_withdraw.c + exchange_api_stefan.c libtalerexchange_la_LIBADD = \ libtalerauditor.la \ diff --git a/src/lib/exchange_api_add_aml_decision.c b/src/lib/exchange_api_add_aml_decision.c @@ -1,350 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2023, 2024 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_add_aml_decision.c - * @brief functions to add an AML decision by an AML officer - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include "taler/taler_json_lib.h" -#include <microhttpd.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_exchange_service.h" -#include "exchange_api_curl_defaults.h" -#include "taler/taler_signatures.h" -#include "taler/taler_curl_lib.h" -#include "taler/taler_json_lib.h" - - -struct TALER_EXCHANGE_AddAmlDecision -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Minor context that holds body and headers. - */ - struct TALER_CURL_PostContext post_ctx; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_AddAmlDecisionCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Reference to the execution context. - */ - struct GNUNET_CURL_Context *ctx; -}; - - -/** - * Function called when we're done processing the - * HTTP POST /aml/$OFFICER_PUB/decision request. - * - * @param cls the `struct TALER_EXCHANGE_AddAmlDecision *` - * @param response_code HTTP response code, 0 on error - * @param response response body, NULL if not in JSON - */ -static void -handle_add_aml_decision_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_AddAmlDecision *wh = cls; - const json_t *json = response; - struct TALER_EXCHANGE_AddAmlDecisionResponse adr = { - .hr.http_status = (unsigned int) response_code, - .hr.reply = json - }; - - wh->job = NULL; - switch (response_code) - { - case 0: - /* no reply */ - adr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - adr.hr.hint = "server offline?"; - break; - case MHD_HTTP_NO_CONTENT: - break; - case MHD_HTTP_FORBIDDEN: - adr.hr.ec = TALER_JSON_get_error_code (json); - adr.hr.hint = TALER_JSON_get_error_hint (json); - break; - case MHD_HTTP_CONFLICT: - adr.hr.ec = TALER_JSON_get_error_code (json); - adr.hr.hint = TALER_JSON_get_error_hint (json); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - adr.hr.ec = TALER_JSON_get_error_code (json); - adr.hr.hint = TALER_JSON_get_error_hint (json); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange AML decision\n", - (unsigned int) response_code, - (int) adr.hr.ec); - break; - } - if (NULL != wh->cb) - { - wh->cb (wh->cb_cls, - &adr); - wh->cb = NULL; - } - TALER_EXCHANGE_post_aml_decision_cancel (wh); -} - - -struct TALER_EXCHANGE_AddAmlDecision * -TALER_EXCHANGE_post_aml_decision ( - struct GNUNET_CURL_Context *ctx, - const char *url, - const struct TALER_NormalizedPaytoHashP *h_payto, - const struct TALER_FullPayto payto_uri, - struct GNUNET_TIME_Timestamp decision_time, - const char *successor_measure, - const char *new_measures, - struct GNUNET_TIME_Timestamp expiration_time, - unsigned int num_rules, - const struct TALER_EXCHANGE_AccountRule *rules, - unsigned int num_measures, - const struct TALER_EXCHANGE_MeasureInformation *measures, - const json_t *properties, - bool keep_investigating, - const char *justification, - const struct TALER_AmlOfficerPrivateKeyP *officer_priv, - unsigned int num_events, - const char **events, - TALER_EXCHANGE_AddAmlDecisionCallback cb, - void *cb_cls) -{ - struct TALER_AmlOfficerPublicKeyP officer_pub; - struct TALER_AmlOfficerSignatureP officer_sig; - struct TALER_EXCHANGE_AddAmlDecision *wh; - CURL *eh; - json_t *body; - json_t *new_rules; - json_t *jrules; - json_t *jmeasures; - json_t *jevents = NULL; - - if (0 != num_events) - { - jevents = json_array (); - GNUNET_assert (NULL != jevents); - for (unsigned int i = 0; i<num_events; i++) - GNUNET_assert (0 == - json_array_append_new (jevents, - json_string (events[i]))); - } - jrules = json_array (); - GNUNET_assert (NULL != jrules); - for (unsigned int i = 0; i<num_rules; i++) - { - const struct TALER_EXCHANGE_AccountRule *al = &rules[i]; - json_t *rule; - json_t *ameasures; - - ameasures = json_array (); - GNUNET_assert (NULL != ameasures); - for (unsigned int j = 0; j<al->num_measures; j++) - GNUNET_assert (0 == - json_array_append_new (ameasures, - json_string (al->measures[j]))); - rule = GNUNET_JSON_PACK ( - TALER_JSON_pack_kycte ("operation_type", - al->operation_type), - TALER_JSON_pack_amount ("threshold", - &al->threshold), - GNUNET_JSON_pack_time_rel ("timeframe", - al->timeframe), - GNUNET_JSON_pack_array_steal ("measures", - ameasures), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_array_steal ("events", - jevents)), - GNUNET_JSON_pack_bool ("exposed", - al->exposed), - GNUNET_JSON_pack_bool ("is_and_combinator", - al->is_and_combinator), - GNUNET_JSON_pack_uint64 ("display_priority", - al->display_priority) - ); - GNUNET_break (0 == - json_array_append_new (jrules, - rule)); - } - - jmeasures = json_object (); - GNUNET_assert (NULL != jmeasures); - for (unsigned int i = 0; i<num_measures; i++) - { - const struct TALER_EXCHANGE_MeasureInformation *mi = &measures[i]; - json_t *measure; - - measure = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("check_name", - mi->check_name), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("prog_name", - mi->prog_name)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_object_incref ("context", - (json_t *) mi->context)) - ); - GNUNET_break (0 == - json_object_set_new (jmeasures, - mi->measure_name, - measure)); - } - - new_rules = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_timestamp ("expiration_time", - expiration_time), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("successor_measure", - successor_measure)), - GNUNET_JSON_pack_array_steal ("rules", - jrules), - GNUNET_JSON_pack_object_steal ("custom_measures", - jmeasures) - ); - - GNUNET_CRYPTO_eddsa_key_get_public ( - &officer_priv->eddsa_priv, - &officer_pub.eddsa_pub); - TALER_officer_aml_decision_sign (justification, - decision_time, - h_payto, - new_rules, - properties, - new_measures, - keep_investigating, - officer_priv, - &officer_sig); - wh = GNUNET_new (struct TALER_EXCHANGE_AddAmlDecision); - wh->cb = cb; - wh->cb_cls = cb_cls; - wh->ctx = ctx; - { - char *path; - char opus[sizeof (officer_pub) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string ( - &officer_pub, - sizeof (officer_pub), - opus, - sizeof (opus)); - *end = '\0'; - GNUNET_asprintf (&path, - "aml/%s/decision", - opus); - wh->url = TALER_url_join (url, - path, - NULL); - GNUNET_free (path); - } - if (NULL == wh->url) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Could not construct request URL.\n"); - GNUNET_free (wh); - json_decref (new_rules); - return NULL; - } - body = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("justification", - justification), - GNUNET_JSON_pack_data_auto ("h_payto", - h_payto), - GNUNET_JSON_pack_allow_null ( - TALER_JSON_pack_full_payto ("payto_uri", - payto_uri)), - GNUNET_JSON_pack_object_steal ("new_rules", - new_rules), - GNUNET_JSON_pack_object_incref ("properties", - (json_t *) properties), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("new_measures", - new_measures)), - GNUNET_JSON_pack_bool ("keep_investigating", - keep_investigating), - GNUNET_JSON_pack_data_auto ("officer_sig", - &officer_sig), - GNUNET_JSON_pack_timestamp ("decision_time", - decision_time)); - eh = TALER_EXCHANGE_curl_easy_get_ (wh->url); - if ( (NULL == eh) || - (GNUNET_OK != - TALER_curl_easy_post (&wh->post_ctx, - eh, - body)) ) - { - GNUNET_break (0); - if (NULL != eh) - curl_easy_cleanup (eh); - json_decref (body); - GNUNET_free (wh->url); - return NULL; - } - json_decref (body); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Requesting URL '%s'\n", - wh->url); - wh->job = GNUNET_CURL_job_add2 (ctx, - eh, - wh->post_ctx.headers, - &handle_add_aml_decision_finished, - wh); - if (NULL == wh->job) - { - TALER_EXCHANGE_post_aml_decision_cancel (wh); - return NULL; - } - return wh; -} - - -void -TALER_EXCHANGE_post_aml_decision_cancel ( - struct TALER_EXCHANGE_AddAmlDecision *wh) -{ - if (NULL != wh->job) - { - GNUNET_CURL_job_cancel (wh->job); - wh->job = NULL; - } - TALER_curl_easy_post_finished (&wh->post_ctx); - GNUNET_free (wh->url); - GNUNET_free (wh); -} diff --git a/src/lib/exchange_api_aml_legitimizations_get.c b/src/lib/exchange_api_aml_legitimizations_get.c @@ -1,510 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2025 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_aml_legitimizations_get.c - * @brief Implementation of the GET /aml/$OFFICER_PUB/legitimizations requests - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_json_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_exchange_service.h" -#include "taler/taler_json_lib.h" -#include "exchange_api_handle.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * Handle for an operation to GET /aml/$OFFICER_PUB/legitimizations. - */ -struct TALER_EXCHANGE_GetAmlLegitimizationsHandle -{ - - /** - * The exchange base URL for this request. - */ - char *exchange_base_url; - - /** - * Our execution context. - */ - struct GNUNET_CURL_Context *ctx; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Signature of the AML officer. - */ - struct TALER_AmlOfficerSignatureP officer_sig; - - /** - * Public key of the AML officer. - */ - struct TALER_AmlOfficerPublicKeyP officer_pub; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_GetAmlLegitimizationsCallback cb; - - /** - * Closure for @a cb. - */ - TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_RESULT_CLOSURE *cb_cls; - - /** - * The url for this request. - */ - char *url; - - /** - * HTTP headers for the job. - */ - struct curl_slist *job_headers; - - /** - * Request options. - */ - struct - { - /** - * Limit on number of results. - */ - int64_t limit; - - /** - * Row offset from which to return results. - */ - uint64_t offset; - - /** - * Hash of payto URI to filter by, NULL for no filter. - */ - const struct TALER_NormalizedPaytoHashP *h_payto; - - /** - * Activity filter. - */ - enum TALER_EXCHANGE_YesNoAll active; - - } options; - -}; - - -/** - * Parse a single measure details entry from JSON. - * - * @param md_json JSON object to parse - * @param[out] md where to store the result - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -parse_measure_details ( - const json_t *md_json, - struct TALER_EXCHANGE_GetAmlLegitimizationsMeasureDetails *md) -{ - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("h_payto", - &md->h_payto), - GNUNET_JSON_spec_uint64 ("rowid", - &md->rowid), - GNUNET_JSON_spec_timestamp ("start_time", - &md->start_time), - GNUNET_JSON_spec_object_const ("measures", - &md->measures), - GNUNET_JSON_spec_bool ("is_finished", - &md->is_finished), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (md_json, - spec, - NULL, - NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_OK; -} - - -/** - * We received an #MHD_HTTP_OK status code. Handle the JSON - * response. - * - * @param algh handle of the request - * @param j JSON response - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -handle_aml_legitimizations_get_ok ( - struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh, - const json_t *j) -{ - struct TALER_EXCHANGE_GetAmlLegitimizationsResponse result = { - .hr.reply = j, - .hr.http_status = MHD_HTTP_OK - }; - const json_t *measures_array; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_array_const ("measures", - &measures_array), - GNUNET_JSON_spec_end () - }; - struct TALER_EXCHANGE_GetAmlLegitimizationsMeasureDetails *measures; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, - NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - result.details.ok.measures_length = json_array_size (measures_array); - if (0 == result.details.ok.measures_length) - { - measures = NULL; - } - else - { - measures - = GNUNET_new_array ( - result.details.ok.measures_length, - struct TALER_EXCHANGE_GetAmlLegitimizationsMeasureDetails); - } - for (size_t i = 0; i < result.details.ok.measures_length; i++) - { - const json_t *measure_json = json_array_get (measures_array, - i); - - if (GNUNET_OK != - parse_measure_details (measure_json, - &measures[i])) - { - GNUNET_free (measures); - return GNUNET_SYSERR; - } - } - result.details.ok.measures = measures; - algh->cb (algh->cb_cls, - &result); - algh->cb = NULL; - GNUNET_free (measures); - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /aml/$OFFICER_PUB/legitimizations GET request. - * - * @param cls the `struct TALER_EXCHANGE_GetAmlLegitimizationsHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_aml_legitimizations_get_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh = cls; - const json_t *j = response; - struct TALER_EXCHANGE_GetAmlLegitimizationsResponse result = { - .hr.reply = j, - .hr.http_status = (unsigned int) response_code - }; - - algh->job = NULL; - switch (response_code) - { - case 0: - result.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - if (GNUNET_OK != - handle_aml_legitimizations_get_ok (algh, - j)) - { - result.hr.http_status = 0; - result.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - } - break; - case MHD_HTTP_NO_CONTENT: - /* can happen */ - break; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - result.hr.ec = TALER_JSON_get_error_code (j); - result.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_UNAUTHORIZED: - /* Invalid officer credentials */ - result.hr.ec = TALER_JSON_get_error_code (j); - result.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_FORBIDDEN: - /* Officer not authorized for this operation */ - result.hr.ec = TALER_JSON_get_error_code (j); - result.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_NOT_FOUND: - /* Officer not found */ - result.hr.ec = TALER_JSON_get_error_code (j); - result.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - result.hr.ec = TALER_JSON_get_error_code (j); - result.hr.hint = TALER_JSON_get_error_hint (j); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - result.hr.ec = TALER_JSON_get_error_code (j); - result.hr.hint = TALER_JSON_get_error_hint (j); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for GET %s\n", - (unsigned int) response_code, - (int) result.hr.ec, - algh->url); - break; - } - if (NULL != algh->cb) - { - algh->cb (algh->cb_cls, - &result); - algh->cb = NULL; - } - TALER_EXCHANGE_get_aml_legitimizations_cancel (algh); -} - - -struct TALER_EXCHANGE_GetAmlLegitimizationsHandle * -TALER_EXCHANGE_get_aml_legitimizations_create ( - struct GNUNET_CURL_Context *ctx, - const char *exchange_base_url, - const struct TALER_AmlOfficerPrivateKeyP *officer_priv) -{ - struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh; - - algh = GNUNET_new (struct TALER_EXCHANGE_GetAmlLegitimizationsHandle); - algh->ctx = ctx; - algh->exchange_base_url = GNUNET_strdup (exchange_base_url); - GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv, - &algh->officer_pub.eddsa_pub); - TALER_officer_aml_query_sign (officer_priv, - &algh->officer_sig); - algh->options.limit = -20; /* Default to last 20 entries */ - algh->options.offset = INT64_MAX; /* Default to maximum row id */ - algh->options.active = TALER_EXCHANGE_YNA_ALL; /* Default to all */ - return algh; -} - - -enum GNUNET_GenericReturnValue -TALER_EXCHANGE_get_aml_legitimizations_set_options_ ( - struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh, - unsigned int num_options, - const struct TALER_EXCHANGE_GetAmlLegitimizationsOptionValue *options) -{ - for (unsigned int i = 0; i < num_options; i++) - { - switch (options[i].option) - { - case TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_OPTION_END: - return GNUNET_OK; - case TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_OPTION_LIMIT: - algh->options.limit = options[i].details.limit; - break; - case TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_OPTION_OFFSET: - algh->options.offset = options[i].details.offset; - break; - case TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_OPTION_H_PAYTO: - algh->options.h_payto = options[i].details.h_payto; - break; - case TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_OPTION_ACTIVE: - algh->options.active = options[i].details.active; - break; - default: - GNUNET_break (0); - return GNUNET_NO; - } - } - return GNUNET_OK; -} - - -enum TALER_EXCHANGE_AmlLegitimizationsGetStartError -TALER_EXCHANGE_get_aml_legitimizations_start ( - struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh, - TALER_EXCHANGE_GetAmlLegitimizationsCallback cb, - TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_RESULT_CLOSURE *cb_cls) -{ - char officer_pub_str[sizeof (struct TALER_AmlOfficerPublicKeyP) * 2]; - char arg_str[sizeof (officer_pub_str) + 64]; - char limit_str[24]; - char offset_str[24]; - char paytoh_str[sizeof (struct TALER_NormalizedPaytoHashP) * 2]; - - if (NULL != algh->job) - { - GNUNET_break (0); - return TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_START_E_AGAIN; - } - algh->cb = cb; - algh->cb_cls = cb_cls; - if (algh->options.offset > INT64_MAX) - { - GNUNET_break (0); - algh->options.offset = INT64_MAX; - } - { - char *end; - - end = GNUNET_STRINGS_data_to_string ( - &algh->officer_pub, - sizeof (algh->officer_pub), - officer_pub_str, - sizeof (officer_pub_str)); - *end = '\0'; - } - if (NULL != algh->options.h_payto) - { - char *end; - - end = GNUNET_STRINGS_data_to_string ( - algh->options.h_payto, - sizeof (struct TALER_NormalizedPaytoHashP), - paytoh_str, - sizeof (paytoh_str)); - *end = '\0'; - } - /* Build query parameters */ - GNUNET_snprintf (offset_str, - sizeof (offset_str), - "%llu", - (unsigned long long) algh->options.offset); - GNUNET_snprintf (limit_str, - sizeof (limit_str), - "%lld", - (long long) algh->options.limit); - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "aml/%s/legitimizations", - officer_pub_str); - algh->url = TALER_url_join (algh->exchange_base_url, - arg_str, - "limit", - limit_str, - "offset", - ( (algh->options.limit > 0) && - (0 == algh->options.offset) ) || - ( (algh->options.limit <= 0) && - (INT64_MAX <= algh->options.offset) ) - ? NULL - : offset_str, - "h_payto", - NULL == algh->options.h_payto - ? NULL - : paytoh_str, - "active", - TALER_EXCHANGE_YNA_ALL == algh->options.active - ? NULL - : TALER_yna_to_string (algh->options.active), - NULL); - if (NULL == algh->url) - { - GNUNET_break (0); - return TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_START_E_INTERNAL; - } - - { - CURL *eh; - - eh = TALER_EXCHANGE_curl_easy_get_ (algh->url); - if (NULL == eh) - { - GNUNET_break (0); - GNUNET_free (algh->url); - algh->url = NULL; - return TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_START_E_INTERNAL; - } - - /* Add authentication header for AML officer */ - { - char *hdr; - char sig_str[sizeof (algh->officer_sig) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string ( - &algh->officer_sig, - sizeof (algh->officer_sig), - sig_str, - sizeof (sig_str)); - *end = '\0'; - GNUNET_asprintf (&hdr, - "%s: %s", - TALER_AML_OFFICER_SIGNATURE_HEADER, - sig_str); - algh->job_headers = curl_slist_append (NULL, - hdr); - GNUNET_free (hdr); - } - algh->job - = GNUNET_CURL_job_add2 ( - algh->ctx, - eh, - algh->job_headers, - &handle_aml_legitimizations_get_finished, - algh); - } - return TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_START_OK; -} - - -void -TALER_EXCHANGE_get_aml_legitimizations_cancel ( - struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh) -{ - if (NULL != algh->job) - { - GNUNET_CURL_job_cancel (algh->job); - algh->job = NULL; - } - curl_slist_free_all (algh->job_headers); - GNUNET_free (algh->exchange_base_url); - GNUNET_free (algh->url); - GNUNET_free (algh); -} - - -/* end of exchange_api_aml_legitimizations_get.c */ diff --git a/src/lib/exchange_api_auditor_add_denomination.c b/src/lib/exchange_api_auditor_add_denomination.c @@ -1,239 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2015-2021 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_auditor_add_denomination.c - * @brief functions for the auditor to add its signature for denomination at the exchange - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include "taler/taler_json_lib.h" -#include <gnunet/gnunet_curl_lib.h> -#include <microhttpd.h> -#include "taler/taler_exchange_service.h" -#include "auditor_api_curl_defaults.h" -#include "taler/taler_signatures.h" -#include "taler/taler_curl_lib.h" -#include "taler/taler_json_lib.h" - - -struct TALER_EXCHANGE_AuditorAddDenominationHandle -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Minor context that holds body and headers. - */ - struct TALER_CURL_PostContext post_ctx; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_AuditorAddDenominationCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Reference to the execution context. - */ - struct GNUNET_CURL_Context *ctx; -}; - - -/** - * Function called when we're done processing the - * HTTP POST /auditor/$AUDITOR_PUB/$H_DENOM_PUB request. - * - * @param cls the `struct TALER_EXCHANGE_AuditorAddDenominationHandle *` - * @param response_code HTTP response code, 0 on error - * @param response response body, NULL if not in JSON - */ -static void -handle_auditor_add_denomination_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_AuditorAddDenominationHandle *ah = cls; - const json_t *json = response; - struct TALER_EXCHANGE_AuditorAddDenominationResponse adr = { - .hr.http_status = (unsigned int) response_code, - .hr.reply = json - }; - - ah->job = NULL; - switch (response_code) - { - case MHD_HTTP_NO_CONTENT: - break; - case MHD_HTTP_FORBIDDEN: - adr.hr.ec = TALER_JSON_get_error_code (json); - adr.hr.hint = TALER_JSON_get_error_hint (json); - break; - case MHD_HTTP_NOT_FOUND: - adr.hr.ec = TALER_JSON_get_error_code (json); - adr.hr.hint = TALER_JSON_get_error_hint (json); - break; - case MHD_HTTP_GONE: - adr.hr.ec = TALER_JSON_get_error_code (json); - adr.hr.hint = TALER_JSON_get_error_hint (json); - break; - case MHD_HTTP_PRECONDITION_FAILED: - adr.hr.ec = TALER_JSON_get_error_code (json); - adr.hr.hint = TALER_JSON_get_error_hint (json); - break; - default: - /* unexpected response code */ - if (NULL != json) - { - adr.hr.ec = TALER_JSON_get_error_code (json); - adr.hr.hint = TALER_JSON_get_error_hint (json); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange auditor-add-denomination at URL `%s'\n", - (unsigned int) response_code, - (int) adr.hr.ec, - ah->url); - } - else - { - adr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - adr.hr.hint = NULL; - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected HTTP response code %u (no JSON returned) at URL `%s'\n", - (unsigned int) response_code, - ah->url); - } - break; - } - if (NULL != ah->cb) - { - ah->cb (ah->cb_cls, - &adr); - ah->cb = NULL; - } - TALER_EXCHANGE_add_auditor_denomination_cancel (ah); -} - - -struct TALER_EXCHANGE_AuditorAddDenominationHandle * -TALER_EXCHANGE_add_auditor_denomination ( - struct GNUNET_CURL_Context *ctx, - const char *url, - const struct TALER_DenominationHashP *h_denom_pub, - const struct TALER_AuditorPublicKeyP *auditor_pub, - const struct TALER_AuditorSignatureP *auditor_sig, - TALER_EXCHANGE_AuditorAddDenominationCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_AuditorAddDenominationHandle *ah; - CURL *eh; - json_t *body; - - ah = GNUNET_new (struct TALER_EXCHANGE_AuditorAddDenominationHandle); - ah->cb = cb; - ah->cb_cls = cb_cls; - ah->ctx = ctx; - { - char apub_str[sizeof (*auditor_pub) * 2]; - char denom_str[sizeof (*h_denom_pub) * 2]; - char arg_str[sizeof (apub_str) + sizeof (denom_str) + 32]; - char *end; - - end = GNUNET_STRINGS_data_to_string (auditor_pub, - sizeof (*auditor_pub), - apub_str, - sizeof (apub_str)); - *end = '\0'; - end = GNUNET_STRINGS_data_to_string (h_denom_pub, - sizeof (*h_denom_pub), - denom_str, - sizeof (denom_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "auditors/%s/%s", - apub_str, - denom_str); - ah->url = TALER_url_join (url, - arg_str, - NULL); - } - if (NULL == ah->url) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Could not construct request URL.\n"); - GNUNET_free (ah); - return NULL; - } - body = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_data_auto ("auditor_sig", - auditor_sig)); - eh = TALER_AUDITOR_curl_easy_get_ (ah->url); - if ( (NULL == eh) || - (GNUNET_OK != - TALER_curl_easy_post (&ah->post_ctx, - eh, - body)) ) - { - GNUNET_break (0); - if (NULL != eh) - curl_easy_cleanup (eh); - json_decref (body); - GNUNET_free (ah->url); - return NULL; - } - json_decref (body); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Requesting URL '%s'\n", - ah->url); - ah->job = GNUNET_CURL_job_add2 (ctx, - eh, - ah->post_ctx.headers, - &handle_auditor_add_denomination_finished, - ah); - if (NULL == ah->job) - { - TALER_EXCHANGE_add_auditor_denomination_cancel (ah); - return NULL; - } - return ah; -} - - -void -TALER_EXCHANGE_add_auditor_denomination_cancel ( - struct TALER_EXCHANGE_AuditorAddDenominationHandle *ah) -{ - if (NULL != ah->job) - { - GNUNET_CURL_job_cancel (ah->job); - ah->job = NULL; - } - TALER_curl_easy_post_finished (&ah->post_ctx); - GNUNET_free (ah->url); - GNUNET_free (ah); -} diff --git a/src/lib/exchange_api_batch_deposit.c b/src/lib/exchange_api_batch_deposit.c @@ -1,820 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2024 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> - */ -/** - * @file lib/exchange_api_batch_deposit.c - * @brief Implementation of the /batch-deposit request of the exchange's HTTP API - * @author Sree Harsha Totakura <sreeharsha@totakura.in> - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_json_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_json_lib.h" -#include "taler/taler_auditor_service.h" -#include "taler/taler_exchange_service.h" -#include "exchange_api_common.h" -#include "exchange_api_handle.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * 1:#AUDITOR_CHANCE is the probability that we report deposits - * to the auditor. - * - * 20==5% of going to auditor. This is possibly still too high, but set - * deliberately this high for testing - */ -#define AUDITOR_CHANCE 20 - - -/** - * Entry in list of ongoing interactions with an auditor. - */ -struct TEAH_AuditorInteractionEntry -{ - /** - * DLL entry. - */ - struct TEAH_AuditorInteractionEntry *next; - - /** - * DLL entry. - */ - struct TEAH_AuditorInteractionEntry *prev; - - /** - * URL of our auditor. For logging. - */ - const char *auditor_url; - - /** - * Interaction state. - */ - struct TALER_AUDITOR_DepositConfirmationHandle *dch; - - /** - * Batch deposit this is for. - */ - struct TALER_EXCHANGE_BatchDepositHandle *dh; -}; - - -/** - * @brief A Deposit Handle - */ -struct TALER_EXCHANGE_BatchDepositHandle -{ - - /** - * The keys of the exchange. - */ - struct TALER_EXCHANGE_Keys *keys; - - /** - * Context for our curl request(s). - */ - struct GNUNET_CURL_Context *ctx; - - /** - * The url for this request. - */ - char *url; - - /** - * Context for #TEH_curl_easy_post(). Keeps the data that must - * persist for Curl to make the upload. - */ - struct TALER_CURL_PostContext post_ctx; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_BatchDepositResultCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Details about the contract. - */ - struct TALER_EXCHANGE_DepositContractDetail dcd; - - /** - * Array with details about the coins. - */ - struct TALER_EXCHANGE_CoinDepositDetail *cdds; - - /** - * Hash of the merchant's wire details. - */ - struct TALER_MerchantWireHashP h_wire; - - /** - * Hash over the extensions, or all zero. - */ - struct TALER_ExtensionPolicyHashP h_policy; - - /** - * Time when this confirmation was generated / when the exchange received - * the deposit request. - */ - struct GNUNET_TIME_Timestamp exchange_timestamp; - - /** - * Exchange signature, set for #auditor_cb. - */ - struct TALER_ExchangeSignatureP exchange_sig; - - /** - * Head of DLL of interactions with this auditor. - */ - struct TEAH_AuditorInteractionEntry *ai_head; - - /** - * Tail of DLL of interactions with this auditor. - */ - struct TEAH_AuditorInteractionEntry *ai_tail; - - /** - * Result to return to the application once @e ai_head is empty. - */ - struct TALER_EXCHANGE_BatchDepositResult dr; - - /** - * Exchange signing public key, set for #auditor_cb. - */ - struct TALER_ExchangePublicKeyP exchange_pub; - - /** - * Total amount deposited without fees as calculated by us. - */ - struct TALER_Amount total_without_fee; - - /** - * Response object to free at the end. - */ - json_t *response; - - /** - * Chance that we will inform the auditor about the deposit - * is 1:n, where the value of this field is "n". - */ - unsigned int auditor_chance; - - /** - * Length of the @e cdds array. - */ - unsigned int num_cdds; - -}; - - -/** - * Finish batch deposit operation by calling the callback. - * - * @param[in] dh handle to finished batch deposit operation - */ -static void -finish_dh (struct TALER_EXCHANGE_BatchDepositHandle *dh) -{ - dh->cb (dh->cb_cls, - &dh->dr); - TALER_EXCHANGE_batch_deposit_cancel (dh); -} - - -/** - * Function called with the result from our call to the - * auditor's /deposit-confirmation handler. - * - * @param cls closure of type `struct TEAH_AuditorInteractionEntry *` - * @param dcr response - */ -static void -acc_confirmation_cb ( - void *cls, - const struct TALER_AUDITOR_DepositConfirmationResponse *dcr) -{ - struct TEAH_AuditorInteractionEntry *aie = cls; - struct TALER_EXCHANGE_BatchDepositHandle *dh = aie->dh; - - if (MHD_HTTP_OK != dcr->hr.http_status) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Failed to submit deposit confirmation to auditor `%s' with HTTP status %d (EC: %d). This is acceptable if it does not happen often.\n", - aie->auditor_url, - dcr->hr.http_status, - dcr->hr.ec); - } - GNUNET_CONTAINER_DLL_remove (dh->ai_head, - dh->ai_tail, - aie); - GNUNET_free (aie); - if (NULL == dh->ai_head) - finish_dh (dh); -} - - -/** - * Function called for each auditor to give us a chance to possibly - * launch a deposit confirmation interaction. - * - * @param cls closure - * @param auditor_url base URL of the auditor - * @param auditor_pub public key of the auditor - */ -static void -auditor_cb (void *cls, - const char *auditor_url, - const struct TALER_AuditorPublicKeyP *auditor_pub) -{ - struct TALER_EXCHANGE_BatchDepositHandle *dh = cls; - const struct TALER_EXCHANGE_SigningPublicKey *spk; - struct TEAH_AuditorInteractionEntry *aie; - const struct TALER_CoinSpendSignatureP *csigs[GNUNET_NZL ( - dh->num_cdds)]; - const struct TALER_CoinSpendPublicKeyP *cpubs[GNUNET_NZL ( - dh->num_cdds)]; - - for (unsigned int i = 0; i<dh->num_cdds; i++) - { - const struct TALER_EXCHANGE_CoinDepositDetail *cdd = &dh->cdds[i]; - - csigs[i] = &cdd->coin_sig; - cpubs[i] = &cdd->coin_pub; - } - - if (0 != - GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, - dh->auditor_chance)) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Not providing deposit confirmation to auditor\n"); - return; - } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Will provide deposit confirmation to auditor `%s'\n", - TALER_B2S (auditor_pub)); - spk = TALER_EXCHANGE_get_signing_key_info (dh->keys, - &dh->exchange_pub); - if (NULL == spk) - { - GNUNET_break_op (0); - return; - } - aie = GNUNET_new (struct TEAH_AuditorInteractionEntry); - aie->dh = dh; - aie->auditor_url = auditor_url; - aie->dch = TALER_AUDITOR_deposit_confirmation ( - dh->ctx, - auditor_url, - &dh->h_wire, - &dh->h_policy, - &dh->dcd.h_contract_terms, - dh->exchange_timestamp, - dh->dcd.wire_deadline, - dh->dcd.refund_deadline, - &dh->total_without_fee, - dh->num_cdds, - cpubs, - csigs, - &dh->dcd.merchant_pub, - &dh->exchange_pub, - &dh->exchange_sig, - &dh->keys->master_pub, - spk->valid_from, - spk->valid_until, - spk->valid_legal, - &spk->master_sig, - &acc_confirmation_cb, - aie); - GNUNET_CONTAINER_DLL_insert (dh->ai_head, - dh->ai_tail, - aie); -} - - -/** - * Function called when we're done processing the - * HTTP /deposit request. - * - * @param cls the `struct TALER_EXCHANGE_BatchDepositHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_deposit_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_BatchDepositHandle *dh = cls; - const json_t *j = response; - struct TALER_EXCHANGE_BatchDepositResult *dr = &dh->dr; - - dh->job = NULL; - dh->response = json_incref ((json_t*) j); - dr->hr.reply = dh->response; - dr->hr.http_status = (unsigned int) response_code; - switch (response_code) - { - case 0: - dr->hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - { - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("exchange_sig", - &dh->exchange_sig), - GNUNET_JSON_spec_fixed_auto ("exchange_pub", - &dh->exchange_pub), - GNUNET_JSON_spec_mark_optional ( - TALER_JSON_spec_web_url ("transaction_base_url", - &dr->details.ok.transaction_base_url), - NULL), - GNUNET_JSON_spec_timestamp ("exchange_timestamp", - &dh->exchange_timestamp), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - dr->hr.http_status = 0; - dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - if (GNUNET_OK != - TALER_EXCHANGE_test_signing_key (dh->keys, - &dh->exchange_pub)) - { - GNUNET_break_op (0); - dr->hr.http_status = 0; - dr->hr.ec = TALER_EC_EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE; - break; - } - { - const struct TALER_CoinSpendSignatureP *csigs[ - GNUNET_NZL (dh->num_cdds)]; - - for (unsigned int i = 0; i<dh->num_cdds; i++) - csigs[i] = &dh->cdds[i].coin_sig; - if (GNUNET_OK != - TALER_exchange_online_deposit_confirmation_verify ( - &dh->dcd.h_contract_terms, - &dh->h_wire, - &dh->h_policy, - dh->exchange_timestamp, - dh->dcd.wire_deadline, - dh->dcd.refund_deadline, - &dh->total_without_fee, - dh->num_cdds, - csigs, - &dh->dcd.merchant_pub, - &dh->exchange_pub, - &dh->exchange_sig)) - { - GNUNET_break_op (0); - dr->hr.http_status = 0; - dr->hr.ec = TALER_EC_EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE; - break; - } - } - TEAH_get_auditors_for_dc (dh->keys, - &auditor_cb, - dh); - } - dr->details.ok.exchange_sig = &dh->exchange_sig; - dr->details.ok.exchange_pub = &dh->exchange_pub; - dr->details.ok.deposit_timestamp = dh->exchange_timestamp; - break; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - dr->hr.ec = TALER_JSON_get_error_code (j); - dr->hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_FORBIDDEN: - dr->hr.ec = TALER_JSON_get_error_code (j); - dr->hr.hint = TALER_JSON_get_error_hint (j); - /* Nothing really to verify, exchange says one of the signatures is - invalid; as we checked them, this should never happen, we - should pass the JSON reply to the application */ - break; - case MHD_HTTP_NOT_FOUND: - dr->hr.ec = TALER_JSON_get_error_code (j); - dr->hr.hint = TALER_JSON_get_error_hint (j); - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - break; - case MHD_HTTP_CONFLICT: - { - dr->hr.ec = TALER_JSON_get_error_code (j); - dr->hr.hint = TALER_JSON_get_error_hint (j); - switch (dr->hr.ec) - { - case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS: - { - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ( - "coin_pub", - &dr->details.conflict.details - .insufficient_funds.coin_pub), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - dr->hr.http_status = 0; - dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - } - break; - case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_AGE_HASH: - { - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ( - "coin_pub", - &dr->details.conflict.details - .coin_conflicting_age_hash.coin_pub), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - dr->hr.http_status = 0; - dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - } - break; - case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY: - { - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ( - "coin_pub", - &dr->details.conflict.details - .coin_conflicting_denomination_key.coin_pub), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - dr->hr.http_status = 0; - dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - } - break; - case TALER_EC_EXCHANGE_DEPOSIT_CONFLICTING_CONTRACT: - break; - default: - GNUNET_break_op (0); - break; - } - } - break; - case MHD_HTTP_GONE: - /* could happen if denomination was revoked */ - /* Note: one might want to check /keys for revocation - signature here, alas tricky in case our /keys - is outdated => left to clients */ - dr->hr.ec = TALER_JSON_get_error_code (j); - dr->hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: - { - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ( - "h_payto", - &dr->details.unavailable_for_legal_reasons.h_payto), - GNUNET_JSON_spec_uint64 ( - "requirement_row", - &dr->details.unavailable_for_legal_reasons.requirement_row), - GNUNET_JSON_spec_bool ( - "bad_kyc_auth", - &dr->details.unavailable_for_legal_reasons.bad_kyc_auth), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - dr->hr.http_status = 0; - dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - } - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - dr->hr.ec = TALER_JSON_get_error_code (j); - dr->hr.hint = TALER_JSON_get_error_hint (j); - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - dr->hr.ec = TALER_JSON_get_error_code (j); - dr->hr.hint = TALER_JSON_get_error_hint (j); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange deposit\n", - (unsigned int) response_code, - dr->hr.ec); - GNUNET_break_op (0); - break; - } - if (NULL != dh->ai_head) - return; - finish_dh (dh); -} - - -struct TALER_EXCHANGE_BatchDepositHandle * -TALER_EXCHANGE_batch_deposit ( - struct GNUNET_CURL_Context *ctx, - const char *url, - struct TALER_EXCHANGE_Keys *keys, - const struct TALER_EXCHANGE_DepositContractDetail *dcd, - unsigned int num_cdds, - const struct TALER_EXCHANGE_CoinDepositDetail cdds[static num_cdds], - TALER_EXCHANGE_BatchDepositResultCallback cb, - void *cb_cls, - enum TALER_ErrorCode *ec) -{ - struct TALER_EXCHANGE_BatchDepositHandle *dh; - json_t *deposit_obj; - json_t *deposits; - CURL *eh; - const struct GNUNET_HashCode *wallet_data_hashp; - - if (0 == num_cdds) - { - GNUNET_break (0); - return NULL; - } - if (GNUNET_TIME_timestamp_cmp (dcd->refund_deadline, - >, - dcd->wire_deadline)) - { - GNUNET_break_op (0); - *ec = TALER_EC_EXCHANGE_DEPOSIT_REFUND_DEADLINE_AFTER_WIRE_DEADLINE; - return NULL; - } - dh = GNUNET_new (struct TALER_EXCHANGE_BatchDepositHandle); - dh->auditor_chance = AUDITOR_CHANCE; - dh->cb = cb; - dh->cb_cls = cb_cls; - dh->cdds = GNUNET_memdup (cdds, - num_cdds * sizeof (*cdds)); - dh->num_cdds = num_cdds; - dh->dcd = *dcd; - if (NULL != dcd->policy_details) - TALER_deposit_policy_hash (dcd->policy_details, - &dh->h_policy); - TALER_merchant_wire_signature_hash (dcd->merchant_payto_uri, - &dcd->wire_salt, - &dh->h_wire); - deposits = json_array (); - GNUNET_assert (NULL != deposits); - GNUNET_assert (GNUNET_OK == - TALER_amount_set_zero (cdds[0].amount.currency, - &dh->total_without_fee)); - for (unsigned int i = 0; i<num_cdds; i++) - { - const struct TALER_EXCHANGE_CoinDepositDetail *cdd = &cdds[i]; - const struct TALER_EXCHANGE_DenomPublicKey *dki; - const struct TALER_AgeCommitmentHashP *h_age_commitmentp; - struct TALER_Amount amount_without_fee; - - dki = TALER_EXCHANGE_get_denomination_key_by_hash (keys, - &cdd->h_denom_pub); - if (NULL == dki) - { - *ec = TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN; - GNUNET_break_op (0); - json_decref (deposits); - return NULL; - } - if (0 > - TALER_amount_subtract (&amount_without_fee, - &cdd->amount, - &dki->fees.deposit)) - { - *ec = TALER_EC_EXCHANGE_DEPOSIT_FEE_ABOVE_AMOUNT; - GNUNET_break_op (0); - GNUNET_free (dh->cdds); - GNUNET_free (dh); - json_decref (deposits); - return NULL; - } - GNUNET_assert (0 <= - TALER_amount_add (&dh->total_without_fee, - &dh->total_without_fee, - &amount_without_fee)); - if (GNUNET_OK != - TALER_EXCHANGE_verify_deposit_signature_ (dcd, - &dh->h_policy, - &dh->h_wire, - cdd, - dki)) - { - *ec = TALER_EC_EXCHANGE_DEPOSIT_COIN_SIGNATURE_INVALID; - GNUNET_break_op (0); - GNUNET_free (dh->cdds); - GNUNET_free (dh); - json_decref (deposits); - return NULL; - } - if (! GNUNET_is_zero (&dcd->merchant_sig)) - { - /* FIXME #9185: check merchant_sig!? */ - } - if (GNUNET_is_zero (&cdd->h_age_commitment)) - h_age_commitmentp = NULL; - else - h_age_commitmentp = &cdd->h_age_commitment; - GNUNET_assert ( - 0 == - json_array_append_new ( - deposits, - GNUNET_JSON_PACK ( - TALER_JSON_pack_amount ("contribution", - &cdd->amount), - GNUNET_JSON_pack_data_auto ("denom_pub_hash", - &cdd->h_denom_pub), - TALER_JSON_pack_denom_sig ("ub_sig", - &cdd->denom_sig), - GNUNET_JSON_pack_data_auto ("coin_pub", - &cdd->coin_pub), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_data_auto ("h_age_commitment", - h_age_commitmentp)), - GNUNET_JSON_pack_data_auto ("coin_sig", - &cdd->coin_sig) - ))); - } - dh->url = TALER_url_join (url, - "batch-deposit", - NULL); - if (NULL == dh->url) - { - GNUNET_break (0); - *ec = TALER_EC_GENERIC_ALLOCATION_FAILURE; - GNUNET_free (dh->url); - GNUNET_free (dh->cdds); - GNUNET_free (dh); - json_decref (deposits); - return NULL; - } - - if (GNUNET_is_zero (&dcd->wallet_data_hash)) - wallet_data_hashp = NULL; - else - wallet_data_hashp = &dcd->wallet_data_hash; - - deposit_obj = GNUNET_JSON_PACK ( - TALER_JSON_pack_full_payto ("merchant_payto_uri", - dcd->merchant_payto_uri), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_data_auto ("extra_wire_subject_metadata", - dcd->extra_wire_subject_metadata)), - GNUNET_JSON_pack_data_auto ("wire_salt", - &dcd->wire_salt), - GNUNET_JSON_pack_data_auto ("h_contract_terms", - &dcd->h_contract_terms), - GNUNET_JSON_pack_array_steal ("coins", - deposits), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_data_auto ("wallet_data_hash", - wallet_data_hashp)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_object_steal ("policy_details", - (json_t *) dcd->policy_details)), - GNUNET_JSON_pack_timestamp ("timestamp", - dcd->wallet_timestamp), - GNUNET_JSON_pack_data_auto ("merchant_pub", - &dcd->merchant_pub), - GNUNET_JSON_pack_data_auto ("merchant_sig", - &dcd->merchant_sig), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_timestamp ("refund_deadline", - dcd->refund_deadline)), - GNUNET_JSON_pack_timestamp ("wire_transfer_deadline", - dcd->wire_deadline)); - GNUNET_assert (NULL != deposit_obj); - eh = TALER_EXCHANGE_curl_easy_get_ (dh->url); - if ( (NULL == eh) || - (GNUNET_OK != - TALER_curl_easy_post (&dh->post_ctx, - eh, - deposit_obj)) ) - { - *ec = TALER_EC_GENERIC_CURL_ALLOCATION_FAILURE; - GNUNET_break (0); - if (NULL != eh) - curl_easy_cleanup (eh); - json_decref (deposit_obj); - GNUNET_free (dh->cdds); - GNUNET_free (dh->url); - GNUNET_free (dh); - return NULL; - } - json_decref (deposit_obj); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "URL for deposit: `%s'\n", - dh->url); - dh->ctx = ctx; - dh->keys = TALER_EXCHANGE_keys_incref (keys); - dh->job = GNUNET_CURL_job_add2 (ctx, - eh, - dh->post_ctx.headers, - &handle_deposit_finished, - dh); - return dh; -} - - -void -TALER_EXCHANGE_batch_deposit_force_dc ( - struct TALER_EXCHANGE_BatchDepositHandle *deposit) -{ - deposit->auditor_chance = 1; -} - - -void -TALER_EXCHANGE_batch_deposit_cancel ( - struct TALER_EXCHANGE_BatchDepositHandle *deposit) -{ - struct TEAH_AuditorInteractionEntry *aie; - - while (NULL != (aie = deposit->ai_head)) - { - GNUNET_assert (aie->dh == deposit); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Not sending deposit confirmation to auditor `%s' due to cancellation\n", - aie->auditor_url); - TALER_AUDITOR_deposit_confirmation_cancel (aie->dch); - GNUNET_CONTAINER_DLL_remove (deposit->ai_head, - deposit->ai_tail, - aie); - GNUNET_free (aie); - } - if (NULL != deposit->job) - { - GNUNET_CURL_job_cancel (deposit->job); - deposit->job = NULL; - } - TALER_EXCHANGE_keys_decref (deposit->keys); - GNUNET_free (deposit->url); - GNUNET_free (deposit->cdds); - TALER_curl_easy_post_finished (&deposit->post_ctx); - json_decref (deposit->response); - GNUNET_free (deposit); -} - - -/* end of exchange_api_batch_deposit.c */ diff --git a/src/lib/exchange_api_blinding_prepare.c b/src/lib/exchange_api_blinding_prepare.c @@ -1,422 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2025 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_blinding_prepare.c - * @brief Implementation of /blinding-prepare requests - * @author Özgür Kesim - */ - -#include "taler/platform.h" -#include <gnunet/gnunet_common.h> -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_json_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include <sys/wait.h> -#include "taler/taler_curl_lib.h" -#include "taler/taler_error_codes.h" -#include "taler/taler_json_lib.h" -#include "taler/taler_exchange_service.h" -#include "exchange_api_common.h" -#include "exchange_api_handle.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" -#include "taler/taler_util.h" - -/** - * A /blinding-prepare request-handle - */ -struct TALER_EXCHANGE_BlindingPrepareHandle -{ - /** - * number of elements to prepare - */ - size_t num; - - /** - * True, if this operation is for melting (or withdraw otherwise). - */ - bool for_melt; - - /** - * The seed for the batch of nonces. - */ - const struct TALER_BlindingMasterSeedP *seed; - - /** - * The url for this request. - */ - char *url; - - /** - * Context for curl. - */ - struct GNUNET_CURL_Context *curl_ctx; - - /** - * CURL handle for the request job. - */ - struct GNUNET_CURL_Job *job; - - /** - * Post Context - */ - struct TALER_CURL_PostContext post_ctx; - - /** - * Function to call with withdraw response results. - */ - TALER_EXCHANGE_BlindingPrepareCallback callback; - - /** - * Closure for @e callback - */ - void *callback_cls; - -}; - - -/** - * We got a 200 OK response for the /blinding-prepare operation. - * Extract the r_pub values and return them to the caller via the callback - * - * @param handle operation handle - * @param response response details - * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors - */ -static enum GNUNET_GenericReturnValue -blinding_prepare_ok (struct TALER_EXCHANGE_BlindingPrepareHandle *handle, - struct TALER_EXCHANGE_BlindingPrepareResponse *response) -{ - const json_t *j_r_pubs; - const char *cipher; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("cipher", - &cipher), - GNUNET_JSON_spec_array_const ("r_pubs", - &j_r_pubs), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (response->hr.reply, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - if (strcmp ("CS", cipher)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - if (json_array_size (j_r_pubs) - != handle->num) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - { - size_t num = handle->num; - const json_t *j_pair; - size_t idx; - struct TALER_ExchangeBlindingValues blinding_values[GNUNET_NZL (num)]; - - memset (blinding_values, - 0, - sizeof(blinding_values)); - - json_array_foreach (j_r_pubs, idx, j_pair) { - struct GNUNET_CRYPTO_BlindingInputValues *bi = - GNUNET_new (struct GNUNET_CRYPTO_BlindingInputValues); - struct GNUNET_CRYPTO_CSPublicRPairP *csv = &bi->details.cs_values; - struct GNUNET_JSON_Specification tuple[] = { - GNUNET_JSON_spec_fixed (NULL, - &csv->r_pub[0], - sizeof(csv->r_pub[0])), - GNUNET_JSON_spec_fixed (NULL, - &csv->r_pub[1], - sizeof(csv->r_pub[1])), - GNUNET_JSON_spec_end () - }; - struct GNUNET_JSON_Specification jspec[] = { - TALER_JSON_spec_tuple_of (NULL, tuple), - GNUNET_JSON_spec_end () - }; - const char *err_msg; - unsigned int err_line; - - if (GNUNET_OK != - GNUNET_JSON_parse (j_pair, - jspec, - &err_msg, - &err_line)) - { - GNUNET_break_op (0); - GNUNET_free (bi); - for (size_t i=0; i < idx; i++) - TALER_denom_ewv_free (&blinding_values[i]); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Error while parsing response: in line %d: %s", - err_line, - err_msg); - return GNUNET_SYSERR; - } - - bi->cipher = GNUNET_CRYPTO_BSA_CS; - bi->rc = 1; - blinding_values[idx].blinding_inputs = bi; - } - - response->details.ok.blinding_values = blinding_values; - response->details.ok.num_blinding_values = num; - - handle->callback ( - handle->callback_cls, - response); - - for (size_t i = 0; i < num; i++) - TALER_denom_ewv_free (&blinding_values[i]); - } - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the HTTP /blinding-prepare request. - * - * @param cls the `struct TALER_EXCHANGE_BlindingPrepareHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_blinding_prepare_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_BlindingPrepareHandle *handle = cls; - const json_t *j_response = response; - struct TALER_EXCHANGE_BlindingPrepareResponse bpr = { - .hr = { - .reply = j_response, - .http_status = (unsigned int) response_code - }, - }; - - handle->job = NULL; - - switch (response_code) - { - case 0: - bpr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - - case MHD_HTTP_OK: - { - if (GNUNET_OK != - blinding_prepare_ok (handle, - &bpr)) - { - GNUNET_break_op (0); - bpr.hr.http_status = 0; - bpr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - } - TALER_EXCHANGE_blinding_prepare_cancel (handle); - return; - - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - bpr.hr.ec = TALER_JSON_get_error_code (j_response); - bpr.hr.hint = TALER_JSON_get_error_hint (j_response); - break; - - case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, the exchange basically just says - that it doesn't know the /csr endpoint or denomination. - Can happen if the exchange doesn't support Clause Schnorr. - We should simply pass the JSON reply to the application. */ - bpr.hr.ec = TALER_JSON_get_error_code (j_response); - bpr.hr.hint = TALER_JSON_get_error_hint (j_response); - break; - - case MHD_HTTP_GONE: - /* could happen if denomination was revoked */ - /* Note: one might want to check /keys for revocation - signature here, alas tricky in case our /keys - is outdated => left to clients */ - bpr.hr.ec = TALER_JSON_get_error_code (j_response); - bpr.hr.hint = TALER_JSON_get_error_hint (j_response); - break; - - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - bpr.hr.ec = TALER_JSON_get_error_code (j_response); - bpr.hr.hint = TALER_JSON_get_error_hint (j_response); - break; - - default: - /* unexpected response code */ - GNUNET_break_op (0); - bpr.hr.ec = TALER_JSON_get_error_code (j_response); - bpr.hr.hint = TALER_JSON_get_error_hint (j_response); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for the blinding-prepare request\n", - (unsigned int) response_code, - (int) bpr.hr.ec); - break; - - } - - handle->callback (handle->callback_cls, - &bpr); - handle->callback = NULL; - TALER_EXCHANGE_blinding_prepare_cancel (handle); -} - - -struct TALER_EXCHANGE_BlindingPrepareHandle * -TALER_EXCHANGE_blinding_prepare ( - struct GNUNET_CURL_Context *curl_ctx, - const char *exchange_url, - const struct TALER_BlindingMasterSeedP *seed, - bool for_melt, - size_t num, - const struct TALER_EXCHANGE_NonceKey nonce_keys[static num], - TALER_EXCHANGE_BlindingPrepareCallback callback, - void *callback_cls) -{ - struct TALER_EXCHANGE_BlindingPrepareHandle *bph; - - if (0 == num) - { - GNUNET_break (0); - return NULL; - } - for (unsigned int i = 0; i<num; i++) - if (GNUNET_CRYPTO_BSA_CS != - nonce_keys[i].pk->key.bsign_pub_key->cipher) - { - GNUNET_break (0); - return NULL; - } - bph = GNUNET_new (struct TALER_EXCHANGE_BlindingPrepareHandle); - bph->num = num; - bph->callback = callback; - bph->for_melt = for_melt; - bph->callback_cls = callback_cls; - bph->url = TALER_url_join (exchange_url, - "blinding-prepare", - NULL); - if (NULL == bph->url) - { - GNUNET_break (0); - GNUNET_free (bph); - return NULL; - } - { - CURL *eh; - json_t *j_nks; - json_t *j_request = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("cipher", - "CS"), - GNUNET_JSON_pack_string ("operation", - for_melt ? "melt" : "withdraw"), - GNUNET_JSON_pack_data_auto ("seed", - seed)); - GNUNET_assert (NULL != j_request); - - j_nks = json_array (); - GNUNET_assert (NULL != j_nks); - - for (size_t i = 0; i<num; i++) - { - const struct TALER_EXCHANGE_NonceKey *nk = &nonce_keys[i]; - json_t *j_entry = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_uint64 ("coin_offset", - nk->cnc_num), - GNUNET_JSON_pack_data_auto ("denom_pub_hash", - &nk->pk->h_key)); - - GNUNET_assert (NULL != j_entry); - GNUNET_assert (0 == - json_array_append_new (j_nks, - j_entry)); - } - GNUNET_assert (0 == - json_object_set_new (j_request, - "nks", - j_nks)); - eh = TALER_EXCHANGE_curl_easy_get_ (bph->url); - if ( (NULL == eh) || - (GNUNET_OK != - TALER_curl_easy_post (&bph->post_ctx, - eh, - j_request))) - { - GNUNET_break (0); - if (NULL != eh) - curl_easy_cleanup (eh); - json_decref (j_request); - GNUNET_free (bph->url); - GNUNET_free (bph); - return NULL; - } - - json_decref (j_request); - bph->job = GNUNET_CURL_job_add2 (curl_ctx, - eh, - bph->post_ctx.headers, - &handle_blinding_prepare_finished, - bph); - if (NULL == bph->job) - { - GNUNET_break (0); - TALER_EXCHANGE_blinding_prepare_cancel (bph); - return NULL; - } - } - return bph; -} - - -void -TALER_EXCHANGE_blinding_prepare_cancel ( - struct TALER_EXCHANGE_BlindingPrepareHandle *bph) -{ - if (NULL == bph) - return; - if (NULL != bph->job) - { - GNUNET_CURL_job_cancel (bph->job); - bph->job = NULL; - } - GNUNET_free (bph->url); - TALER_curl_easy_post_finished (&bph->post_ctx); - GNUNET_free (bph); -} - - -/* end of lib/exchange_api_blinding_prepare.c */ diff --git a/src/lib/exchange_api_coins_history.c b/src/lib/exchange_api_coins_history.c @@ -1,1235 +0,0 @@ -/* - This file is part of TALER - 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 General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_coins_history.c - * @brief Implementation of the POST /coins/$COIN_PUB/history requests - * @author Christian Grothoff - * - * NOTE: this is an incomplete draft, never finished! - */ -#include "taler/platform.h" -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP history codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_json_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_exchange_service.h" -#include "taler/taler_json_lib.h" -#include "exchange_api_handle.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * @brief A /coins/$RID/history Handle - */ -struct TALER_EXCHANGE_CoinsHistoryHandle -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Context for #TEH_curl_easy_post(). Keeps the data that must - * persist for Curl to make the upload. - */ - struct TALER_CURL_PostContext post_ctx; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_CoinsHistoryCallback cb; - - /** - * Public key of the coin we are querying. - */ - struct TALER_CoinSpendPublicKeyP coin_pub; - - /** - * Closure for @a cb. - */ - void *cb_cls; - -}; - - -/** - * Context for coin helpers. - */ -struct CoinHistoryParseContext -{ - - /** - * Keys of the exchange. - */ - struct TALER_EXCHANGE_Keys *keys; - - /** - * Denomination of the coin. - */ - const struct TALER_EXCHANGE_DenomPublicKey *dk; - - /** - * Our coin public key. - */ - const struct TALER_CoinSpendPublicKeyP *coin_pub; - - /** - * Where to sum up total refunds. - */ - struct TALER_Amount *total_in; - - /** - * Total amount encountered. - */ - struct TALER_Amount *total_out; - -}; - - -/** - * Signature of functions that operate on one of - * the coin's history entries. - * - * @param[in,out] pc overall context - * @param[out] rh where to write the history entry - * @param amount main amount of this operation - * @param transaction JSON details for the operation - * @return #GNUNET_SYSERR on error, - * #GNUNET_OK to add, #GNUNET_NO to subtract - */ -typedef enum GNUNET_GenericReturnValue -(*CoinCheckHelper)(struct CoinHistoryParseContext *pc, - struct TALER_EXCHANGE_CoinHistoryEntry *rh, - const struct TALER_Amount *amount, - json_t *transaction); - - -/** - * Handle deposit entry in the coin's history. - * - * @param[in,out] pc overall context - * @param[out] rh history entry to initialize - * @param amount main amount of this operation - * @param transaction JSON details for the operation - * @return #GNUNET_SYSERR on error, - * #GNUNET_OK to add, #GNUNET_NO to subtract - */ -static enum GNUNET_GenericReturnValue -help_deposit (struct CoinHistoryParseContext *pc, - struct TALER_EXCHANGE_CoinHistoryEntry *rh, - const struct TALER_Amount *amount, - json_t *transaction) -{ - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount_any ("deposit_fee", - &rh->details.deposit.deposit_fee), - GNUNET_JSON_spec_fixed_auto ("merchant_pub", - &rh->details.deposit.merchant_pub), - GNUNET_JSON_spec_timestamp ("timestamp", - &rh->details.deposit.wallet_timestamp), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_timestamp ("refund_deadline", - &rh->details.deposit.refund_deadline), - NULL), - GNUNET_JSON_spec_fixed_auto ("h_contract_terms", - &rh->details.deposit.h_contract_terms), - GNUNET_JSON_spec_fixed_auto ("h_wire", - &rh->details.deposit.h_wire), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_fixed_auto ("h_policy", - &rh->details.deposit.h_policy), - &rh->details.deposit.no_h_policy), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_fixed_auto ("wallet_data_hash", - &rh->details.deposit.wallet_data_hash), - &rh->details.deposit.no_wallet_data_hash), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_fixed_auto ("h_age_commitment", - &rh->details.deposit.hac), - &rh->details.deposit.no_hac), - GNUNET_JSON_spec_fixed_auto ("coin_sig", - &rh->details.deposit.sig), - GNUNET_JSON_spec_end () - }; - - rh->details.deposit.refund_deadline = GNUNET_TIME_UNIT_ZERO_TS; - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_wallet_deposit_verify ( - amount, - &rh->details.deposit.deposit_fee, - &rh->details.deposit.h_wire, - &rh->details.deposit.h_contract_terms, - rh->details.deposit.no_wallet_data_hash - ? NULL - : &rh->details.deposit.wallet_data_hash, - rh->details.deposit.no_hac - ? NULL - : &rh->details.deposit.hac, - rh->details.deposit.no_h_policy - ? NULL - : &rh->details.deposit.h_policy, - &pc->dk->h_key, - rh->details.deposit.wallet_timestamp, - &rh->details.deposit.merchant_pub, - rh->details.deposit.refund_deadline, - pc->coin_pub, - &rh->details.deposit.sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - /* check that deposit fee matches our expectations from /keys! */ - if ( (GNUNET_YES != - TALER_amount_cmp_currency (&rh->details.deposit.deposit_fee, - &pc->dk->fees.deposit)) || - (0 != - TALER_amount_cmp (&rh->details.deposit.deposit_fee, - &pc->dk->fees.deposit)) ) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_YES; -} - - -/** - * Handle melt entry in the coin's history. - * - * @param[in,out] pc overall context - * @param[out] rh history entry to initialize - * @param amount main amount of this operation - * @param transaction JSON details for the operation - * @return #GNUNET_SYSERR on error, - * #GNUNET_OK to add, #GNUNET_NO to subtract - */ -static enum GNUNET_GenericReturnValue -help_melt (struct CoinHistoryParseContext *pc, - struct TALER_EXCHANGE_CoinHistoryEntry *rh, - const struct TALER_Amount *amount, - json_t *transaction) -{ - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount_any ("melt_fee", - &rh->details.melt.melt_fee), - GNUNET_JSON_spec_fixed_auto ("rc", - &rh->details.melt.rc), - // FIXME: also return refresh_seed? - // FIXME: also return blinding_seed? - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_fixed_auto ("h_age_commitment", - &rh->details.melt.h_age_commitment), - &rh->details.melt.no_hac), - GNUNET_JSON_spec_fixed_auto ("coin_sig", - &rh->details.melt.sig), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - /* check that melt fee matches our expectations from /keys! */ - if ( (GNUNET_YES != - TALER_amount_cmp_currency (&rh->details.melt.melt_fee, - &pc->dk->fees.refresh)) || - (0 != - TALER_amount_cmp (&rh->details.melt.melt_fee, - &pc->dk->fees.refresh)) ) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_wallet_melt_verify ( - amount, - &rh->details.melt.melt_fee, - &rh->details.melt.rc, - &pc->dk->h_key, - rh->details.melt.no_hac - ? NULL - : &rh->details.melt.h_age_commitment, - pc->coin_pub, - &rh->details.melt.sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_YES; -} - - -/** - * Handle refund entry in the coin's history. - * - * @param[in,out] pc overall context - * @param[out] rh history entry to initialize - * @param amount main amount of this operation - * @param transaction JSON details for the operation - * @return #GNUNET_SYSERR on error, - * #GNUNET_OK to add, #GNUNET_NO to subtract - */ -static enum GNUNET_GenericReturnValue -help_refund (struct CoinHistoryParseContext *pc, - struct TALER_EXCHANGE_CoinHistoryEntry *rh, - const struct TALER_Amount *amount, - json_t *transaction) -{ - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount_any ("refund_fee", - &rh->details.refund.refund_fee), - GNUNET_JSON_spec_fixed_auto ("h_contract_terms", - &rh->details.refund.h_contract_terms), - GNUNET_JSON_spec_fixed_auto ("merchant_pub", - &rh->details.refund.merchant_pub), - GNUNET_JSON_spec_uint64 ("rtransaction_id", - &rh->details.refund.rtransaction_id), - GNUNET_JSON_spec_fixed_auto ("merchant_sig", - &rh->details.refund.sig), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (0 > - TALER_amount_add (&rh->details.refund.sig_amount, - &rh->details.refund.refund_fee, - amount)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_merchant_refund_verify (pc->coin_pub, - &rh->details.refund.h_contract_terms, - rh->details.refund.rtransaction_id, - &rh->details.refund.sig_amount, - &rh->details.refund.merchant_pub, - &rh->details.refund.sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - /* NOTE: theoretically, we could also check that the given - merchant_pub and h_contract_terms appear in the - history under deposits. However, there is really no benefit - for the exchange to lie here, so not checking is probably OK - (an auditor ought to check, though). Then again, we similarly - had no reason to check the merchant's signature (other than a - well-formendess check). */ - - /* check that refund fee matches our expectations from /keys! */ - if ( (GNUNET_YES != - TALER_amount_cmp_currency (&rh->details.refund.refund_fee, - &pc->dk->fees.refund)) || - (0 != - TALER_amount_cmp (&rh->details.refund.refund_fee, - &pc->dk->fees.refund)) ) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_NO; -} - - -/** - * Handle recoup entry in the coin's history. - * - * @param[in,out] pc overall context - * @param[out] rh history entry to initialize - * @param amount main amount of this operation - * @param transaction JSON details for the operation - * @return #GNUNET_SYSERR on error, - * #GNUNET_OK to add, #GNUNET_NO to subtract - */ -static enum GNUNET_GenericReturnValue -help_recoup (struct CoinHistoryParseContext *pc, - struct TALER_EXCHANGE_CoinHistoryEntry *rh, - const struct TALER_Amount *amount, - json_t *transaction) -{ - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("exchange_sig", - &rh->details.recoup.exchange_sig), - GNUNET_JSON_spec_fixed_auto ("exchange_pub", - &rh->details.recoup.exchange_pub), - GNUNET_JSON_spec_fixed_auto ("reserve_pub", - &rh->details.recoup.reserve_pub), - GNUNET_JSON_spec_fixed_auto ("coin_sig", - &rh->details.recoup.coin_sig), - GNUNET_JSON_spec_fixed_auto ("coin_blind", - &rh->details.recoup.coin_bks), - GNUNET_JSON_spec_timestamp ("timestamp", - &rh->details.recoup.timestamp), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_exchange_online_confirm_recoup_verify ( - rh->details.recoup.timestamp, - amount, - pc->coin_pub, - &rh->details.recoup.reserve_pub, - &rh->details.recoup.exchange_pub, - &rh->details.recoup.exchange_sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_wallet_recoup_verify (&pc->dk->h_key, - &rh->details.recoup.coin_bks, - pc->coin_pub, - &rh->details.recoup.coin_sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_YES; -} - - -/** - * Handle recoup-refresh entry in the coin's history. - * This is the coin that was subjected to a recoup, - * the value being credited to the old coin. - * - * @param[in,out] pc overall context - * @param[out] rh history entry to initialize - * @param amount main amount of this operation - * @param transaction JSON details for the operation - * @return #GNUNET_SYSERR on error, - * #GNUNET_OK to add, #GNUNET_NO to subtract - */ -static enum GNUNET_GenericReturnValue -help_recoup_refresh (struct CoinHistoryParseContext *pc, - struct TALER_EXCHANGE_CoinHistoryEntry *rh, - const struct TALER_Amount *amount, - json_t *transaction) -{ - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("exchange_sig", - &rh->details.recoup_refresh.exchange_sig), - GNUNET_JSON_spec_fixed_auto ("exchange_pub", - &rh->details.recoup_refresh.exchange_pub), - GNUNET_JSON_spec_fixed_auto ("coin_sig", - &rh->details.recoup_refresh.coin_sig), - GNUNET_JSON_spec_fixed_auto ("old_coin_pub", - &rh->details.recoup_refresh.old_coin_pub), - GNUNET_JSON_spec_fixed_auto ("coin_blind", - &rh->details.recoup_refresh.coin_bks), - GNUNET_JSON_spec_timestamp ("timestamp", - &rh->details.recoup_refresh.timestamp), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_exchange_online_confirm_recoup_refresh_verify ( - rh->details.recoup_refresh.timestamp, - amount, - pc->coin_pub, - &rh->details.recoup_refresh.old_coin_pub, - &rh->details.recoup_refresh.exchange_pub, - &rh->details.recoup_refresh.exchange_sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_wallet_recoup_verify (&pc->dk->h_key, - &rh->details.recoup_refresh.coin_bks, - pc->coin_pub, - &rh->details.recoup_refresh.coin_sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_YES; -} - - -/** - * Handle old coin recoup entry in the coin's history. - * This is the coin that was credited in a recoup, - * the value being credited to the this coin. - * - * @param[in,out] pc overall context - * @param[out] rh history entry to initialize - * @param amount main amount of this operation - * @param transaction JSON details for the operation - * @return #GNUNET_SYSERR on error, - * #GNUNET_OK to add, #GNUNET_NO to subtract - */ -static enum GNUNET_GenericReturnValue -help_old_coin_recoup (struct CoinHistoryParseContext *pc, - struct TALER_EXCHANGE_CoinHistoryEntry *rh, - const struct TALER_Amount *amount, - json_t *transaction) -{ - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("exchange_sig", - &rh->details.old_coin_recoup.exchange_sig), - GNUNET_JSON_spec_fixed_auto ("exchange_pub", - &rh->details.old_coin_recoup.exchange_pub), - GNUNET_JSON_spec_fixed_auto ("coin_pub", - &rh->details.old_coin_recoup.new_coin_pub), - GNUNET_JSON_spec_timestamp ("timestamp", - &rh->details.old_coin_recoup.timestamp), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_exchange_online_confirm_recoup_refresh_verify ( - rh->details.old_coin_recoup.timestamp, - amount, - &rh->details.old_coin_recoup.new_coin_pub, - pc->coin_pub, - &rh->details.old_coin_recoup.exchange_pub, - &rh->details.old_coin_recoup.exchange_sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_NO; -} - - -/** - * Handle purse deposit entry in the coin's history. - * - * @param[in,out] pc overall context - * @param[out] rh history entry to initialize - * @param amount main amount of this operation - * @param transaction JSON details for the operation - * @return #GNUNET_SYSERR on error, - * #GNUNET_OK to add, #GNUNET_NO to subtract - */ -static enum GNUNET_GenericReturnValue -help_purse_deposit (struct CoinHistoryParseContext *pc, - struct TALER_EXCHANGE_CoinHistoryEntry *rh, - const struct TALER_Amount *amount, - json_t *transaction) -{ - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_web_url ("exchange_base_url", - &rh->details.purse_deposit.exchange_base_url), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_fixed_auto ("h_age_commitment", - &rh->details.purse_deposit.phac), - NULL), - // FIXME: return deposit_fee? - GNUNET_JSON_spec_fixed_auto ("purse_pub", - &rh->details.purse_deposit.purse_pub), - GNUNET_JSON_spec_bool ("refunded", - &rh->details.purse_deposit.refunded), - GNUNET_JSON_spec_fixed_auto ("coin_sig", - &rh->details.purse_deposit.coin_sig), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_wallet_purse_deposit_verify ( - rh->details.purse_deposit.exchange_base_url, - &rh->details.purse_deposit.purse_pub, - amount, - &pc->dk->h_key, - &rh->details.purse_deposit.phac, - pc->coin_pub, - &rh->details.purse_deposit.coin_sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (rh->details.purse_deposit.refunded) - { - /* We wave the deposit fee. */ - if (0 > - TALER_amount_add (pc->total_in, - pc->total_in, - &pc->dk->fees.deposit)) - { - /* overflow in refund history? inconceivable! Bad exchange! */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - } - return GNUNET_YES; -} - - -/** - * Handle purse refund entry in the coin's history. - * - * @param[in,out] pc overall context - * @param[out] rh history entry to initialize - * @param amount main amount of this operation - * @param transaction JSON details for the operation - * @return #GNUNET_SYSERR on error, - * #GNUNET_OK to add, #GNUNET_NO to subtract - */ -static enum GNUNET_GenericReturnValue -help_purse_refund (struct CoinHistoryParseContext *pc, - struct TALER_EXCHANGE_CoinHistoryEntry *rh, - const struct TALER_Amount *amount, - json_t *transaction) -{ - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount_any ("refund_fee", - &rh->details.purse_refund.refund_fee), - GNUNET_JSON_spec_fixed_auto ("exchange_sig", - &rh->details.purse_refund.exchange_sig), - GNUNET_JSON_spec_fixed_auto ("exchange_pub", - &rh->details.purse_refund.exchange_pub), - GNUNET_JSON_spec_fixed_auto ("purse_pub", - &rh->details.purse_refund.purse_pub), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_exchange_online_purse_refund_verify ( - amount, - &rh->details.purse_refund.refund_fee, - pc->coin_pub, - &rh->details.purse_refund.purse_pub, - &rh->details.purse_refund.exchange_pub, - &rh->details.purse_refund.exchange_sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if ( (GNUNET_YES != - TALER_amount_cmp_currency (&rh->details.purse_refund.refund_fee, - &pc->dk->fees.refund)) || - (0 != - TALER_amount_cmp (&rh->details.purse_refund.refund_fee, - &pc->dk->fees.refund)) ) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_NO; -} - - -/** - * Handle reserve deposit entry in the coin's history. - * - * @param[in,out] pc overall context - * @param[out] rh history entry to initialize - * @param amount main amount of this operation - * @param transaction JSON details for the operation - * @return #GNUNET_SYSERR on error, - * #GNUNET_OK to add, #GNUNET_NO to subtract - */ -static enum GNUNET_GenericReturnValue -help_reserve_open_deposit (struct CoinHistoryParseContext *pc, - struct TALER_EXCHANGE_CoinHistoryEntry *rh, - const struct TALER_Amount *amount, - json_t *transaction) -{ - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("reserve_sig", - &rh->details.reserve_open_deposit.reserve_sig), - GNUNET_JSON_spec_fixed_auto ("coin_sig", - &rh->details.reserve_open_deposit.coin_sig), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_wallet_reserve_open_deposit_verify ( - amount, - &rh->details.reserve_open_deposit.reserve_sig, - pc->coin_pub, - &rh->details.reserve_open_deposit.coin_sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_YES; -} - - -enum GNUNET_GenericReturnValue -TALER_EXCHANGE_parse_coin_history ( - const struct TALER_EXCHANGE_Keys *keys, - const struct TALER_EXCHANGE_DenomPublicKey *dk, - const json_t *history, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - struct TALER_Amount *total_in, - struct TALER_Amount *total_out, - unsigned int rlen, - struct TALER_EXCHANGE_CoinHistoryEntry rhistory[static rlen]) -{ - const struct - { - const char *type; - CoinCheckHelper helper; - enum TALER_EXCHANGE_CoinTransactionType ctt; - } map[] = { - { "DEPOSIT", - &help_deposit, - TALER_EXCHANGE_CTT_DEPOSIT }, - { "MELT", - &help_melt, - TALER_EXCHANGE_CTT_MELT }, - { "REFUND", - &help_refund, - TALER_EXCHANGE_CTT_REFUND }, - { "RECOUP", - &help_recoup, - TALER_EXCHANGE_CTT_RECOUP }, - { "RECOUP-REFRESH", - &help_recoup_refresh, - TALER_EXCHANGE_CTT_RECOUP_REFRESH }, - { "OLD-COIN-RECOUP", - &help_old_coin_recoup, - TALER_EXCHANGE_CTT_OLD_COIN_RECOUP }, - { "PURSE-DEPOSIT", - &help_purse_deposit, - TALER_EXCHANGE_CTT_PURSE_DEPOSIT }, - { "PURSE-REFUND", - &help_purse_refund, - TALER_EXCHANGE_CTT_PURSE_REFUND }, - { "RESERVE-OPEN-DEPOSIT", - &help_reserve_open_deposit, - TALER_EXCHANGE_CTT_RESERVE_OPEN_DEPOSIT }, - { NULL, NULL, TALER_EXCHANGE_CTT_NONE } - }; - struct CoinHistoryParseContext pc = { - .dk = dk, - .coin_pub = coin_pub, - .total_out = total_out, - .total_in = total_in - }; - size_t len; - - if (NULL == history) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - len = json_array_size (history); - if (0 == len) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - *total_in = dk->value; - GNUNET_assert (GNUNET_OK == - TALER_amount_set_zero (total_in->currency, - total_out)); - for (size_t off = 0; off<len; off++) - { - struct TALER_EXCHANGE_CoinHistoryEntry *rh = &rhistory[off]; - json_t *transaction = json_array_get (history, - off); - enum GNUNET_GenericReturnValue add; - const char *type; - struct GNUNET_JSON_Specification spec_glob[] = { - TALER_JSON_spec_amount_any ("amount", - &rh->amount), - GNUNET_JSON_spec_string ("type", - &type), - GNUNET_JSON_spec_uint64 ("history_offset", - &rh->history_offset), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - spec_glob, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_YES != - TALER_amount_cmp_currency (&rh->amount, - total_in)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Operation of type %s with amount %s\n", - type, - TALER_amount2s (&rh->amount)); - add = GNUNET_SYSERR; - for (unsigned int i = 0; NULL != map[i].type; i++) - { - if (0 == strcasecmp (type, - map[i].type)) - { - rh->type = map[i].ctt; - add = map[i].helper (&pc, - rh, - &rh->amount, - transaction); - break; - } - } - switch (add) - { - case GNUNET_SYSERR: - /* entry type not supported, new version on server? */ - rh->type = TALER_EXCHANGE_CTT_NONE; - GNUNET_break_op (0); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected type `%s' in response\n", - type); - return GNUNET_SYSERR; - case GNUNET_YES: - /* This amount should be debited from the coin */ - if (0 > - TALER_amount_add (total_out, - total_out, - &rh->amount)) - { - /* overflow in history already!? inconceivable! Bad exchange! */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - break; - case GNUNET_NO: - /* This amount should be credited to the coin. */ - if (0 > - TALER_amount_add (total_in, - total_in, - &rh->amount)) - { - /* overflow in refund history? inconceivable! Bad exchange! */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - break; - } /* end of switch(add) */ - } - return GNUNET_OK; -} - - -/** - * We received an #MHD_HTTP_OK history code. Handle the JSON - * response. - * - * @param rsh handle of the request - * @param j JSON response - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -handle_coins_history_ok (struct TALER_EXCHANGE_CoinsHistoryHandle *rsh, - const json_t *j) -{ - struct TALER_EXCHANGE_CoinHistory rs = { - .hr.reply = j, - .hr.http_status = MHD_HTTP_OK - }; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount_any ("balance", - &rs.details.ok.balance), - GNUNET_JSON_spec_fixed_auto ("h_denom_pub", - &rs.details.ok.h_denom_pub), - GNUNET_JSON_spec_array_const ("history", - &rs.details.ok.history), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, - NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (NULL != rsh->cb) - { - rsh->cb (rsh->cb_cls, - &rs); - rsh->cb = NULL; - } - GNUNET_JSON_parse_free (spec); - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /coins/$RID/history request. - * - * @param cls the `struct TALER_EXCHANGE_CoinsHistoryHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_coins_history_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_CoinsHistoryHandle *rsh = cls; - const json_t *j = response; - struct TALER_EXCHANGE_CoinHistory rs = { - .hr.reply = j, - .hr.http_status = (unsigned int) response_code - }; - - rsh->job = NULL; - switch (response_code) - { - case 0: - rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - if (GNUNET_OK != - handle_coins_history_ok (rsh, - j)) - { - rs.hr.http_status = 0; - rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - } - break; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - GNUNET_break (0); - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_FORBIDDEN: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - GNUNET_break (0); - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for coins history\n", - (unsigned int) response_code, - (int) rs.hr.ec); - break; - } - if (NULL != rsh->cb) - { - rsh->cb (rsh->cb_cls, - &rs); - rsh->cb = NULL; - } - TALER_EXCHANGE_coins_history_cancel (rsh); -} - - -struct TALER_EXCHANGE_CoinsHistoryHandle * -TALER_EXCHANGE_coins_history ( - struct GNUNET_CURL_Context *ctx, - const char *url, - const struct TALER_CoinSpendPrivateKeyP *coin_priv, - uint64_t start_off, - TALER_EXCHANGE_CoinsHistoryCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_CoinsHistoryHandle *rsh; - CURL *eh; - char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 64]; - struct curl_slist *job_headers; - - rsh = GNUNET_new (struct TALER_EXCHANGE_CoinsHistoryHandle); - rsh->cb = cb; - rsh->cb_cls = cb_cls; - GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, - &rsh->coin_pub.eddsa_pub); - { - char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string ( - &rsh->coin_pub, - sizeof (rsh->coin_pub), - pub_str, - sizeof (pub_str)); - *end = '\0'; - if (0 != start_off) - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "coins/%s/history?start=%llu", - pub_str, - (unsigned long long) start_off); - else - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "coins/%s/history", - pub_str); - } - rsh->url = TALER_url_join (url, - arg_str, - NULL); - if (NULL == rsh->url) - { - GNUNET_free (rsh); - return NULL; - } - eh = TALER_EXCHANGE_curl_easy_get_ (rsh->url); - if (NULL == eh) - { - GNUNET_break (0); - GNUNET_free (rsh->url); - GNUNET_free (rsh); - return NULL; - } - - { - struct TALER_CoinSpendSignatureP coin_sig; - char *sig_hdr; - char *hdr; - - TALER_wallet_coin_history_sign (start_off, - coin_priv, - &coin_sig); - - sig_hdr = GNUNET_STRINGS_data_to_string_alloc ( - &coin_sig, - sizeof (coin_sig)); - GNUNET_asprintf (&hdr, - "%s: %s", - TALER_COIN_HISTORY_SIGNATURE_HEADER, - sig_hdr); - GNUNET_free (sig_hdr); - job_headers = curl_slist_append (NULL, - hdr); - GNUNET_free (hdr); - if (NULL == job_headers) - { - GNUNET_break (0); - curl_easy_cleanup (eh); - return NULL; - } - } - - rsh->job = GNUNET_CURL_job_add2 (ctx, - eh, - job_headers, - &handle_coins_history_finished, - rsh); - curl_slist_free_all (job_headers); - return rsh; -} - - -void -TALER_EXCHANGE_coins_history_cancel ( - struct TALER_EXCHANGE_CoinsHistoryHandle *rsh) -{ - if (NULL != rsh->job) - { - GNUNET_CURL_job_cancel (rsh->job); - rsh->job = NULL; - } - TALER_curl_easy_post_finished (&rsh->post_ctx); - GNUNET_free (rsh->url); - GNUNET_free (rsh); -} - - -/** - * Verify that @a coin_sig does NOT appear in the @a history of a coin's - * transactions and thus whatever transaction is authorized by @a coin_sig is - * a conflict with @a proof. - * - * @param history coin history to check - * @param coin_sig signature that must not be in @a history - * @return #GNUNET_OK if @a coin_sig is not in @a history - */ -enum GNUNET_GenericReturnValue -TALER_EXCHANGE_check_coin_signature_conflict ( - const json_t *history, - const struct TALER_CoinSpendSignatureP *coin_sig) -{ - size_t off; - json_t *entry; - - json_array_foreach (history, off, entry) - { - struct TALER_CoinSpendSignatureP cs; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("coin_sig", - &cs), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (entry, - spec, - NULL, NULL)) - continue; /* entry without coin signature */ - if (0 == - GNUNET_memcmp (&cs, - coin_sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - } - return GNUNET_OK; -} - - -#if FIXME_IMPLEMENT /* #9422 */ -/** - * FIXME-Oec-#9422: we need some specific routines that show - * that certain coin operations are indeed in conflict, - * for example that the coin is of a different denomination - * or different age restrictions. - * This relates to unimplemented error handling for - * coins in the exchange! - * - * Check that the provided @a proof indeeds indicates - * a conflict for @a coin_pub. - * - * @param keys exchange keys - * @param proof provided conflict proof - * @param dk denomination of @a coin_pub that the client - * used - * @param coin_pub public key of the coin - * @param required balance required on the coin for the operation - * @return #GNUNET_OK if @a proof holds - */ -// FIXME-#9422: should be properly defined and implemented! -enum GNUNET_GenericReturnValue -TALER_EXCHANGE_check_coin_conflict_ ( - const struct TALER_EXCHANGE_Keys *keys, - const json_t *proof, - const struct TALER_EXCHANGE_DenomPublicKey *dk, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - const struct TALER_Amount *required) -{ - enum TALER_ErrorCode ec; - - ec = TALER_JSON_get_error_code (proof); - switch (ec) - { - case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS: - /* Nothing to check anymore here, proof needs to be - checked in the GET /coins/$COIN_PUB handler */ - break; - case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY: - // FIXME-#9422: write check! - break; - default: - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_OK; -} - - -#endif - - -/* end of exchange_api_coins_history.c */ diff --git a/src/lib/exchange_api_contracts_get.c b/src/lib/exchange_api_contracts_get.c @@ -1,262 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2022 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_contracts_get.c - * @brief Implementation of the /contracts/ GET request - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_json_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_json_lib.h" -#include "taler/taler_exchange_service.h" -#include "exchange_api_handle.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * @brief A Contract Get Handle - */ -struct TALER_EXCHANGE_ContractsGetHandle -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_ContractGetCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Private key needed to decrypt the contract. - */ - struct TALER_ContractDiffiePrivateP contract_priv; - - /** - * Public key matching @e contract_priv. - */ - struct TALER_ContractDiffiePublicP cpub; - -}; - - -/** - * Function called when we're done processing the - * HTTP /track/transaction request. - * - * @param cls the `struct TALER_EXCHANGE_ContractsGetHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_contract_get_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_ContractsGetHandle *cgh = cls; - const json_t *j = response; - struct TALER_EXCHANGE_ContractGetResponse dr = { - .hr.reply = j, - .hr.http_status = (unsigned int) response_code - }; - - cgh->job = NULL; - switch (response_code) - { - case 0: - dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - { - void *econtract; - size_t econtract_size; - struct TALER_PurseContractSignatureP econtract_sig; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("purse_pub", - &dr.details.ok.purse_pub), - GNUNET_JSON_spec_fixed_auto ("econtract_sig", - &econtract_sig), - GNUNET_JSON_spec_varsize ("econtract", - &econtract, - &econtract_size), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - if (GNUNET_OK != - TALER_wallet_econtract_upload_verify ( - econtract, - econtract_size, - &cgh->cpub, - &dr.details.ok.purse_pub, - &econtract_sig)) - { - GNUNET_break (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_EXCHANGE_CONTRACTS_SIGNATURE_INVALID; - GNUNET_JSON_parse_free (spec); - break; - } - dr.details.ok.econtract = econtract; - dr.details.ok.econtract_size = econtract_size; - cgh->cb (cgh->cb_cls, - &dr); - GNUNET_JSON_parse_free (spec); - TALER_EXCHANGE_contract_get_cancel (cgh); - return; - } - case MHD_HTTP_BAD_REQUEST: - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - break; - case MHD_HTTP_FORBIDDEN: - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - /* Nothing really to verify, exchange says one of the signatures is - invalid; as we checked them, this should never happen, we - should pass the JSON reply to the application */ - break; - case MHD_HTTP_NOT_FOUND: - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - /* Exchange does not know about transaction; - we should pass the reply to the application */ - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange GET contracts\n", - (unsigned int) response_code, - (int) dr.hr.ec); - GNUNET_break_op (0); - break; - } - cgh->cb (cgh->cb_cls, - &dr); - TALER_EXCHANGE_contract_get_cancel (cgh); -} - - -struct TALER_EXCHANGE_ContractsGetHandle * -TALER_EXCHANGE_contract_get ( - struct GNUNET_CURL_Context *ctx, - const char *url, - const struct TALER_ContractDiffiePrivateP *contract_priv, - TALER_EXCHANGE_ContractGetCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_ContractsGetHandle *cgh; - CURL *eh; - char arg_str[sizeof (cgh->cpub) * 2 + 48]; - - cgh = GNUNET_new (struct TALER_EXCHANGE_ContractsGetHandle); - cgh->cb = cb; - cgh->cb_cls = cb_cls; - GNUNET_CRYPTO_ecdhe_key_get_public (&contract_priv->ecdhe_priv, - &cgh->cpub.ecdhe_pub); - { - char cpub_str[sizeof (cgh->cpub) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string (&cgh->cpub, - sizeof (cgh->cpub), - cpub_str, - sizeof (cpub_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "contracts/%s", - cpub_str); - } - - cgh->url = TALER_url_join (url, - arg_str, - NULL); - if (NULL == cgh->url) - { - GNUNET_free (cgh); - return NULL; - } - cgh->contract_priv = *contract_priv; - - eh = TALER_EXCHANGE_curl_easy_get_ (cgh->url); - if (NULL == eh) - { - GNUNET_break (0); - GNUNET_free (cgh->url); - GNUNET_free (cgh); - return NULL; - } - cgh->job = GNUNET_CURL_job_add (ctx, - eh, - &handle_contract_get_finished, - cgh); - return cgh; -} - - -void -TALER_EXCHANGE_contract_get_cancel ( - struct TALER_EXCHANGE_ContractsGetHandle *cgh) -{ - if (NULL != cgh->job) - { - GNUNET_CURL_job_cancel (cgh->job); - cgh->job = NULL; - } - GNUNET_free (cgh->url); - GNUNET_free (cgh); -} - - -/* end of exchange_api_contracts_get.c */ diff --git a/src/lib/exchange_api_delete-purses-PURSE_PUB.c b/src/lib/exchange_api_delete-purses-PURSE_PUB.c @@ -0,0 +1,243 @@ +/* + This file is part of TALER + Copyright (C) 2022 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> + */ +/** + * @file lib/exchange_api_delete-purses-PURSE_PUB.c + * @brief Implementation of the client to delete a purse + * into an account + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_json_lib.h" +#include "taler/taler_exchange_service.h" +#include "exchange_api_handle.h" +#include "exchange_api_common.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A purse delete with deposit handle + */ +struct TALER_EXCHANGE_PurseDeleteHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_PurseDeleteCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Header with the purse_sig. + */ + struct curl_slist *xhdr; +}; + + +/** + * Function called when we're done processing the + * HTTP DELETE /purse/$PID request. + * + * @param cls the `struct TALER_EXCHANGE_PurseDeleteHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_purse_delete_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_PurseDeleteHandle *pdh = cls; + const json_t *j = response; + struct TALER_EXCHANGE_PurseDeleteResponse dr = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code + }; + + pdh->job = NULL; + switch (response_code) + { + case 0: + dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_FORBIDDEN: + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + /* Nothing really to verify, exchange says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + break; + case MHD_HTTP_CONFLICT: + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange deposit\n", + (unsigned int) response_code, + dr.hr.ec); + GNUNET_break_op (0); + break; + } + pdh->cb (pdh->cb_cls, + &dr); + TALER_EXCHANGE_purse_delete_cancel (pdh); +} + + +struct TALER_EXCHANGE_PurseDeleteHandle * +TALER_EXCHANGE_purse_delete ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const struct TALER_PurseContractPrivateKeyP *purse_priv, + TALER_EXCHANGE_PurseDeleteCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_PurseDeleteHandle *pdh; + CURL *eh; + struct TALER_PurseContractPublicKeyP purse_pub; + struct TALER_PurseContractSignatureP purse_sig; + char arg_str[sizeof (purse_pub) * 2 + 32]; + + pdh = GNUNET_new (struct TALER_EXCHANGE_PurseDeleteHandle); + pdh->cb = cb; + pdh->cb_cls = cb_cls; + GNUNET_CRYPTO_eddsa_key_get_public (&purse_priv->eddsa_priv, + &purse_pub.eddsa_pub); + { + char pub_str[sizeof (purse_pub) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string (&purse_pub, + sizeof (purse_pub), + pub_str, + sizeof (pub_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "purses/%s", + pub_str); + } + pdh->url = TALER_url_join (url, + arg_str, + NULL); + if (NULL == pdh->url) + { + GNUNET_break (0); + GNUNET_free (pdh); + return NULL; + } + TALER_wallet_purse_delete_sign (purse_priv, + &purse_sig); + { + char *delete_str; + char *xhdr; + + delete_str = + GNUNET_STRINGS_data_to_string_alloc (&purse_sig, + sizeof (purse_sig)); + GNUNET_asprintf (&xhdr, + "Taler-Purse-Signature: %s", + delete_str); + GNUNET_free (delete_str); + pdh->xhdr = curl_slist_append (NULL, + xhdr); + GNUNET_free (xhdr); + } + eh = TALER_EXCHANGE_curl_easy_get_ (pdh->url); + if (NULL == eh) + { + GNUNET_break (0); + curl_slist_free_all (pdh->xhdr); + GNUNET_free (pdh->url); + GNUNET_free (pdh); + return NULL; + } + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_CUSTOMREQUEST, + MHD_HTTP_METHOD_DELETE)); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "URL for purse delete: `%s'\n", + pdh->url); + pdh->job = GNUNET_CURL_job_add2 (ctx, + eh, + pdh->xhdr, + &handle_purse_delete_finished, + pdh); + return pdh; +} + + +void +TALER_EXCHANGE_purse_delete_cancel ( + struct TALER_EXCHANGE_PurseDeleteHandle *pdh) +{ + if (NULL != pdh->job) + { + GNUNET_CURL_job_cancel (pdh->job); + pdh->job = NULL; + } + curl_slist_free_all (pdh->xhdr); + GNUNET_free (pdh->url); + GNUNET_free (pdh); +} + + +/* end of exchange_api_purse_delete.c */ diff --git a/src/lib/exchange_api_deposits_get.c b/src/lib/exchange_api_deposits_get.c @@ -1,411 +0,0 @@ -/* - This file is part of TALER - 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 General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_deposits_get.c - * @brief Implementation of the /deposits/ GET request - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_json_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_json_lib.h" -#include "taler/taler_exchange_service.h" -#include "exchange_api_handle.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * @brief A Deposit Get Handle - */ -struct TALER_EXCHANGE_DepositGetHandle -{ - - /** - * The keys of the this request handle will use - */ - struct TALER_EXCHANGE_Keys *keys; - - /** - * The url for this request. - */ - char *url; - - /** - * Context for #TEH_curl_easy_post(). Keeps the data that must - * persist for Curl to make the upload. - */ - struct TALER_CURL_PostContext ctx; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_DepositGetCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Hash over the wiring information of the merchant. - */ - struct TALER_MerchantWireHashP h_wire; - - /** - * Hash over the contract for which this deposit is made. - */ - struct TALER_PrivateContractHashP h_contract_terms; - - /** - * The coin's public key. This is the value that must have been - * signed (blindly) by the Exchange. - */ - struct TALER_CoinSpendPublicKeyP coin_pub; - -}; - - -/** - * Function called when we're done processing the - * HTTP /track/transaction request. - * - * @param cls the `struct TALER_EXCHANGE_DepositGetHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_deposit_wtid_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_DepositGetHandle *dwh = cls; - const json_t *j = response; - struct TALER_EXCHANGE_GetDepositResponse dr = { - .hr.reply = j, - .hr.http_status = (unsigned int) response_code - }; - - dwh->job = NULL; - switch (response_code) - { - case 0: - dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - { - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ( - "wtid", - &dr.details.ok.wtid), - GNUNET_JSON_spec_timestamp ( - "execution_time", - &dr.details.ok.execution_time), - TALER_JSON_spec_amount_any ( - "coin_contribution", - &dr.details.ok.coin_contribution), - GNUNET_JSON_spec_fixed_auto ( - "exchange_sig", - &dr.details.ok.exchange_sig), - GNUNET_JSON_spec_fixed_auto ( - "exchange_pub", - &dr.details.ok.exchange_pub), - GNUNET_JSON_spec_end () - }; - const struct TALER_EXCHANGE_Keys *key_state; - - key_state = dwh->keys; - GNUNET_assert (NULL != key_state); - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - if (GNUNET_OK != - TALER_EXCHANGE_test_signing_key (key_state, - &dr.details.ok.exchange_pub)) - { - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_SIGNATURE_BY_EXCHANGE; - break; - } - if (GNUNET_OK != - TALER_exchange_online_confirm_wire_verify ( - &dwh->h_wire, - &dwh->h_contract_terms, - &dr.details.ok.wtid, - &dwh->coin_pub, - dr.details.ok.execution_time, - &dr.details.ok.coin_contribution, - &dr.details.ok.exchange_pub, - &dr.details.ok.exchange_sig)) - { - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_SIGNATURE_BY_EXCHANGE; - break; - } - dwh->cb (dwh->cb_cls, - &dr); - TALER_EXCHANGE_deposits_get_cancel (dwh); - return; - } - case MHD_HTTP_ACCEPTED: - { - /* Transaction known, but not executed yet */ - bool no_legi = false; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_timestamp ( - "execution_time", - &dr.details.accepted.execution_time), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_fixed_auto ( - "account_pub", - &dr.details.accepted.account_pub), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_uint64 ( - "requirement_row", - &dr.details.accepted.requirement_row), - &no_legi), - GNUNET_JSON_spec_bool ( - "kyc_ok", - &dr.details.accepted.kyc_ok), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - if (no_legi) - dr.details.accepted.requirement_row = 0; - dwh->cb (dwh->cb_cls, - &dr); - TALER_EXCHANGE_deposits_get_cancel (dwh); - return; - } - case MHD_HTTP_BAD_REQUEST: - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - break; - case MHD_HTTP_FORBIDDEN: - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - /* Nothing really to verify, exchange says one of the signatures is - invalid; as we checked them, this should never happen, we - should pass the JSON reply to the application */ - break; - case MHD_HTTP_NOT_FOUND: - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - /* Exchange does not know about transaction; - we should pass the reply to the application */ - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange GET deposits\n", - (unsigned int) response_code, - (int) dr.hr.ec); - GNUNET_break_op (0); - break; - } - dwh->cb (dwh->cb_cls, - &dr); - TALER_EXCHANGE_deposits_get_cancel (dwh); -} - - -struct TALER_EXCHANGE_DepositGetHandle * -TALER_EXCHANGE_deposits_get ( - struct GNUNET_CURL_Context *ctx, - const char *url, - struct TALER_EXCHANGE_Keys *keys, - const struct TALER_MerchantPrivateKeyP *merchant_priv, - const struct TALER_MerchantWireHashP *h_wire, - const struct TALER_PrivateContractHashP *h_contract_terms, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - struct GNUNET_TIME_Relative timeout, - TALER_EXCHANGE_DepositGetCallback cb, - void *cb_cls) -{ - struct TALER_MerchantPublicKeyP merchant; - struct TALER_MerchantSignatureP merchant_sig; - struct TALER_EXCHANGE_DepositGetHandle *dwh; - CURL *eh; - char arg_str[(sizeof (struct TALER_CoinSpendPublicKeyP) - + sizeof (struct TALER_MerchantWireHashP) - + sizeof (struct TALER_MerchantPublicKeyP) - + sizeof (struct TALER_PrivateContractHashP) - + sizeof (struct TALER_MerchantSignatureP)) * 2 + 48]; - unsigned int tms - = (unsigned int) timeout.rel_value_us - / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us; - - GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv, - &merchant.eddsa_pub); - TALER_merchant_deposit_sign (h_contract_terms, - h_wire, - coin_pub, - merchant_priv, - &merchant_sig); - { - char cpub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2]; - char mpub_str[sizeof (struct TALER_MerchantPublicKeyP) * 2]; - char msig_str[sizeof (struct TALER_MerchantSignatureP) * 2]; - char chash_str[sizeof (struct TALER_PrivateContractHashP) * 2]; - char whash_str[sizeof (struct TALER_MerchantWireHashP) * 2]; - char timeout_str[24]; - char *end; - - end = GNUNET_STRINGS_data_to_string (h_wire, - sizeof (*h_wire), - whash_str, - sizeof (whash_str)); - *end = '\0'; - end = GNUNET_STRINGS_data_to_string (&merchant, - sizeof (merchant), - mpub_str, - sizeof (mpub_str)); - *end = '\0'; - end = GNUNET_STRINGS_data_to_string (h_contract_terms, - sizeof (*h_contract_terms), - chash_str, - sizeof (chash_str)); - *end = '\0'; - end = GNUNET_STRINGS_data_to_string (coin_pub, - sizeof (*coin_pub), - cpub_str, - sizeof (cpub_str)); - *end = '\0'; - end = GNUNET_STRINGS_data_to_string (&merchant_sig, - sizeof (merchant_sig), - msig_str, - sizeof (msig_str)); - *end = '\0'; - if (GNUNET_TIME_relative_is_zero (timeout)) - { - timeout_str[0] = '\0'; - } - else - { - GNUNET_snprintf ( - timeout_str, - sizeof (timeout_str), - "%u", - tms); - } - - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "deposits/%s/%s/%s/%s?merchant_sig=%s%s%s", - whash_str, - mpub_str, - chash_str, - cpub_str, - msig_str, - 0 == tms - ? "" - : "&timeout_ms=", - timeout_str); - } - - dwh = GNUNET_new (struct TALER_EXCHANGE_DepositGetHandle); - dwh->cb = cb; - dwh->cb_cls = cb_cls; - dwh->url = TALER_url_join (url, - arg_str, - NULL); - if (NULL == dwh->url) - { - GNUNET_free (dwh); - return NULL; - } - dwh->h_wire = *h_wire; - dwh->h_contract_terms = *h_contract_terms; - dwh->coin_pub = *coin_pub; - eh = TALER_EXCHANGE_curl_easy_get_ (dwh->url); - if (NULL == eh) - { - GNUNET_break (0); - GNUNET_free (dwh->url); - GNUNET_free (dwh); - return NULL; - } - if (0 != tms) - { - GNUNET_break (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_TIMEOUT_MS, - (long) (tms + 100L))); - } - dwh->job = GNUNET_CURL_job_add (ctx, - eh, - &handle_deposit_wtid_finished, - dwh); - dwh->keys = TALER_EXCHANGE_keys_incref (keys); - return dwh; -} - - -void -TALER_EXCHANGE_deposits_get_cancel (struct TALER_EXCHANGE_DepositGetHandle *dwh) -{ - if (NULL != dwh->job) - { - GNUNET_CURL_job_cancel (dwh->job); - dwh->job = NULL; - } - GNUNET_free (dwh->url); - TALER_curl_easy_post_finished (&dwh->ctx); - TALER_EXCHANGE_keys_decref (dwh->keys); - GNUNET_free (dwh); -} - - -/* end of exchange_api_deposits_get.c */ diff --git a/src/lib/exchange_api_get-aml-OFFICER_PUB-attributes-H_NORMALIZED_PAYTO.c b/src/lib/exchange_api_get-aml-OFFICER_PUB-attributes-H_NORMALIZED_PAYTO.c @@ -0,0 +1,386 @@ +/* + This file is part of TALER + Copyright (C) 2023, 2024 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_get-aml-OFFICER_PUB-attributes-H_NORMALIZED_PAYTO.c + * @brief Implementation of the /aml/$OFFICER_PUB/attributes request + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_exchange_service.h" +#include "taler/taler_json_lib.h" +#include "exchange_api_handle.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A GET /aml/$OFFICER_PUB/attributes Handle + */ +struct TALER_EXCHANGE_LookupKycAttributes +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_LookupKycAttributesCallback attributes_cb; + + /** + * Closure for @e cb. + */ + void *attributes_cb_cls; + + /** + * HTTP headers for the job. + */ + struct curl_slist *job_headers; + +}; + + +/** + * Parse AML decision summary array. + * + * @param[in,out] lh handle to use for allocations + * @param jdetails JSON array with AML decision summaries + * @param[out] detail_ar where to write the result + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +parse_kyc_attributes ( + struct TALER_EXCHANGE_LookupKycAttributes *lh, + const json_t *jdetails, + struct TALER_EXCHANGE_KycAttributeDetail *detail_ar) +{ + json_t *obj; + size_t idx; + + json_array_foreach (jdetails, idx, obj) + { + struct TALER_EXCHANGE_KycAttributeDetail *detail + = &detail_ar[idx]; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_uint64 ("rowid", + &detail->row_id), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("provider_name", + &detail->provider_name), + NULL), + GNUNET_JSON_spec_object_const ("attributes", + &detail->attributes), + GNUNET_JSON_spec_timestamp ("collection_time", + &detail->collection_time), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (obj, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + return GNUNET_OK; +} + + +/** + * Parse the provided decision data from the "200 OK" response. + * + * @param[in,out] lh handle (callback may be zero'ed out) + * @param json json reply with the data for one coin + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +static enum GNUNET_GenericReturnValue +parse_attributes_ok (struct TALER_EXCHANGE_LookupKycAttributes *lh, + const json_t *json) +{ + struct TALER_EXCHANGE_KycAttributesResponse lr = { + .hr.reply = json, + .hr.http_status = MHD_HTTP_OK + }; + const json_t *jdetails; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_array_const ("details", + &jdetails), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + lr.details.ok.kyc_attributes_length + = json_array_size (jdetails); + { + struct TALER_EXCHANGE_KycAttributeDetail details[ + GNUNET_NZL (lr.details.ok.kyc_attributes_length)]; + enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR; + + memset (details, + 0, + sizeof (details)); + lr.details.ok.kyc_attributes = details; + ret = parse_kyc_attributes (lh, + jdetails, + details); + if (GNUNET_OK == ret) + { + lh->attributes_cb (lh->attributes_cb_cls, + &lr); + lh->attributes_cb = NULL; + } + return ret; + } +} + + +/** + * Function called when we're done processing the + * HTTP /aml/$OFFICER_PUB/attributes request. + * + * @param cls the `struct TALER_EXCHANGE_LookupKycAttributes` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_lookup_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_LookupKycAttributes *lh = cls; + const json_t *j = response; + struct TALER_EXCHANGE_KycAttributesResponse lr = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code + }; + + lh->job = NULL; + switch (response_code) + { + case 0: + lr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + parse_attributes_ok (lh, + j)) + { + GNUNET_break_op (0); + lr.hr.http_status = 0; + lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + GNUNET_assert (NULL == lh->attributes_cb); + TALER_EXCHANGE_lookup_kyc_attributes_cancel (lh); + return; + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_BAD_REQUEST: + lr.hr.ec = TALER_JSON_get_error_code (j); + lr.hr.hint = TALER_JSON_get_error_hint (j); + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_FORBIDDEN: + lr.hr.ec = TALER_JSON_get_error_code (j); + lr.hr.hint = TALER_JSON_get_error_hint (j); + /* Nothing really to verify, exchange says this coin was not melted; we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + lr.hr.ec = TALER_JSON_get_error_code (j); + lr.hr.hint = TALER_JSON_get_error_hint (j); + /* Nothing really to verify, exchange says this coin was not melted; we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + lr.hr.ec = TALER_JSON_get_error_code (j); + lr.hr.hint = TALER_JSON_get_error_hint (j); + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + lr.hr.ec = TALER_JSON_get_error_code (j); + lr.hr.hint = TALER_JSON_get_error_hint (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for lookup AML attributes\n", + (unsigned int) response_code, + (int) lr.hr.ec); + break; + } + if (NULL != lh->attributes_cb) + lh->attributes_cb (lh->attributes_cb_cls, + &lr); + TALER_EXCHANGE_lookup_kyc_attributes_cancel (lh); +} + + +struct TALER_EXCHANGE_LookupKycAttributes * +TALER_EXCHANGE_lookup_kyc_attributes ( + struct GNUNET_CURL_Context *ctx, + const char *exchange_url, + const struct TALER_NormalizedPaytoHashP *h_payto, + uint64_t offset, + int64_t limit, + const struct TALER_AmlOfficerPrivateKeyP *officer_priv, + TALER_EXCHANGE_LookupKycAttributesCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_LookupKycAttributes *lh; + CURL *eh; + struct TALER_AmlOfficerPublicKeyP officer_pub; + struct TALER_AmlOfficerSignatureP officer_sig; + char arg_str[sizeof (officer_pub) * 2 + + sizeof (*h_payto) * 2 + + 32]; + + GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv, + &officer_pub.eddsa_pub); + TALER_officer_aml_query_sign (officer_priv, + &officer_sig); + + { + char payto_s[sizeof (*h_payto) * 2]; + char pub_str[sizeof (officer_pub) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string ( + h_payto, + sizeof (*h_payto), + payto_s, + sizeof (payto_s)); + *end = '\0'; + end = GNUNET_STRINGS_data_to_string ( + &officer_pub, + sizeof (officer_pub), + pub_str, + sizeof (pub_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "aml/%s/attributes/%s", + pub_str, + payto_s); + } + lh = GNUNET_new (struct TALER_EXCHANGE_LookupKycAttributes); + lh->attributes_cb = cb; + lh->attributes_cb_cls = cb_cls; + { + char limit_s[24]; + char offset_s[24]; + + GNUNET_snprintf (limit_s, + sizeof (limit_s), + "%lld", + (long long) limit); + GNUNET_snprintf (offset_s, + sizeof (offset_s), + "%llu", + (unsigned long long) offset); + lh->url = TALER_url_join ( + exchange_url, + arg_str, + "limit", + limit_s, + "offset", + offset_s, + "h_payto", + NULL); + } + if (NULL == lh->url) + { + GNUNET_free (lh); + return NULL; + } + eh = TALER_EXCHANGE_curl_easy_get_ (lh->url); + if (NULL == eh) + { + GNUNET_break (0); + GNUNET_free (lh->url); + GNUNET_free (lh); + return NULL; + } + { + char *hdr; + char sig_str[sizeof (officer_sig) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string ( + &officer_sig, + sizeof (officer_sig), + sig_str, + sizeof (sig_str)); + *end = '\0'; + + GNUNET_asprintf (&hdr, + "%s: %s", + TALER_AML_OFFICER_SIGNATURE_HEADER, + sig_str); + lh->job_headers = curl_slist_append (NULL, + hdr); + GNUNET_free (hdr); + lh->job_headers = curl_slist_append (lh->job_headers, + "Content-type: application/json"); + lh->job = GNUNET_CURL_job_add2 (ctx, + eh, + lh->job_headers, + &handle_lookup_finished, + lh); + } + return lh; +} + + +void +TALER_EXCHANGE_lookup_kyc_attributes_cancel ( + struct TALER_EXCHANGE_LookupKycAttributes *lh) +{ + if (NULL != lh->job) + { + GNUNET_CURL_job_cancel (lh->job); + lh->job = NULL; + } + curl_slist_free_all (lh->job_headers); + GNUNET_free (lh->url); + GNUNET_free (lh); +} + + +/* end of exchange_api_lookup_kyc_attributes.c */ diff --git a/src/lib/exchange_api_get-aml-OFFICER_PUB-decisions.c b/src/lib/exchange_api_get-aml-OFFICER_PUB-decisions.c @@ -0,0 +1,626 @@ +/* + This file is part of TALER + Copyright (C) 2023, 2024 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_get-aml-OFFICER_PUB-decisions.c + * @brief Implementation of the /aml/$OFFICER_PUB/decisions request + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_exchange_service.h" +#include "taler/taler_json_lib.h" +#include "exchange_api_handle.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A GET /aml/$OFFICER_PUB/decisions Handle + */ +struct TALER_EXCHANGE_LookupAmlDecisions +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_LookupAmlDecisionsCallback decisions_cb; + + /** + * Closure for @e cb. + */ + void *decisions_cb_cls; + + /** + * HTTP headers for the job. + */ + struct curl_slist *job_headers; + + /** + * Array with measure information. + */ + struct TALER_EXCHANGE_MeasureInformation *mip; + + /** + * Array with rule information. + */ + struct TALER_EXCHANGE_KycRule *rp; + + /** + * Array with all the measures (of all the rules!). + */ + const char **mp; +}; + + +/** + * Parse AML limits array. + * + * @param[in,out] lh handle to use for allocations + * @param jlimits JSON array with AML rules + * @param[out] ds where to write the result + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +parse_limits (struct TALER_EXCHANGE_LookupAmlDecisions *lh, + const json_t *jlimits, + struct TALER_EXCHANGE_AmlDecision *ds) +{ + struct TALER_EXCHANGE_LegitimizationRuleSet *limits + = &ds->limits; + const json_t *jrules; + const json_t *jmeasures; + size_t mip_len; + size_t rule_len; + size_t total; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_timestamp ("expiration_time", + &limits->expiration_time), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("successor_measure", + &limits->successor_measure), + NULL), + GNUNET_JSON_spec_array_const ("rules", + &jrules), + GNUNET_JSON_spec_object_const ("custom_measures", + &jmeasures), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (jlimits, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + mip_len = json_object_size (jmeasures); + lh->mip = GNUNET_new_array (mip_len, + struct TALER_EXCHANGE_MeasureInformation); + limits->measures = lh->mip; + limits->measures_length = mip_len; + + { + const char *measure_name; + const json_t *jmeasure; + + json_object_foreach ((json_t*) jmeasures, + measure_name, + jmeasure) + { + struct TALER_EXCHANGE_MeasureInformation *mi + = &lh->mip[--mip_len]; + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_string ("check_name", + &mi->check_name), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("prog_name", + &mi->prog_name), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_object_const ("context", + &mi->context), + NULL), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (jmeasure, + ispec, + NULL, + NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + mi->measure_name = measure_name; + } + } + + total = 0; + + { + const json_t *rule; + size_t idx; + + json_array_foreach ((json_t *) jrules, + idx, + rule) + { + total += json_array_size (json_object_get (rule, + "measures")); + } + } + + rule_len = json_array_size (jrules); + lh->rp = GNUNET_new_array (rule_len, + struct TALER_EXCHANGE_KycRule); + lh->mp = GNUNET_new_array (total, + const char *); + + { + const json_t *rule; + size_t idx; + + json_array_foreach ((json_t *) jrules, + idx, + rule) + { + const json_t *smeasures; + struct TALER_EXCHANGE_KycRule *r + = &lh->rp[--rule_len]; + struct GNUNET_JSON_Specification ispec[] = { + TALER_JSON_spec_kycte ("operation_type", + &r->operation_type), + TALER_JSON_spec_amount_any ("threshold", + &r->threshold), + GNUNET_JSON_spec_relative_time ("timeframe", + &r->timeframe), + GNUNET_JSON_spec_array_const ("measures", + &smeasures), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_bool ("exposed", + &r->exposed), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_bool ("is_and_combinator", + &r->is_and_combinator), + NULL), + GNUNET_JSON_spec_uint32 ("display_priority", + &r->display_priority), + GNUNET_JSON_spec_end () + }; + size_t mlen; + + if (GNUNET_OK != + GNUNET_JSON_parse (rule, + ispec, + NULL, + NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + mlen = json_array_size (smeasures); + GNUNET_assert (mlen <= total); + total -= mlen; + + { + size_t midx; + const json_t *smeasure; + + json_array_foreach (smeasures, + midx, + smeasure) + { + const char *sval = json_string_value (smeasure); + + if (NULL == sval) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + lh->mp[total + midx] = sval; + if (0 == strcasecmp (sval, + "verboten")) + r->verboten = true; + } + } + r->measures = &lh->mp[total]; + r->measures_length = r->verboten ? 0 : total; + } + } + return GNUNET_OK; +} + + +/** + * Parse AML decision summary array. + * + * @param[in,out] lh handle to use for allocations + * @param decisions JSON array with AML decision summaries + * @param[out] decision_ar where to write the result + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +parse_aml_decisions ( + struct TALER_EXCHANGE_LookupAmlDecisions *lh, + const json_t *decisions, + struct TALER_EXCHANGE_AmlDecision *decision_ar) +{ + json_t *obj; + size_t idx; + + json_array_foreach (decisions, idx, obj) + { + struct TALER_EXCHANGE_AmlDecision *decision = &decision_ar[idx]; + const json_t *jlimits; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("h_payto", + &decision->h_payto), + GNUNET_JSON_spec_uint64 ("rowid", + &decision->rowid), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("justification", + &decision->justification), + NULL), + GNUNET_JSON_spec_timestamp ("decision_time", + &decision->decision_time), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_object_const ("properties", + &decision->jproperties), + NULL), + GNUNET_JSON_spec_object_const ("limits", + &jlimits), + GNUNET_JSON_spec_bool ("to_investigate", + &decision->to_investigate), + GNUNET_JSON_spec_bool ("is_active", + &decision->is_active), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (obj, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + parse_limits (lh, + jlimits, + decision)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + return GNUNET_OK; +} + + +/** + * Parse the provided decision data from the "200 OK" response. + * + * @param[in,out] lh handle (callback may be zero'ed out) + * @param json json reply with the data for one coin + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +static enum GNUNET_GenericReturnValue +parse_decisions_ok (struct TALER_EXCHANGE_LookupAmlDecisions *lh, + const json_t *json) +{ + struct TALER_EXCHANGE_AmlDecisionsResponse lr = { + .hr.reply = json, + .hr.http_status = MHD_HTTP_OK + }; + const json_t *records; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_array_const ("records", + &records), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + lr.details.ok.decisions_length + = json_array_size (records); + { + struct TALER_EXCHANGE_AmlDecision decisions[ + GNUNET_NZL (lr.details.ok.decisions_length)]; + enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR; + + memset (decisions, + 0, + sizeof (decisions)); + lr.details.ok.decisions = decisions; + ret = parse_aml_decisions (lh, + records, + decisions); + if (GNUNET_OK == ret) + { + lh->decisions_cb (lh->decisions_cb_cls, + &lr); + lh->decisions_cb = NULL; + } + GNUNET_free (lh->mip); + GNUNET_free (lh->rp); + GNUNET_free (lh->mp); + return ret; + } +} + + +/** + * Function called when we're done processing the + * HTTP /aml/$OFFICER_PUB/decisions request. + * + * @param cls the `struct TALER_EXCHANGE_LookupAmlDecisions` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_lookup_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_LookupAmlDecisions *lh = cls; + const json_t *j = response; + struct TALER_EXCHANGE_AmlDecisionsResponse lr = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code + }; + + lh->job = NULL; + switch (response_code) + { + case 0: + lr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + parse_decisions_ok (lh, + j)) + { + GNUNET_break_op (0); + lr.hr.http_status = 0; + lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + GNUNET_assert (NULL == lh->decisions_cb); + TALER_EXCHANGE_lookup_aml_decisions_cancel (lh); + return; + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_BAD_REQUEST: + json_dumpf (j, + stderr, + JSON_INDENT (2)); + lr.hr.ec = TALER_JSON_get_error_code (j); + lr.hr.hint = TALER_JSON_get_error_hint (j); + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_FORBIDDEN: + lr.hr.ec = TALER_JSON_get_error_code (j); + lr.hr.hint = TALER_JSON_get_error_hint (j); + /* Nothing really to verify, exchange says this coin was not melted; we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + lr.hr.ec = TALER_JSON_get_error_code (j); + lr.hr.hint = TALER_JSON_get_error_hint (j); + /* Nothing really to verify, exchange says this coin was not melted; we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + lr.hr.ec = TALER_JSON_get_error_code (j); + lr.hr.hint = TALER_JSON_get_error_hint (j); + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + lr.hr.ec = TALER_JSON_get_error_code (j); + lr.hr.hint = TALER_JSON_get_error_hint (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for lookup AML decisions\n", + (unsigned int) response_code, + (int) lr.hr.ec); + break; + } + if (NULL != lh->decisions_cb) + lh->decisions_cb (lh->decisions_cb_cls, + &lr); + TALER_EXCHANGE_lookup_aml_decisions_cancel (lh); +} + + +struct TALER_EXCHANGE_LookupAmlDecisions * +TALER_EXCHANGE_lookup_aml_decisions ( + struct GNUNET_CURL_Context *ctx, + const char *exchange_url, + const struct TALER_NormalizedPaytoHashP *h_payto, + enum TALER_EXCHANGE_YesNoAll investigation_only, + enum TALER_EXCHANGE_YesNoAll active_only, + uint64_t offset, + int64_t limit, + const struct TALER_AmlOfficerPrivateKeyP *officer_priv, + TALER_EXCHANGE_LookupAmlDecisionsCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_LookupAmlDecisions *lh; + CURL *eh; + struct TALER_AmlOfficerPublicKeyP officer_pub; + struct TALER_AmlOfficerSignatureP officer_sig; + char arg_str[sizeof (struct TALER_AmlOfficerPublicKeyP) * 2 + + 32]; + + GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv, + &officer_pub.eddsa_pub); + TALER_officer_aml_query_sign (officer_priv, + &officer_sig); + { + char pub_str[sizeof (officer_pub) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string ( + &officer_pub, + sizeof (officer_pub), + pub_str, + sizeof (pub_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "aml/%s/decisions", + pub_str); + } + lh = GNUNET_new (struct TALER_EXCHANGE_LookupAmlDecisions); + lh->decisions_cb = cb; + lh->decisions_cb_cls = cb_cls; + { + char limit_s[24]; + char offset_s[24]; + char payto_s[sizeof (*h_payto) * 2]; + char *end; + + if (NULL != h_payto) + { + end = GNUNET_STRINGS_data_to_string ( + h_payto, + sizeof (*h_payto), + payto_s, + sizeof (payto_s)); + *end = '\0'; + } + GNUNET_snprintf (limit_s, + sizeof (limit_s), + "%lld", + (long long) limit); + GNUNET_snprintf (offset_s, + sizeof (offset_s), + "%llu", + (unsigned long long) offset); + lh->url = TALER_url_join ( + exchange_url, + arg_str, + "limit", + limit_s, + "offset", + ( ( (limit < 0) && (UINT64_MAX == offset) ) || + ( (limit > 0) && (0 == offset) ) ) + ? NULL + : offset_s, + "h_payto", + NULL != h_payto + ? payto_s + : NULL, + "active", + TALER_EXCHANGE_YNA_ALL != active_only + ? TALER_yna_to_string (active_only) + : NULL, + "investigation", + TALER_EXCHANGE_YNA_ALL != investigation_only + ? TALER_yna_to_string (investigation_only) + : NULL, + NULL); + } + if (NULL == lh->url) + { + GNUNET_free (lh); + return NULL; + } + eh = TALER_EXCHANGE_curl_easy_get_ (lh->url); + if (NULL == eh) + { + GNUNET_break (0); + GNUNET_free (lh->url); + GNUNET_free (lh); + return NULL; + } + { + char *hdr; + char sig_str[sizeof (officer_sig) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string ( + &officer_sig, + sizeof (officer_sig), + sig_str, + sizeof (sig_str)); + *end = '\0'; + + GNUNET_asprintf (&hdr, + "%s: %s", + TALER_AML_OFFICER_SIGNATURE_HEADER, + sig_str); + lh->job_headers = curl_slist_append (NULL, + hdr); + GNUNET_free (hdr); + lh->job_headers = curl_slist_append (lh->job_headers, + "Content-type: application/json"); + lh->job = GNUNET_CURL_job_add2 (ctx, + eh, + lh->job_headers, + &handle_lookup_finished, + lh); + } + return lh; +} + + +void +TALER_EXCHANGE_lookup_aml_decisions_cancel ( + struct TALER_EXCHANGE_LookupAmlDecisions *lh) +{ + if (NULL != lh->job) + { + GNUNET_CURL_job_cancel (lh->job); + lh->job = NULL; + } + curl_slist_free_all (lh->job_headers); + GNUNET_free (lh->url); + GNUNET_free (lh); +} + + +/* end of exchange_api_lookup_aml_decisions.c */ diff --git a/src/lib/exchange_api_get-aml-OFFICER_PUB-kyc-statistics-NAMES.c b/src/lib/exchange_api_get-aml-OFFICER_PUB-kyc-statistics-NAMES.c @@ -0,0 +1,317 @@ +/* + This file is part of TALER + Copyright (C) 2023, 2024 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_get-aml-OFFICER_PUB-kyc-statistics-NAMES.c + * @brief Implementation of the /aml/$OFFICER_PUB/kyc-statistics/$NAME request + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_exchange_service.h" +#include "taler/taler_json_lib.h" +#include "exchange_api_handle.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A GET /aml/$OFFICER_PUB/kyc-statistics/$NAME Handle + */ +struct TALER_EXCHANGE_KycGetStatisticsHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_KycStatisticsCallback stats_cb; + + /** + * Closure for @e cb. + */ + void *stats_cb_cls; + + /** + * HTTP headers for the job. + */ + struct curl_slist *job_headers; + +}; + + +/** + * Parse the provided decision data from the "200 OK" response. + * + * @param[in,out] lh handle (callback may be zero'ed out) + * @param json json reply with the data for one coin + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +static enum GNUNET_GenericReturnValue +parse_stats_ok ( + struct TALER_EXCHANGE_KycGetStatisticsHandle *lh, + const json_t *json) +{ + struct TALER_EXCHANGE_KycGetStatisticsResponse lr = { + .hr.reply = json, + .hr.http_status = MHD_HTTP_OK + }; + uint64_t cnt; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_uint64 ("counter", + &cnt), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + lr.details.ok.counter = (unsigned long long) cnt; + lh->stats_cb (lh->stats_cb_cls, + &lr); + lh->stats_cb = NULL; + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /aml/$OFFICER_PUB/kyc-statistics/$NAME request. + * + * @param cls the `struct TALER_EXCHANGE_KycGetStatisticsHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_lookup_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_KycGetStatisticsHandle *lh = cls; + const json_t *j = response; + struct TALER_EXCHANGE_KycGetStatisticsResponse lr = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code + }; + + lh->job = NULL; + switch (response_code) + { + case 0: + lr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + parse_stats_ok (lh, + j)) + { + GNUNET_break_op (0); + lr.hr.http_status = 0; + lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + GNUNET_assert (NULL == lh->stats_cb); + TALER_EXCHANGE_kyc_get_statistics_cancel (lh); + return; + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_BAD_REQUEST: + lr.hr.ec = TALER_JSON_get_error_code (j); + lr.hr.hint = TALER_JSON_get_error_hint (j); + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_FORBIDDEN: + lr.hr.ec = TALER_JSON_get_error_code (j); + lr.hr.hint = TALER_JSON_get_error_hint (j); + /* Nothing really to verify, exchange says this coin was not melted; we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + lr.hr.ec = TALER_JSON_get_error_code (j); + lr.hr.hint = TALER_JSON_get_error_hint (j); + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + lr.hr.ec = TALER_JSON_get_error_code (j); + lr.hr.hint = TALER_JSON_get_error_hint (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for GET KYC statistics\n", + (unsigned int) response_code, + (int) lr.hr.ec); + break; + } + if (NULL != lh->stats_cb) + lh->stats_cb (lh->stats_cb_cls, + &lr); + TALER_EXCHANGE_kyc_get_statistics_cancel (lh); +} + + +struct TALER_EXCHANGE_KycGetStatisticsHandle * +TALER_EXCHANGE_kyc_get_statistics ( + struct GNUNET_CURL_Context *ctx, + const char *exchange_url, + const char *name, + struct GNUNET_TIME_Timestamp start_date, + struct GNUNET_TIME_Timestamp end_date, + const struct TALER_AmlOfficerPrivateKeyP *officer_priv, + TALER_EXCHANGE_KycStatisticsCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_KycGetStatisticsHandle *lh; + CURL *eh; + struct TALER_AmlOfficerPublicKeyP officer_pub; + struct TALER_AmlOfficerSignatureP officer_sig; + char arg_str[sizeof (struct TALER_AmlOfficerPublicKeyP) * 2 + + 32]; + char sd_str[32]; + char ed_str[32]; + const char *sd = NULL; + const char *ed = NULL; + + if (! GNUNET_TIME_absolute_is_zero (start_date.abs_time)) + { + unsigned long long sec; + + sec = (unsigned long long) GNUNET_TIME_timestamp_to_s (start_date); + GNUNET_snprintf (sd_str, + sizeof (sd_str), + "%llu", + sec); + sd = sd_str; + } + if (! GNUNET_TIME_absolute_is_never (end_date.abs_time)) + { + unsigned long long sec; + + sec = (unsigned long long) GNUNET_TIME_timestamp_to_s (end_date); + GNUNET_snprintf (ed_str, + sizeof (ed_str), + "%llu", + sec); + ed = ed_str; + } + GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv, + &officer_pub.eddsa_pub); + TALER_officer_aml_query_sign (officer_priv, + &officer_sig); + { + char pub_str[sizeof (officer_pub) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string ( + &officer_pub, + sizeof (officer_pub), + pub_str, + sizeof (pub_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "aml/%s/kyc-statistics/%s", + pub_str, + name); + } + lh = GNUNET_new (struct TALER_EXCHANGE_KycGetStatisticsHandle); + lh->stats_cb = cb; + lh->stats_cb_cls = cb_cls; + lh->url = TALER_url_join (exchange_url, + arg_str, + "start_date", + sd, + "end_date", + ed, + NULL); + if (NULL == lh->url) + { + GNUNET_free (lh); + return NULL; + } + eh = TALER_EXCHANGE_curl_easy_get_ (lh->url); + if (NULL == eh) + { + GNUNET_break (0); + GNUNET_free (lh->url); + GNUNET_free (lh); + return NULL; + } + { + char *hdr; + char sig_str[sizeof (officer_sig) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string ( + &officer_sig, + sizeof (officer_sig), + sig_str, + sizeof (sig_str)); + *end = '\0'; + + GNUNET_asprintf (&hdr, + "%s: %s", + TALER_AML_OFFICER_SIGNATURE_HEADER, + sig_str); + lh->job_headers = curl_slist_append (NULL, + hdr); + GNUNET_free (hdr); + lh->job_headers = curl_slist_append (lh->job_headers, + "Content-type: application/json"); + lh->job = GNUNET_CURL_job_add2 (ctx, + eh, + lh->job_headers, + &handle_lookup_finished, + lh); + } + return lh; +} + + +void +TALER_EXCHANGE_kyc_get_statistics_cancel ( + struct TALER_EXCHANGE_KycGetStatisticsHandle *lh) +{ + if (NULL != lh->job) + { + GNUNET_CURL_job_cancel (lh->job); + lh->job = NULL; + } + curl_slist_free_all (lh->job_headers); + GNUNET_free (lh->url); + GNUNET_free (lh); +} + + +/* end of exchange_api_get_kyc_statistics.c */ diff --git a/src/lib/exchange_api_get-aml-OFFICER_PUB-legitimizations.c b/src/lib/exchange_api_get-aml-OFFICER_PUB-legitimizations.c @@ -0,0 +1,510 @@ +/* + This file is part of TALER + Copyright (C) 2025 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_get-aml-OFFICER_PUB-legitimizations.c + * @brief Implementation of the GET /aml/$OFFICER_PUB/legitimizations requests + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_exchange_service.h" +#include "taler/taler_json_lib.h" +#include "exchange_api_handle.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * Handle for an operation to GET /aml/$OFFICER_PUB/legitimizations. + */ +struct TALER_EXCHANGE_GetAmlLegitimizationsHandle +{ + + /** + * The exchange base URL for this request. + */ + char *exchange_base_url; + + /** + * Our execution context. + */ + struct GNUNET_CURL_Context *ctx; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Signature of the AML officer. + */ + struct TALER_AmlOfficerSignatureP officer_sig; + + /** + * Public key of the AML officer. + */ + struct TALER_AmlOfficerPublicKeyP officer_pub; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_GetAmlLegitimizationsCallback cb; + + /** + * Closure for @a cb. + */ + TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_RESULT_CLOSURE *cb_cls; + + /** + * The url for this request. + */ + char *url; + + /** + * HTTP headers for the job. + */ + struct curl_slist *job_headers; + + /** + * Request options. + */ + struct + { + /** + * Limit on number of results. + */ + int64_t limit; + + /** + * Row offset from which to return results. + */ + uint64_t offset; + + /** + * Hash of payto URI to filter by, NULL for no filter. + */ + const struct TALER_NormalizedPaytoHashP *h_payto; + + /** + * Activity filter. + */ + enum TALER_EXCHANGE_YesNoAll active; + + } options; + +}; + + +/** + * Parse a single measure details entry from JSON. + * + * @param md_json JSON object to parse + * @param[out] md where to store the result + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +parse_measure_details ( + const json_t *md_json, + struct TALER_EXCHANGE_GetAmlLegitimizationsMeasureDetails *md) +{ + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("h_payto", + &md->h_payto), + GNUNET_JSON_spec_uint64 ("rowid", + &md->rowid), + GNUNET_JSON_spec_timestamp ("start_time", + &md->start_time), + GNUNET_JSON_spec_object_const ("measures", + &md->measures), + GNUNET_JSON_spec_bool ("is_finished", + &md->is_finished), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (md_json, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * We received an #MHD_HTTP_OK status code. Handle the JSON + * response. + * + * @param algh handle of the request + * @param j JSON response + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +handle_aml_legitimizations_get_ok ( + struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh, + const json_t *j) +{ + struct TALER_EXCHANGE_GetAmlLegitimizationsResponse result = { + .hr.reply = j, + .hr.http_status = MHD_HTTP_OK + }; + const json_t *measures_array; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_array_const ("measures", + &measures_array), + GNUNET_JSON_spec_end () + }; + struct TALER_EXCHANGE_GetAmlLegitimizationsMeasureDetails *measures; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + result.details.ok.measures_length = json_array_size (measures_array); + if (0 == result.details.ok.measures_length) + { + measures = NULL; + } + else + { + measures + = GNUNET_new_array ( + result.details.ok.measures_length, + struct TALER_EXCHANGE_GetAmlLegitimizationsMeasureDetails); + } + for (size_t i = 0; i < result.details.ok.measures_length; i++) + { + const json_t *measure_json = json_array_get (measures_array, + i); + + if (GNUNET_OK != + parse_measure_details (measure_json, + &measures[i])) + { + GNUNET_free (measures); + return GNUNET_SYSERR; + } + } + result.details.ok.measures = measures; + algh->cb (algh->cb_cls, + &result); + algh->cb = NULL; + GNUNET_free (measures); + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /aml/$OFFICER_PUB/legitimizations GET request. + * + * @param cls the `struct TALER_EXCHANGE_GetAmlLegitimizationsHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_aml_legitimizations_get_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh = cls; + const json_t *j = response; + struct TALER_EXCHANGE_GetAmlLegitimizationsResponse result = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code + }; + + algh->job = NULL; + switch (response_code) + { + case 0: + result.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + handle_aml_legitimizations_get_ok (algh, + j)) + { + result.hr.http_status = 0; + result.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + } + break; + case MHD_HTTP_NO_CONTENT: + /* can happen */ + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + result.hr.ec = TALER_JSON_get_error_code (j); + result.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_UNAUTHORIZED: + /* Invalid officer credentials */ + result.hr.ec = TALER_JSON_get_error_code (j); + result.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_FORBIDDEN: + /* Officer not authorized for this operation */ + result.hr.ec = TALER_JSON_get_error_code (j); + result.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_NOT_FOUND: + /* Officer not found */ + result.hr.ec = TALER_JSON_get_error_code (j); + result.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + result.hr.ec = TALER_JSON_get_error_code (j); + result.hr.hint = TALER_JSON_get_error_hint (j); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + result.hr.ec = TALER_JSON_get_error_code (j); + result.hr.hint = TALER_JSON_get_error_hint (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for GET %s\n", + (unsigned int) response_code, + (int) result.hr.ec, + algh->url); + break; + } + if (NULL != algh->cb) + { + algh->cb (algh->cb_cls, + &result); + algh->cb = NULL; + } + TALER_EXCHANGE_get_aml_legitimizations_cancel (algh); +} + + +struct TALER_EXCHANGE_GetAmlLegitimizationsHandle * +TALER_EXCHANGE_get_aml_legitimizations_create ( + struct GNUNET_CURL_Context *ctx, + const char *exchange_base_url, + const struct TALER_AmlOfficerPrivateKeyP *officer_priv) +{ + struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh; + + algh = GNUNET_new (struct TALER_EXCHANGE_GetAmlLegitimizationsHandle); + algh->ctx = ctx; + algh->exchange_base_url = GNUNET_strdup (exchange_base_url); + GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv, + &algh->officer_pub.eddsa_pub); + TALER_officer_aml_query_sign (officer_priv, + &algh->officer_sig); + algh->options.limit = -20; /* Default to last 20 entries */ + algh->options.offset = INT64_MAX; /* Default to maximum row id */ + algh->options.active = TALER_EXCHANGE_YNA_ALL; /* Default to all */ + return algh; +} + + +enum GNUNET_GenericReturnValue +TALER_EXCHANGE_get_aml_legitimizations_set_options_ ( + struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh, + unsigned int num_options, + const struct TALER_EXCHANGE_GetAmlLegitimizationsOptionValue *options) +{ + for (unsigned int i = 0; i < num_options; i++) + { + switch (options[i].option) + { + case TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_OPTION_END: + return GNUNET_OK; + case TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_OPTION_LIMIT: + algh->options.limit = options[i].details.limit; + break; + case TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_OPTION_OFFSET: + algh->options.offset = options[i].details.offset; + break; + case TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_OPTION_H_PAYTO: + algh->options.h_payto = options[i].details.h_payto; + break; + case TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_OPTION_ACTIVE: + algh->options.active = options[i].details.active; + break; + default: + GNUNET_break (0); + return GNUNET_NO; + } + } + return GNUNET_OK; +} + + +enum TALER_EXCHANGE_AmlLegitimizationsGetStartError +TALER_EXCHANGE_get_aml_legitimizations_start ( + struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh, + TALER_EXCHANGE_GetAmlLegitimizationsCallback cb, + TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_RESULT_CLOSURE *cb_cls) +{ + char officer_pub_str[sizeof (struct TALER_AmlOfficerPublicKeyP) * 2]; + char arg_str[sizeof (officer_pub_str) + 64]; + char limit_str[24]; + char offset_str[24]; + char paytoh_str[sizeof (struct TALER_NormalizedPaytoHashP) * 2]; + + if (NULL != algh->job) + { + GNUNET_break (0); + return TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_START_E_AGAIN; + } + algh->cb = cb; + algh->cb_cls = cb_cls; + if (algh->options.offset > INT64_MAX) + { + GNUNET_break (0); + algh->options.offset = INT64_MAX; + } + { + char *end; + + end = GNUNET_STRINGS_data_to_string ( + &algh->officer_pub, + sizeof (algh->officer_pub), + officer_pub_str, + sizeof (officer_pub_str)); + *end = '\0'; + } + if (NULL != algh->options.h_payto) + { + char *end; + + end = GNUNET_STRINGS_data_to_string ( + algh->options.h_payto, + sizeof (struct TALER_NormalizedPaytoHashP), + paytoh_str, + sizeof (paytoh_str)); + *end = '\0'; + } + /* Build query parameters */ + GNUNET_snprintf (offset_str, + sizeof (offset_str), + "%llu", + (unsigned long long) algh->options.offset); + GNUNET_snprintf (limit_str, + sizeof (limit_str), + "%lld", + (long long) algh->options.limit); + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "aml/%s/legitimizations", + officer_pub_str); + algh->url = TALER_url_join (algh->exchange_base_url, + arg_str, + "limit", + limit_str, + "offset", + ( (algh->options.limit > 0) && + (0 == algh->options.offset) ) || + ( (algh->options.limit <= 0) && + (INT64_MAX <= algh->options.offset) ) + ? NULL + : offset_str, + "h_payto", + NULL == algh->options.h_payto + ? NULL + : paytoh_str, + "active", + TALER_EXCHANGE_YNA_ALL == algh->options.active + ? NULL + : TALER_yna_to_string (algh->options.active), + NULL); + if (NULL == algh->url) + { + GNUNET_break (0); + return TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_START_E_INTERNAL; + } + + { + CURL *eh; + + eh = TALER_EXCHANGE_curl_easy_get_ (algh->url); + if (NULL == eh) + { + GNUNET_break (0); + GNUNET_free (algh->url); + algh->url = NULL; + return TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_START_E_INTERNAL; + } + + /* Add authentication header for AML officer */ + { + char *hdr; + char sig_str[sizeof (algh->officer_sig) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string ( + &algh->officer_sig, + sizeof (algh->officer_sig), + sig_str, + sizeof (sig_str)); + *end = '\0'; + GNUNET_asprintf (&hdr, + "%s: %s", + TALER_AML_OFFICER_SIGNATURE_HEADER, + sig_str); + algh->job_headers = curl_slist_append (NULL, + hdr); + GNUNET_free (hdr); + } + algh->job + = GNUNET_CURL_job_add2 ( + algh->ctx, + eh, + algh->job_headers, + &handle_aml_legitimizations_get_finished, + algh); + } + return TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_START_OK; +} + + +void +TALER_EXCHANGE_get_aml_legitimizations_cancel ( + struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh) +{ + if (NULL != algh->job) + { + GNUNET_CURL_job_cancel (algh->job); + algh->job = NULL; + } + curl_slist_free_all (algh->job_headers); + GNUNET_free (algh->exchange_base_url); + GNUNET_free (algh->url); + GNUNET_free (algh); +} + + +/* end of exchange_api_aml_legitimizations_get.c */ diff --git a/src/lib/exchange_api_get-aml-OFFICER_PUB-measures.c b/src/lib/exchange_api_get-aml-OFFICER_PUB-measures.c @@ -0,0 +1,650 @@ +/* + This file is part of TALER + Copyright (C) 2023, 2024 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_get-aml-OFFICER_PUB-measures.c + * @brief Implementation of the GET /aml/$OFFICER_PUB/measures request + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_exchange_service.h" +#include "taler/taler_json_lib.h" +#include "exchange_api_handle.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * Scrap buffer of temporary arrays. + */ +struct Scrap +{ + /** + * Kept in DLL. + */ + struct Scrap *next; + + /** + * Kept in DLL. + */ + struct Scrap *prev; + + /** + * Pointer to our allocation. + */ + const char **ptr; +}; + + +/** + * @brief A GET /aml/$OFFICER_PUB/measures Handle + */ +struct TALER_EXCHANGE_AmlGetMeasuresHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_AmlMeasuresCallback measures_cb; + + /** + * Closure for @e measures_cb. + */ + void *measures_cb_cls; + + /** + * HTTP headers for the job. + */ + struct curl_slist *job_headers; + + /** + * Head of scrap list. + */ + struct Scrap *scrap_head; + + /** + * Tail of scrap list. + */ + struct Scrap *scrap_tail; +}; + + +/** + * Parse AML measures. + * + * @param jroots JSON object with measure data + * @param[out] roots where to write the result + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +parse_aml_roots ( + const json_t *jroots, + struct TALER_EXCHANGE_AvailableAmlMeasures *roots) +{ + const json_t *obj; + const char *name; + size_t idx = 0; + + json_object_foreach ((json_t *) jroots, name, obj) + { + struct TALER_EXCHANGE_AvailableAmlMeasures *root + = &roots[idx++]; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("check_name", + &root->check_name), + GNUNET_JSON_spec_string ("prog_name", + &root->prog_name), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_object_const ("context", + &root->context), + NULL), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (obj, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + root->measure_name = name; + } + return GNUNET_OK; +} + + +/** + * Create array of length @a len in scrap book. + * + * @param[in,out] lh context for allocations + * @param len length of array + * @return scrap array + */ +static const char ** +make_scrap ( + struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh, + unsigned int len) +{ + struct Scrap *s = GNUNET_new (struct Scrap); + + s->ptr = GNUNET_new_array (len, + const char *); + GNUNET_CONTAINER_DLL_insert (lh->scrap_head, + lh->scrap_tail, + s); + return s->ptr; +} + + +/** + * Free all scrap space. + * + * @param[in,out] lh scrap context + */ +static void +free_scrap (struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh) +{ + struct Scrap *s; + + while (NULL != (s = lh->scrap_head)) + { + GNUNET_CONTAINER_DLL_remove (lh->scrap_head, + lh->scrap_tail, + s); + GNUNET_free (s->ptr); + GNUNET_free (s); + } +} + + +/** + * Convert JSON array of strings to string array. + * + * @param j JSON array to convert + * @param[out] a array to initialize + * @return true on success + */ +static bool +j_to_a (const json_t *j, + const char **a) +{ + const json_t *e; + size_t idx; + + json_array_foreach ((json_t *) j, idx, e) + { + if (NULL == (a[idx] = json_string_value (e))) + return false; + } + return true; +} + + +/** + * Parse AML programs. + * + * @param[in,out] lh context for allocations + * @param jprogs JSON object with program data + * @param[out] progs where to write the result + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +parse_aml_programs ( + struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh, + const json_t *jprogs, + struct TALER_EXCHANGE_AvailableAmlPrograms *progs) +{ + const json_t *obj; + const char *name; + size_t idx = 0; + + json_object_foreach ((json_t *) jprogs, name, obj) + { + struct TALER_EXCHANGE_AvailableAmlPrograms *prog + = &progs[idx++]; + const json_t *jcontext; + const json_t *jinputs; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("description", + &prog->description), + GNUNET_JSON_spec_array_const ("context", + &jcontext), + GNUNET_JSON_spec_array_const ("inputs", + &jinputs), + GNUNET_JSON_spec_end () + }; + unsigned int len; + const char **ptr; + + if (GNUNET_OK != + GNUNET_JSON_parse (obj, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + prog->prog_name = name; + prog->contexts_length + = (unsigned int) json_array_size (jcontext); + prog->inputs_length + = (unsigned int) json_array_size (jinputs); + len = prog->contexts_length + prog->inputs_length; + if ( ((unsigned long long) len) != + (unsigned long long) json_array_size (jcontext) + + (unsigned long long) json_array_size (jinputs) ) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + ptr = make_scrap (lh, + len); + prog->contexts = ptr; + if (! j_to_a (jcontext, + prog->contexts)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + prog->inputs = &ptr[prog->contexts_length]; + if (! j_to_a (jinputs, + prog->inputs)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + return GNUNET_OK; +} + + +/** + * Parse AML measures. + * + * @param[in,out] lh context for allocations + * @param jchecks JSON object with measure data + * @param[out] checks where to write the result + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +parse_aml_checks ( + struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh, + const json_t *jchecks, + struct TALER_EXCHANGE_AvailableKycChecks *checks) +{ + const json_t *obj; + const char *name; + size_t idx = 0; + + json_object_foreach ((json_t *) jchecks, name, obj) + { + struct TALER_EXCHANGE_AvailableKycChecks *check + = &checks[idx++]; + const json_t *jrequires; + const json_t *joutputs; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("description", + &check->description), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_object_const ("description_i18n", + &check->description_i18n), + NULL), + GNUNET_JSON_spec_array_const ("requires", + &jrequires), + GNUNET_JSON_spec_array_const ("outputs", + &joutputs), + GNUNET_JSON_spec_string ("fallback", + &check->fallback), + GNUNET_JSON_spec_end () + }; + unsigned int len; + const char **ptr; + + if (GNUNET_OK != + GNUNET_JSON_parse (obj, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + check->check_name = name; + + check->requires_length + = (unsigned int) json_array_size (jrequires); + check->outputs_length + = (unsigned int) json_array_size (joutputs); + len = check->requires_length + check->outputs_length; + if ( ((unsigned long long) len) != + (unsigned long long) json_array_size (jrequires) + + (unsigned long long) json_array_size (joutputs) ) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + ptr = make_scrap (lh, + len); + check->requires = ptr; + if (! j_to_a (jrequires, + check->requires)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + check->outputs = &ptr[check->requires_length]; + if (! j_to_a (joutputs, + check->outputs)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + return GNUNET_OK; +} + + +/** + * Parse the provided decision data from the "200 OK" response. + * + * @param[in,out] lh handle (callback may be zero'ed out) + * @param json json reply with the data for one coin + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +static enum GNUNET_GenericReturnValue +parse_measures_ok (struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh, + const json_t *json) +{ + struct TALER_EXCHANGE_AmlGetMeasuresResponse lr = { + .hr.reply = json, + .hr.http_status = MHD_HTTP_OK + }; + const json_t *jroots; + const json_t *jprograms; + const json_t *jchecks; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_object_const ("roots", + &jroots), + GNUNET_JSON_spec_object_const ("programs", + &jprograms), + GNUNET_JSON_spec_object_const ("checks", + &jchecks), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + lr.details.ok.roots_length + = (unsigned int) json_object_size (jroots); + lr.details.ok.programs_length + = (unsigned int) json_object_size (jprograms); + lr.details.ok.checks_length + = (unsigned int) json_object_size (jchecks); + if ( ( ((size_t) lr.details.ok.roots_length) + != json_object_size (jroots)) || + ( ((size_t) lr.details.ok.programs_length) + != json_object_size (jprograms)) || + ( ((size_t) lr.details.ok.checks_length) + != json_object_size (jchecks)) ) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + { + struct TALER_EXCHANGE_AvailableAmlMeasures roots[ + GNUNET_NZL (lr.details.ok.roots_length)]; + struct TALER_EXCHANGE_AvailableAmlPrograms progs[ + GNUNET_NZL (lr.details.ok.programs_length)]; + struct TALER_EXCHANGE_AvailableKycChecks checks[ + GNUNET_NZL (lr.details.ok.checks_length)]; + enum GNUNET_GenericReturnValue ret; + + memset (roots, + 0, + sizeof (roots)); + memset (progs, + 0, + sizeof (progs)); + memset (checks, + 0, + sizeof (checks)); + lr.details.ok.roots = roots; + lr.details.ok.programs = progs; + lr.details.ok.checks = checks; + ret = parse_aml_roots (jroots, + roots); + if (GNUNET_OK == ret) + ret = parse_aml_programs (lh, + jprograms, + progs); + if (GNUNET_OK == ret) + ret = parse_aml_checks (lh, + jchecks, + checks); + if (GNUNET_OK == ret) + { + lh->measures_cb (lh->measures_cb_cls, + &lr); + lh->measures_cb = NULL; + } + free_scrap (lh); + return ret; + } +} + + +/** + * Function called when we're done processing the + * HTTP /aml/$OFFICER_PUB/measures request. + * + * @param cls the `struct TALER_EXCHANGE_AmlGetMeasuresHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_lookup_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh = cls; + const json_t *j = response; + struct TALER_EXCHANGE_AmlGetMeasuresResponse lr = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code + }; + + lh->job = NULL; + switch (response_code) + { + case 0: + lr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + parse_measures_ok (lh, + j)) + { + GNUNET_break_op (0); + lr.hr.http_status = 0; + lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + GNUNET_assert (NULL == lh->measures_cb); + TALER_EXCHANGE_aml_get_measures_cancel (lh); + return; + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_BAD_REQUEST: + lr.hr.ec = TALER_JSON_get_error_code (j); + lr.hr.hint = TALER_JSON_get_error_hint (j); + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_FORBIDDEN: + lr.hr.ec = TALER_JSON_get_error_code (j); + lr.hr.hint = TALER_JSON_get_error_hint (j); + /* Nothing really to verify, exchange says this coin was not melted; we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + lr.hr.ec = TALER_JSON_get_error_code (j); + lr.hr.hint = TALER_JSON_get_error_hint (j); + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + lr.hr.ec = TALER_JSON_get_error_code (j); + lr.hr.hint = TALER_JSON_get_error_hint (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for get AML measures\n", + (unsigned int) response_code, + (int) lr.hr.ec); + break; + } + if (NULL != lh->measures_cb) + lh->measures_cb (lh->measures_cb_cls, + &lr); + TALER_EXCHANGE_aml_get_measures_cancel (lh); +} + + +struct TALER_EXCHANGE_AmlGetMeasuresHandle * +TALER_EXCHANGE_aml_get_measures ( + struct GNUNET_CURL_Context *ctx, + const char *exchange_url, + const struct TALER_AmlOfficerPrivateKeyP *officer_priv, + TALER_EXCHANGE_AmlMeasuresCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh; + CURL *eh; + struct TALER_AmlOfficerPublicKeyP officer_pub; + struct TALER_AmlOfficerSignatureP officer_sig; + char arg_str[sizeof (struct TALER_AmlOfficerPublicKeyP) * 2 + + 32]; + + GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv, + &officer_pub.eddsa_pub); + TALER_officer_aml_query_sign (officer_priv, + &officer_sig); + { + char pub_str[sizeof (officer_pub) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string ( + &officer_pub, + sizeof (officer_pub), + pub_str, + sizeof (pub_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "aml/%s/measures", + pub_str); + } + lh = GNUNET_new (struct TALER_EXCHANGE_AmlGetMeasuresHandle); + lh->measures_cb = cb; + lh->measures_cb_cls = cb_cls; + lh->url = TALER_url_join (exchange_url, + arg_str, + NULL); + if (NULL == lh->url) + { + GNUNET_free (lh); + return NULL; + } + eh = TALER_EXCHANGE_curl_easy_get_ (lh->url); + if (NULL == eh) + { + GNUNET_break (0); + GNUNET_free (lh->url); + GNUNET_free (lh); + return NULL; + } + { + char *hdr; + char sig_str[sizeof (officer_sig) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string ( + &officer_sig, + sizeof (officer_sig), + sig_str, + sizeof (sig_str)); + *end = '\0'; + + GNUNET_asprintf (&hdr, + "%s: %s", + TALER_AML_OFFICER_SIGNATURE_HEADER, + sig_str); + lh->job_headers = curl_slist_append (NULL, + hdr); + GNUNET_free (hdr); + lh->job_headers = curl_slist_append (lh->job_headers, + "Content-type: application/json"); + lh->job = GNUNET_CURL_job_add2 (ctx, + eh, + lh->job_headers, + &handle_lookup_finished, + lh); + } + return lh; +} + + +void +TALER_EXCHANGE_aml_get_measures_cancel ( + struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh) +{ + if (NULL != lh->job) + { + GNUNET_CURL_job_cancel (lh->job); + lh->job = NULL; + } + curl_slist_free_all (lh->job_headers); + GNUNET_free (lh->url); + GNUNET_free (lh); +} + + +/* end of exchange_api_get_aml_measures.c */ diff --git a/src/lib/exchange_api_get-coins-COIN_PUB-history.c b/src/lib/exchange_api_get-coins-COIN_PUB-history.c @@ -0,0 +1,1235 @@ +/* + This file is part of TALER + 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 General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_get-coins-COIN_PUB-history.c + * @brief Implementation of the POST /coins/$COIN_PUB/history requests + * @author Christian Grothoff + * + * NOTE: this is an incomplete draft, never finished! + */ +#include "taler/platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP history codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_exchange_service.h" +#include "taler/taler_json_lib.h" +#include "exchange_api_handle.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A /coins/$RID/history Handle + */ +struct TALER_EXCHANGE_CoinsHistoryHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Context for #TEH_curl_easy_post(). Keeps the data that must + * persist for Curl to make the upload. + */ + struct TALER_CURL_PostContext post_ctx; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_CoinsHistoryCallback cb; + + /** + * Public key of the coin we are querying. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + + /** + * Closure for @a cb. + */ + void *cb_cls; + +}; + + +/** + * Context for coin helpers. + */ +struct CoinHistoryParseContext +{ + + /** + * Keys of the exchange. + */ + struct TALER_EXCHANGE_Keys *keys; + + /** + * Denomination of the coin. + */ + const struct TALER_EXCHANGE_DenomPublicKey *dk; + + /** + * Our coin public key. + */ + const struct TALER_CoinSpendPublicKeyP *coin_pub; + + /** + * Where to sum up total refunds. + */ + struct TALER_Amount *total_in; + + /** + * Total amount encountered. + */ + struct TALER_Amount *total_out; + +}; + + +/** + * Signature of functions that operate on one of + * the coin's history entries. + * + * @param[in,out] pc overall context + * @param[out] rh where to write the history entry + * @param amount main amount of this operation + * @param transaction JSON details for the operation + * @return #GNUNET_SYSERR on error, + * #GNUNET_OK to add, #GNUNET_NO to subtract + */ +typedef enum GNUNET_GenericReturnValue +(*CoinCheckHelper)(struct CoinHistoryParseContext *pc, + struct TALER_EXCHANGE_CoinHistoryEntry *rh, + const struct TALER_Amount *amount, + json_t *transaction); + + +/** + * Handle deposit entry in the coin's history. + * + * @param[in,out] pc overall context + * @param[out] rh history entry to initialize + * @param amount main amount of this operation + * @param transaction JSON details for the operation + * @return #GNUNET_SYSERR on error, + * #GNUNET_OK to add, #GNUNET_NO to subtract + */ +static enum GNUNET_GenericReturnValue +help_deposit (struct CoinHistoryParseContext *pc, + struct TALER_EXCHANGE_CoinHistoryEntry *rh, + const struct TALER_Amount *amount, + json_t *transaction) +{ + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount_any ("deposit_fee", + &rh->details.deposit.deposit_fee), + GNUNET_JSON_spec_fixed_auto ("merchant_pub", + &rh->details.deposit.merchant_pub), + GNUNET_JSON_spec_timestamp ("timestamp", + &rh->details.deposit.wallet_timestamp), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_timestamp ("refund_deadline", + &rh->details.deposit.refund_deadline), + NULL), + GNUNET_JSON_spec_fixed_auto ("h_contract_terms", + &rh->details.deposit.h_contract_terms), + GNUNET_JSON_spec_fixed_auto ("h_wire", + &rh->details.deposit.h_wire), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("h_policy", + &rh->details.deposit.h_policy), + &rh->details.deposit.no_h_policy), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("wallet_data_hash", + &rh->details.deposit.wallet_data_hash), + &rh->details.deposit.no_wallet_data_hash), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("h_age_commitment", + &rh->details.deposit.hac), + &rh->details.deposit.no_hac), + GNUNET_JSON_spec_fixed_auto ("coin_sig", + &rh->details.deposit.sig), + GNUNET_JSON_spec_end () + }; + + rh->details.deposit.refund_deadline = GNUNET_TIME_UNIT_ZERO_TS; + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_wallet_deposit_verify ( + amount, + &rh->details.deposit.deposit_fee, + &rh->details.deposit.h_wire, + &rh->details.deposit.h_contract_terms, + rh->details.deposit.no_wallet_data_hash + ? NULL + : &rh->details.deposit.wallet_data_hash, + rh->details.deposit.no_hac + ? NULL + : &rh->details.deposit.hac, + rh->details.deposit.no_h_policy + ? NULL + : &rh->details.deposit.h_policy, + &pc->dk->h_key, + rh->details.deposit.wallet_timestamp, + &rh->details.deposit.merchant_pub, + rh->details.deposit.refund_deadline, + pc->coin_pub, + &rh->details.deposit.sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + /* check that deposit fee matches our expectations from /keys! */ + if ( (GNUNET_YES != + TALER_amount_cmp_currency (&rh->details.deposit.deposit_fee, + &pc->dk->fees.deposit)) || + (0 != + TALER_amount_cmp (&rh->details.deposit.deposit_fee, + &pc->dk->fees.deposit)) ) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_YES; +} + + +/** + * Handle melt entry in the coin's history. + * + * @param[in,out] pc overall context + * @param[out] rh history entry to initialize + * @param amount main amount of this operation + * @param transaction JSON details for the operation + * @return #GNUNET_SYSERR on error, + * #GNUNET_OK to add, #GNUNET_NO to subtract + */ +static enum GNUNET_GenericReturnValue +help_melt (struct CoinHistoryParseContext *pc, + struct TALER_EXCHANGE_CoinHistoryEntry *rh, + const struct TALER_Amount *amount, + json_t *transaction) +{ + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount_any ("melt_fee", + &rh->details.melt.melt_fee), + GNUNET_JSON_spec_fixed_auto ("rc", + &rh->details.melt.rc), + // FIXME: also return refresh_seed? + // FIXME: also return blinding_seed? + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("h_age_commitment", + &rh->details.melt.h_age_commitment), + &rh->details.melt.no_hac), + GNUNET_JSON_spec_fixed_auto ("coin_sig", + &rh->details.melt.sig), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + /* check that melt fee matches our expectations from /keys! */ + if ( (GNUNET_YES != + TALER_amount_cmp_currency (&rh->details.melt.melt_fee, + &pc->dk->fees.refresh)) || + (0 != + TALER_amount_cmp (&rh->details.melt.melt_fee, + &pc->dk->fees.refresh)) ) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_wallet_melt_verify ( + amount, + &rh->details.melt.melt_fee, + &rh->details.melt.rc, + &pc->dk->h_key, + rh->details.melt.no_hac + ? NULL + : &rh->details.melt.h_age_commitment, + pc->coin_pub, + &rh->details.melt.sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_YES; +} + + +/** + * Handle refund entry in the coin's history. + * + * @param[in,out] pc overall context + * @param[out] rh history entry to initialize + * @param amount main amount of this operation + * @param transaction JSON details for the operation + * @return #GNUNET_SYSERR on error, + * #GNUNET_OK to add, #GNUNET_NO to subtract + */ +static enum GNUNET_GenericReturnValue +help_refund (struct CoinHistoryParseContext *pc, + struct TALER_EXCHANGE_CoinHistoryEntry *rh, + const struct TALER_Amount *amount, + json_t *transaction) +{ + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount_any ("refund_fee", + &rh->details.refund.refund_fee), + GNUNET_JSON_spec_fixed_auto ("h_contract_terms", + &rh->details.refund.h_contract_terms), + GNUNET_JSON_spec_fixed_auto ("merchant_pub", + &rh->details.refund.merchant_pub), + GNUNET_JSON_spec_uint64 ("rtransaction_id", + &rh->details.refund.rtransaction_id), + GNUNET_JSON_spec_fixed_auto ("merchant_sig", + &rh->details.refund.sig), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (0 > + TALER_amount_add (&rh->details.refund.sig_amount, + &rh->details.refund.refund_fee, + amount)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_merchant_refund_verify (pc->coin_pub, + &rh->details.refund.h_contract_terms, + rh->details.refund.rtransaction_id, + &rh->details.refund.sig_amount, + &rh->details.refund.merchant_pub, + &rh->details.refund.sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + /* NOTE: theoretically, we could also check that the given + merchant_pub and h_contract_terms appear in the + history under deposits. However, there is really no benefit + for the exchange to lie here, so not checking is probably OK + (an auditor ought to check, though). Then again, we similarly + had no reason to check the merchant's signature (other than a + well-formendess check). */ + + /* check that refund fee matches our expectations from /keys! */ + if ( (GNUNET_YES != + TALER_amount_cmp_currency (&rh->details.refund.refund_fee, + &pc->dk->fees.refund)) || + (0 != + TALER_amount_cmp (&rh->details.refund.refund_fee, + &pc->dk->fees.refund)) ) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_NO; +} + + +/** + * Handle recoup entry in the coin's history. + * + * @param[in,out] pc overall context + * @param[out] rh history entry to initialize + * @param amount main amount of this operation + * @param transaction JSON details for the operation + * @return #GNUNET_SYSERR on error, + * #GNUNET_OK to add, #GNUNET_NO to subtract + */ +static enum GNUNET_GenericReturnValue +help_recoup (struct CoinHistoryParseContext *pc, + struct TALER_EXCHANGE_CoinHistoryEntry *rh, + const struct TALER_Amount *amount, + json_t *transaction) +{ + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &rh->details.recoup.exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &rh->details.recoup.exchange_pub), + GNUNET_JSON_spec_fixed_auto ("reserve_pub", + &rh->details.recoup.reserve_pub), + GNUNET_JSON_spec_fixed_auto ("coin_sig", + &rh->details.recoup.coin_sig), + GNUNET_JSON_spec_fixed_auto ("coin_blind", + &rh->details.recoup.coin_bks), + GNUNET_JSON_spec_timestamp ("timestamp", + &rh->details.recoup.timestamp), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_exchange_online_confirm_recoup_verify ( + rh->details.recoup.timestamp, + amount, + pc->coin_pub, + &rh->details.recoup.reserve_pub, + &rh->details.recoup.exchange_pub, + &rh->details.recoup.exchange_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_wallet_recoup_verify (&pc->dk->h_key, + &rh->details.recoup.coin_bks, + pc->coin_pub, + &rh->details.recoup.coin_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_YES; +} + + +/** + * Handle recoup-refresh entry in the coin's history. + * This is the coin that was subjected to a recoup, + * the value being credited to the old coin. + * + * @param[in,out] pc overall context + * @param[out] rh history entry to initialize + * @param amount main amount of this operation + * @param transaction JSON details for the operation + * @return #GNUNET_SYSERR on error, + * #GNUNET_OK to add, #GNUNET_NO to subtract + */ +static enum GNUNET_GenericReturnValue +help_recoup_refresh (struct CoinHistoryParseContext *pc, + struct TALER_EXCHANGE_CoinHistoryEntry *rh, + const struct TALER_Amount *amount, + json_t *transaction) +{ + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &rh->details.recoup_refresh.exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &rh->details.recoup_refresh.exchange_pub), + GNUNET_JSON_spec_fixed_auto ("coin_sig", + &rh->details.recoup_refresh.coin_sig), + GNUNET_JSON_spec_fixed_auto ("old_coin_pub", + &rh->details.recoup_refresh.old_coin_pub), + GNUNET_JSON_spec_fixed_auto ("coin_blind", + &rh->details.recoup_refresh.coin_bks), + GNUNET_JSON_spec_timestamp ("timestamp", + &rh->details.recoup_refresh.timestamp), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_exchange_online_confirm_recoup_refresh_verify ( + rh->details.recoup_refresh.timestamp, + amount, + pc->coin_pub, + &rh->details.recoup_refresh.old_coin_pub, + &rh->details.recoup_refresh.exchange_pub, + &rh->details.recoup_refresh.exchange_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_wallet_recoup_verify (&pc->dk->h_key, + &rh->details.recoup_refresh.coin_bks, + pc->coin_pub, + &rh->details.recoup_refresh.coin_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_YES; +} + + +/** + * Handle old coin recoup entry in the coin's history. + * This is the coin that was credited in a recoup, + * the value being credited to the this coin. + * + * @param[in,out] pc overall context + * @param[out] rh history entry to initialize + * @param amount main amount of this operation + * @param transaction JSON details for the operation + * @return #GNUNET_SYSERR on error, + * #GNUNET_OK to add, #GNUNET_NO to subtract + */ +static enum GNUNET_GenericReturnValue +help_old_coin_recoup (struct CoinHistoryParseContext *pc, + struct TALER_EXCHANGE_CoinHistoryEntry *rh, + const struct TALER_Amount *amount, + json_t *transaction) +{ + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &rh->details.old_coin_recoup.exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &rh->details.old_coin_recoup.exchange_pub), + GNUNET_JSON_spec_fixed_auto ("coin_pub", + &rh->details.old_coin_recoup.new_coin_pub), + GNUNET_JSON_spec_timestamp ("timestamp", + &rh->details.old_coin_recoup.timestamp), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_exchange_online_confirm_recoup_refresh_verify ( + rh->details.old_coin_recoup.timestamp, + amount, + &rh->details.old_coin_recoup.new_coin_pub, + pc->coin_pub, + &rh->details.old_coin_recoup.exchange_pub, + &rh->details.old_coin_recoup.exchange_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_NO; +} + + +/** + * Handle purse deposit entry in the coin's history. + * + * @param[in,out] pc overall context + * @param[out] rh history entry to initialize + * @param amount main amount of this operation + * @param transaction JSON details for the operation + * @return #GNUNET_SYSERR on error, + * #GNUNET_OK to add, #GNUNET_NO to subtract + */ +static enum GNUNET_GenericReturnValue +help_purse_deposit (struct CoinHistoryParseContext *pc, + struct TALER_EXCHANGE_CoinHistoryEntry *rh, + const struct TALER_Amount *amount, + json_t *transaction) +{ + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_web_url ("exchange_base_url", + &rh->details.purse_deposit.exchange_base_url), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("h_age_commitment", + &rh->details.purse_deposit.phac), + NULL), + // FIXME: return deposit_fee? + GNUNET_JSON_spec_fixed_auto ("purse_pub", + &rh->details.purse_deposit.purse_pub), + GNUNET_JSON_spec_bool ("refunded", + &rh->details.purse_deposit.refunded), + GNUNET_JSON_spec_fixed_auto ("coin_sig", + &rh->details.purse_deposit.coin_sig), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_wallet_purse_deposit_verify ( + rh->details.purse_deposit.exchange_base_url, + &rh->details.purse_deposit.purse_pub, + amount, + &pc->dk->h_key, + &rh->details.purse_deposit.phac, + pc->coin_pub, + &rh->details.purse_deposit.coin_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (rh->details.purse_deposit.refunded) + { + /* We wave the deposit fee. */ + if (0 > + TALER_amount_add (pc->total_in, + pc->total_in, + &pc->dk->fees.deposit)) + { + /* overflow in refund history? inconceivable! Bad exchange! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + return GNUNET_YES; +} + + +/** + * Handle purse refund entry in the coin's history. + * + * @param[in,out] pc overall context + * @param[out] rh history entry to initialize + * @param amount main amount of this operation + * @param transaction JSON details for the operation + * @return #GNUNET_SYSERR on error, + * #GNUNET_OK to add, #GNUNET_NO to subtract + */ +static enum GNUNET_GenericReturnValue +help_purse_refund (struct CoinHistoryParseContext *pc, + struct TALER_EXCHANGE_CoinHistoryEntry *rh, + const struct TALER_Amount *amount, + json_t *transaction) +{ + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount_any ("refund_fee", + &rh->details.purse_refund.refund_fee), + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &rh->details.purse_refund.exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &rh->details.purse_refund.exchange_pub), + GNUNET_JSON_spec_fixed_auto ("purse_pub", + &rh->details.purse_refund.purse_pub), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_exchange_online_purse_refund_verify ( + amount, + &rh->details.purse_refund.refund_fee, + pc->coin_pub, + &rh->details.purse_refund.purse_pub, + &rh->details.purse_refund.exchange_pub, + &rh->details.purse_refund.exchange_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if ( (GNUNET_YES != + TALER_amount_cmp_currency (&rh->details.purse_refund.refund_fee, + &pc->dk->fees.refund)) || + (0 != + TALER_amount_cmp (&rh->details.purse_refund.refund_fee, + &pc->dk->fees.refund)) ) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_NO; +} + + +/** + * Handle reserve deposit entry in the coin's history. + * + * @param[in,out] pc overall context + * @param[out] rh history entry to initialize + * @param amount main amount of this operation + * @param transaction JSON details for the operation + * @return #GNUNET_SYSERR on error, + * #GNUNET_OK to add, #GNUNET_NO to subtract + */ +static enum GNUNET_GenericReturnValue +help_reserve_open_deposit (struct CoinHistoryParseContext *pc, + struct TALER_EXCHANGE_CoinHistoryEntry *rh, + const struct TALER_Amount *amount, + json_t *transaction) +{ + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("reserve_sig", + &rh->details.reserve_open_deposit.reserve_sig), + GNUNET_JSON_spec_fixed_auto ("coin_sig", + &rh->details.reserve_open_deposit.coin_sig), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_wallet_reserve_open_deposit_verify ( + amount, + &rh->details.reserve_open_deposit.reserve_sig, + pc->coin_pub, + &rh->details.reserve_open_deposit.coin_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_YES; +} + + +enum GNUNET_GenericReturnValue +TALER_EXCHANGE_parse_coin_history ( + const struct TALER_EXCHANGE_Keys *keys, + const struct TALER_EXCHANGE_DenomPublicKey *dk, + const json_t *history, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + struct TALER_Amount *total_in, + struct TALER_Amount *total_out, + unsigned int rlen, + struct TALER_EXCHANGE_CoinHistoryEntry rhistory[static rlen]) +{ + const struct + { + const char *type; + CoinCheckHelper helper; + enum TALER_EXCHANGE_CoinTransactionType ctt; + } map[] = { + { "DEPOSIT", + &help_deposit, + TALER_EXCHANGE_CTT_DEPOSIT }, + { "MELT", + &help_melt, + TALER_EXCHANGE_CTT_MELT }, + { "REFUND", + &help_refund, + TALER_EXCHANGE_CTT_REFUND }, + { "RECOUP", + &help_recoup, + TALER_EXCHANGE_CTT_RECOUP }, + { "RECOUP-REFRESH", + &help_recoup_refresh, + TALER_EXCHANGE_CTT_RECOUP_REFRESH }, + { "OLD-COIN-RECOUP", + &help_old_coin_recoup, + TALER_EXCHANGE_CTT_OLD_COIN_RECOUP }, + { "PURSE-DEPOSIT", + &help_purse_deposit, + TALER_EXCHANGE_CTT_PURSE_DEPOSIT }, + { "PURSE-REFUND", + &help_purse_refund, + TALER_EXCHANGE_CTT_PURSE_REFUND }, + { "RESERVE-OPEN-DEPOSIT", + &help_reserve_open_deposit, + TALER_EXCHANGE_CTT_RESERVE_OPEN_DEPOSIT }, + { NULL, NULL, TALER_EXCHANGE_CTT_NONE } + }; + struct CoinHistoryParseContext pc = { + .dk = dk, + .coin_pub = coin_pub, + .total_out = total_out, + .total_in = total_in + }; + size_t len; + + if (NULL == history) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + len = json_array_size (history); + if (0 == len) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + *total_in = dk->value; + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (total_in->currency, + total_out)); + for (size_t off = 0; off<len; off++) + { + struct TALER_EXCHANGE_CoinHistoryEntry *rh = &rhistory[off]; + json_t *transaction = json_array_get (history, + off); + enum GNUNET_GenericReturnValue add; + const char *type; + struct GNUNET_JSON_Specification spec_glob[] = { + TALER_JSON_spec_amount_any ("amount", + &rh->amount), + GNUNET_JSON_spec_string ("type", + &type), + GNUNET_JSON_spec_uint64 ("history_offset", + &rh->history_offset), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + spec_glob, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_YES != + TALER_amount_cmp_currency (&rh->amount, + total_in)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Operation of type %s with amount %s\n", + type, + TALER_amount2s (&rh->amount)); + add = GNUNET_SYSERR; + for (unsigned int i = 0; NULL != map[i].type; i++) + { + if (0 == strcasecmp (type, + map[i].type)) + { + rh->type = map[i].ctt; + add = map[i].helper (&pc, + rh, + &rh->amount, + transaction); + break; + } + } + switch (add) + { + case GNUNET_SYSERR: + /* entry type not supported, new version on server? */ + rh->type = TALER_EXCHANGE_CTT_NONE; + GNUNET_break_op (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected type `%s' in response\n", + type); + return GNUNET_SYSERR; + case GNUNET_YES: + /* This amount should be debited from the coin */ + if (0 > + TALER_amount_add (total_out, + total_out, + &rh->amount)) + { + /* overflow in history already!? inconceivable! Bad exchange! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + break; + case GNUNET_NO: + /* This amount should be credited to the coin. */ + if (0 > + TALER_amount_add (total_in, + total_in, + &rh->amount)) + { + /* overflow in refund history? inconceivable! Bad exchange! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + break; + } /* end of switch(add) */ + } + return GNUNET_OK; +} + + +/** + * We received an #MHD_HTTP_OK history code. Handle the JSON + * response. + * + * @param rsh handle of the request + * @param j JSON response + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +handle_coins_history_ok (struct TALER_EXCHANGE_CoinsHistoryHandle *rsh, + const json_t *j) +{ + struct TALER_EXCHANGE_CoinHistory rs = { + .hr.reply = j, + .hr.http_status = MHD_HTTP_OK + }; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount_any ("balance", + &rs.details.ok.balance), + GNUNET_JSON_spec_fixed_auto ("h_denom_pub", + &rs.details.ok.h_denom_pub), + GNUNET_JSON_spec_array_const ("history", + &rs.details.ok.history), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (NULL != rsh->cb) + { + rsh->cb (rsh->cb_cls, + &rs); + rsh->cb = NULL; + } + GNUNET_JSON_parse_free (spec); + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /coins/$RID/history request. + * + * @param cls the `struct TALER_EXCHANGE_CoinsHistoryHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_coins_history_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_CoinsHistoryHandle *rsh = cls; + const json_t *j = response; + struct TALER_EXCHANGE_CoinHistory rs = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code + }; + + rsh->job = NULL; + switch (response_code) + { + case 0: + rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + handle_coins_history_ok (rsh, + j)) + { + rs.hr.http_status = 0; + rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + GNUNET_break (0); + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_FORBIDDEN: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + GNUNET_break (0); + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for coins history\n", + (unsigned int) response_code, + (int) rs.hr.ec); + break; + } + if (NULL != rsh->cb) + { + rsh->cb (rsh->cb_cls, + &rs); + rsh->cb = NULL; + } + TALER_EXCHANGE_coins_history_cancel (rsh); +} + + +struct TALER_EXCHANGE_CoinsHistoryHandle * +TALER_EXCHANGE_coins_history ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const struct TALER_CoinSpendPrivateKeyP *coin_priv, + uint64_t start_off, + TALER_EXCHANGE_CoinsHistoryCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_CoinsHistoryHandle *rsh; + CURL *eh; + char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 64]; + struct curl_slist *job_headers; + + rsh = GNUNET_new (struct TALER_EXCHANGE_CoinsHistoryHandle); + rsh->cb = cb; + rsh->cb_cls = cb_cls; + GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, + &rsh->coin_pub.eddsa_pub); + { + char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string ( + &rsh->coin_pub, + sizeof (rsh->coin_pub), + pub_str, + sizeof (pub_str)); + *end = '\0'; + if (0 != start_off) + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "coins/%s/history?start=%llu", + pub_str, + (unsigned long long) start_off); + else + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "coins/%s/history", + pub_str); + } + rsh->url = TALER_url_join (url, + arg_str, + NULL); + if (NULL == rsh->url) + { + GNUNET_free (rsh); + return NULL; + } + eh = TALER_EXCHANGE_curl_easy_get_ (rsh->url); + if (NULL == eh) + { + GNUNET_break (0); + GNUNET_free (rsh->url); + GNUNET_free (rsh); + return NULL; + } + + { + struct TALER_CoinSpendSignatureP coin_sig; + char *sig_hdr; + char *hdr; + + TALER_wallet_coin_history_sign (start_off, + coin_priv, + &coin_sig); + + sig_hdr = GNUNET_STRINGS_data_to_string_alloc ( + &coin_sig, + sizeof (coin_sig)); + GNUNET_asprintf (&hdr, + "%s: %s", + TALER_COIN_HISTORY_SIGNATURE_HEADER, + sig_hdr); + GNUNET_free (sig_hdr); + job_headers = curl_slist_append (NULL, + hdr); + GNUNET_free (hdr); + if (NULL == job_headers) + { + GNUNET_break (0); + curl_easy_cleanup (eh); + return NULL; + } + } + + rsh->job = GNUNET_CURL_job_add2 (ctx, + eh, + job_headers, + &handle_coins_history_finished, + rsh); + curl_slist_free_all (job_headers); + return rsh; +} + + +void +TALER_EXCHANGE_coins_history_cancel ( + struct TALER_EXCHANGE_CoinsHistoryHandle *rsh) +{ + if (NULL != rsh->job) + { + GNUNET_CURL_job_cancel (rsh->job); + rsh->job = NULL; + } + TALER_curl_easy_post_finished (&rsh->post_ctx); + GNUNET_free (rsh->url); + GNUNET_free (rsh); +} + + +/** + * Verify that @a coin_sig does NOT appear in the @a history of a coin's + * transactions and thus whatever transaction is authorized by @a coin_sig is + * a conflict with @a proof. + * + * @param history coin history to check + * @param coin_sig signature that must not be in @a history + * @return #GNUNET_OK if @a coin_sig is not in @a history + */ +enum GNUNET_GenericReturnValue +TALER_EXCHANGE_check_coin_signature_conflict ( + const json_t *history, + const struct TALER_CoinSpendSignatureP *coin_sig) +{ + size_t off; + json_t *entry; + + json_array_foreach (history, off, entry) + { + struct TALER_CoinSpendSignatureP cs; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("coin_sig", + &cs), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (entry, + spec, + NULL, NULL)) + continue; /* entry without coin signature */ + if (0 == + GNUNET_memcmp (&cs, + coin_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + return GNUNET_OK; +} + + +#if FIXME_IMPLEMENT /* #9422 */ +/** + * FIXME-Oec-#9422: we need some specific routines that show + * that certain coin operations are indeed in conflict, + * for example that the coin is of a different denomination + * or different age restrictions. + * This relates to unimplemented error handling for + * coins in the exchange! + * + * Check that the provided @a proof indeeds indicates + * a conflict for @a coin_pub. + * + * @param keys exchange keys + * @param proof provided conflict proof + * @param dk denomination of @a coin_pub that the client + * used + * @param coin_pub public key of the coin + * @param required balance required on the coin for the operation + * @return #GNUNET_OK if @a proof holds + */ +// FIXME-#9422: should be properly defined and implemented! +enum GNUNET_GenericReturnValue +TALER_EXCHANGE_check_coin_conflict_ ( + const struct TALER_EXCHANGE_Keys *keys, + const json_t *proof, + const struct TALER_EXCHANGE_DenomPublicKey *dk, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_Amount *required) +{ + enum TALER_ErrorCode ec; + + ec = TALER_JSON_get_error_code (proof); + switch (ec) + { + case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS: + /* Nothing to check anymore here, proof needs to be + checked in the GET /coins/$COIN_PUB handler */ + break; + case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY: + // FIXME-#9422: write check! + break; + default: + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +#endif + + +/* end of exchange_api_coins_history.c */ diff --git a/src/lib/exchange_api_get-contracts-CONTRACT_PUB.c b/src/lib/exchange_api_get-contracts-CONTRACT_PUB.c @@ -0,0 +1,262 @@ +/* + This file is part of TALER + Copyright (C) 2022 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_get-contracts-CONTRACT_PUB.c + * @brief Implementation of the /contracts/ GET request + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_json_lib.h" +#include "taler/taler_exchange_service.h" +#include "exchange_api_handle.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A Contract Get Handle + */ +struct TALER_EXCHANGE_ContractsGetHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_ContractGetCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Private key needed to decrypt the contract. + */ + struct TALER_ContractDiffiePrivateP contract_priv; + + /** + * Public key matching @e contract_priv. + */ + struct TALER_ContractDiffiePublicP cpub; + +}; + + +/** + * Function called when we're done processing the + * HTTP /track/transaction request. + * + * @param cls the `struct TALER_EXCHANGE_ContractsGetHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_contract_get_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_ContractsGetHandle *cgh = cls; + const json_t *j = response; + struct TALER_EXCHANGE_ContractGetResponse dr = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code + }; + + cgh->job = NULL; + switch (response_code) + { + case 0: + dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + { + void *econtract; + size_t econtract_size; + struct TALER_PurseContractSignatureP econtract_sig; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("purse_pub", + &dr.details.ok.purse_pub), + GNUNET_JSON_spec_fixed_auto ("econtract_sig", + &econtract_sig), + GNUNET_JSON_spec_varsize ("econtract", + &econtract, + &econtract_size), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + if (GNUNET_OK != + TALER_wallet_econtract_upload_verify ( + econtract, + econtract_size, + &cgh->cpub, + &dr.details.ok.purse_pub, + &econtract_sig)) + { + GNUNET_break (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_EXCHANGE_CONTRACTS_SIGNATURE_INVALID; + GNUNET_JSON_parse_free (spec); + break; + } + dr.details.ok.econtract = econtract; + dr.details.ok.econtract_size = econtract_size; + cgh->cb (cgh->cb_cls, + &dr); + GNUNET_JSON_parse_free (spec); + TALER_EXCHANGE_contract_get_cancel (cgh); + return; + } + case MHD_HTTP_BAD_REQUEST: + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_FORBIDDEN: + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + /* Nothing really to verify, exchange says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + /* Exchange does not know about transaction; + we should pass the reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange GET contracts\n", + (unsigned int) response_code, + (int) dr.hr.ec); + GNUNET_break_op (0); + break; + } + cgh->cb (cgh->cb_cls, + &dr); + TALER_EXCHANGE_contract_get_cancel (cgh); +} + + +struct TALER_EXCHANGE_ContractsGetHandle * +TALER_EXCHANGE_contract_get ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const struct TALER_ContractDiffiePrivateP *contract_priv, + TALER_EXCHANGE_ContractGetCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_ContractsGetHandle *cgh; + CURL *eh; + char arg_str[sizeof (cgh->cpub) * 2 + 48]; + + cgh = GNUNET_new (struct TALER_EXCHANGE_ContractsGetHandle); + cgh->cb = cb; + cgh->cb_cls = cb_cls; + GNUNET_CRYPTO_ecdhe_key_get_public (&contract_priv->ecdhe_priv, + &cgh->cpub.ecdhe_pub); + { + char cpub_str[sizeof (cgh->cpub) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string (&cgh->cpub, + sizeof (cgh->cpub), + cpub_str, + sizeof (cpub_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "contracts/%s", + cpub_str); + } + + cgh->url = TALER_url_join (url, + arg_str, + NULL); + if (NULL == cgh->url) + { + GNUNET_free (cgh); + return NULL; + } + cgh->contract_priv = *contract_priv; + + eh = TALER_EXCHANGE_curl_easy_get_ (cgh->url); + if (NULL == eh) + { + GNUNET_break (0); + GNUNET_free (cgh->url); + GNUNET_free (cgh); + return NULL; + } + cgh->job = GNUNET_CURL_job_add (ctx, + eh, + &handle_contract_get_finished, + cgh); + return cgh; +} + + +void +TALER_EXCHANGE_contract_get_cancel ( + struct TALER_EXCHANGE_ContractsGetHandle *cgh) +{ + if (NULL != cgh->job) + { + GNUNET_CURL_job_cancel (cgh->job); + cgh->job = NULL; + } + GNUNET_free (cgh->url); + GNUNET_free (cgh); +} + + +/* end of exchange_api_contracts_get.c */ diff --git a/src/lib/exchange_api_get-deposits-H_WIRE-MERCHANT_PUB-H_CONTRACT_TERMS-COIN_PUB.c b/src/lib/exchange_api_get-deposits-H_WIRE-MERCHANT_PUB-H_CONTRACT_TERMS-COIN_PUB.c @@ -0,0 +1,411 @@ +/* + This file is part of TALER + 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 General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_get-deposits-H_WIRE-MERCHANT_PUB-H_CONTRACT_TERMS-COIN_PUB.c + * @brief Implementation of the /deposits/ GET request + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_json_lib.h" +#include "taler/taler_exchange_service.h" +#include "exchange_api_handle.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A Deposit Get Handle + */ +struct TALER_EXCHANGE_DepositGetHandle +{ + + /** + * The keys of the this request handle will use + */ + struct TALER_EXCHANGE_Keys *keys; + + /** + * The url for this request. + */ + char *url; + + /** + * Context for #TEH_curl_easy_post(). Keeps the data that must + * persist for Curl to make the upload. + */ + struct TALER_CURL_PostContext ctx; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_DepositGetCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Hash over the wiring information of the merchant. + */ + struct TALER_MerchantWireHashP h_wire; + + /** + * Hash over the contract for which this deposit is made. + */ + struct TALER_PrivateContractHashP h_contract_terms; + + /** + * The coin's public key. This is the value that must have been + * signed (blindly) by the Exchange. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + +}; + + +/** + * Function called when we're done processing the + * HTTP /track/transaction request. + * + * @param cls the `struct TALER_EXCHANGE_DepositGetHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_deposit_wtid_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_DepositGetHandle *dwh = cls; + const json_t *j = response; + struct TALER_EXCHANGE_GetDepositResponse dr = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code + }; + + dwh->job = NULL; + switch (response_code) + { + case 0: + dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ( + "wtid", + &dr.details.ok.wtid), + GNUNET_JSON_spec_timestamp ( + "execution_time", + &dr.details.ok.execution_time), + TALER_JSON_spec_amount_any ( + "coin_contribution", + &dr.details.ok.coin_contribution), + GNUNET_JSON_spec_fixed_auto ( + "exchange_sig", + &dr.details.ok.exchange_sig), + GNUNET_JSON_spec_fixed_auto ( + "exchange_pub", + &dr.details.ok.exchange_pub), + GNUNET_JSON_spec_end () + }; + const struct TALER_EXCHANGE_Keys *key_state; + + key_state = dwh->keys; + GNUNET_assert (NULL != key_state); + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (key_state, + &dr.details.ok.exchange_pub)) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_SIGNATURE_BY_EXCHANGE; + break; + } + if (GNUNET_OK != + TALER_exchange_online_confirm_wire_verify ( + &dwh->h_wire, + &dwh->h_contract_terms, + &dr.details.ok.wtid, + &dwh->coin_pub, + dr.details.ok.execution_time, + &dr.details.ok.coin_contribution, + &dr.details.ok.exchange_pub, + &dr.details.ok.exchange_sig)) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_SIGNATURE_BY_EXCHANGE; + break; + } + dwh->cb (dwh->cb_cls, + &dr); + TALER_EXCHANGE_deposits_get_cancel (dwh); + return; + } + case MHD_HTTP_ACCEPTED: + { + /* Transaction known, but not executed yet */ + bool no_legi = false; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_timestamp ( + "execution_time", + &dr.details.accepted.execution_time), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ( + "account_pub", + &dr.details.accepted.account_pub), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint64 ( + "requirement_row", + &dr.details.accepted.requirement_row), + &no_legi), + GNUNET_JSON_spec_bool ( + "kyc_ok", + &dr.details.accepted.kyc_ok), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + if (no_legi) + dr.details.accepted.requirement_row = 0; + dwh->cb (dwh->cb_cls, + &dr); + TALER_EXCHANGE_deposits_get_cancel (dwh); + return; + } + case MHD_HTTP_BAD_REQUEST: + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_FORBIDDEN: + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + /* Nothing really to verify, exchange says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + /* Exchange does not know about transaction; + we should pass the reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange GET deposits\n", + (unsigned int) response_code, + (int) dr.hr.ec); + GNUNET_break_op (0); + break; + } + dwh->cb (dwh->cb_cls, + &dr); + TALER_EXCHANGE_deposits_get_cancel (dwh); +} + + +struct TALER_EXCHANGE_DepositGetHandle * +TALER_EXCHANGE_deposits_get ( + struct GNUNET_CURL_Context *ctx, + const char *url, + struct TALER_EXCHANGE_Keys *keys, + const struct TALER_MerchantPrivateKeyP *merchant_priv, + const struct TALER_MerchantWireHashP *h_wire, + const struct TALER_PrivateContractHashP *h_contract_terms, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + struct GNUNET_TIME_Relative timeout, + TALER_EXCHANGE_DepositGetCallback cb, + void *cb_cls) +{ + struct TALER_MerchantPublicKeyP merchant; + struct TALER_MerchantSignatureP merchant_sig; + struct TALER_EXCHANGE_DepositGetHandle *dwh; + CURL *eh; + char arg_str[(sizeof (struct TALER_CoinSpendPublicKeyP) + + sizeof (struct TALER_MerchantWireHashP) + + sizeof (struct TALER_MerchantPublicKeyP) + + sizeof (struct TALER_PrivateContractHashP) + + sizeof (struct TALER_MerchantSignatureP)) * 2 + 48]; + unsigned int tms + = (unsigned int) timeout.rel_value_us + / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us; + + GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv, + &merchant.eddsa_pub); + TALER_merchant_deposit_sign (h_contract_terms, + h_wire, + coin_pub, + merchant_priv, + &merchant_sig); + { + char cpub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2]; + char mpub_str[sizeof (struct TALER_MerchantPublicKeyP) * 2]; + char msig_str[sizeof (struct TALER_MerchantSignatureP) * 2]; + char chash_str[sizeof (struct TALER_PrivateContractHashP) * 2]; + char whash_str[sizeof (struct TALER_MerchantWireHashP) * 2]; + char timeout_str[24]; + char *end; + + end = GNUNET_STRINGS_data_to_string (h_wire, + sizeof (*h_wire), + whash_str, + sizeof (whash_str)); + *end = '\0'; + end = GNUNET_STRINGS_data_to_string (&merchant, + sizeof (merchant), + mpub_str, + sizeof (mpub_str)); + *end = '\0'; + end = GNUNET_STRINGS_data_to_string (h_contract_terms, + sizeof (*h_contract_terms), + chash_str, + sizeof (chash_str)); + *end = '\0'; + end = GNUNET_STRINGS_data_to_string (coin_pub, + sizeof (*coin_pub), + cpub_str, + sizeof (cpub_str)); + *end = '\0'; + end = GNUNET_STRINGS_data_to_string (&merchant_sig, + sizeof (merchant_sig), + msig_str, + sizeof (msig_str)); + *end = '\0'; + if (GNUNET_TIME_relative_is_zero (timeout)) + { + timeout_str[0] = '\0'; + } + else + { + GNUNET_snprintf ( + timeout_str, + sizeof (timeout_str), + "%u", + tms); + } + + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "deposits/%s/%s/%s/%s?merchant_sig=%s%s%s", + whash_str, + mpub_str, + chash_str, + cpub_str, + msig_str, + 0 == tms + ? "" + : "&timeout_ms=", + timeout_str); + } + + dwh = GNUNET_new (struct TALER_EXCHANGE_DepositGetHandle); + dwh->cb = cb; + dwh->cb_cls = cb_cls; + dwh->url = TALER_url_join (url, + arg_str, + NULL); + if (NULL == dwh->url) + { + GNUNET_free (dwh); + return NULL; + } + dwh->h_wire = *h_wire; + dwh->h_contract_terms = *h_contract_terms; + dwh->coin_pub = *coin_pub; + eh = TALER_EXCHANGE_curl_easy_get_ (dwh->url); + if (NULL == eh) + { + GNUNET_break (0); + GNUNET_free (dwh->url); + GNUNET_free (dwh); + return NULL; + } + if (0 != tms) + { + GNUNET_break (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_TIMEOUT_MS, + (long) (tms + 100L))); + } + dwh->job = GNUNET_CURL_job_add (ctx, + eh, + &handle_deposit_wtid_finished, + dwh); + dwh->keys = TALER_EXCHANGE_keys_incref (keys); + return dwh; +} + + +void +TALER_EXCHANGE_deposits_get_cancel (struct TALER_EXCHANGE_DepositGetHandle *dwh) +{ + if (NULL != dwh->job) + { + GNUNET_CURL_job_cancel (dwh->job); + dwh->job = NULL; + } + GNUNET_free (dwh->url); + TALER_curl_easy_post_finished (&dwh->ctx); + TALER_EXCHANGE_keys_decref (dwh->keys); + GNUNET_free (dwh); +} + + +/* end of exchange_api_deposits_get.c */ diff --git a/src/lib/exchange_api_get-kyc-check-H_NORMALIZED_PAYTO.c b/src/lib/exchange_api_get-kyc-check-H_NORMALIZED_PAYTO.c @@ -0,0 +1,415 @@ +/* + This file is part of TALER + Copyright (C) 2021-2024 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_get-kyc-check-H_NORMALIZED_PAYTO.c + * @brief Implementation of the /kyc-check request + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <microhttpd.h> /* just for HTTP check codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_exchange_service.h" +#include "taler/taler_json_lib.h" +#include "exchange_api_handle.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A ``/kyc-check`` handle + */ +struct TALER_EXCHANGE_KycCheckHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_KycStatusCallback cb; + + /** + * Closure for @e cb. + */ + void *cb_cls; + +}; + + +static enum GNUNET_GenericReturnValue +parse_account_status ( + struct TALER_EXCHANGE_KycCheckHandle *kch, + const json_t *j, + struct TALER_EXCHANGE_KycStatus *ks, + struct TALER_EXCHANGE_AccountKycStatus *status) +{ + const json_t *limits = NULL; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_bool ("aml_review", + &status->aml_review), + GNUNET_JSON_spec_uint64 ("rule_gen", + &status->rule_gen), + GNUNET_JSON_spec_fixed_auto ("access_token", + &status->access_token), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_array_const ("limits", + &limits), + NULL), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if ( (NULL != limits) && + (0 != json_array_size (limits)) ) + { + size_t limit_length = json_array_size (limits); + struct TALER_EXCHANGE_AccountLimit ala[GNUNET_NZL (limit_length)]; + size_t i; + json_t *limit; + + json_array_foreach (limits, i, limit) + { + struct TALER_EXCHANGE_AccountLimit *al = &ala[i]; + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_bool ("soft_limit", + &al->soft_limit), + NULL), + GNUNET_JSON_spec_relative_time ("timeframe", + &al->timeframe), + TALER_JSON_spec_kycte ("operation_type", + &al->operation_type), + TALER_JSON_spec_amount_any ("threshold", + &al->threshold), + GNUNET_JSON_spec_end () + }; + + al->soft_limit = false; + if (GNUNET_OK != + GNUNET_JSON_parse (limit, + ispec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + status->limits = ala; + status->limits_length = limit_length; + kch->cb (kch->cb_cls, + ks); + } + else + { + kch->cb (kch->cb_cls, + ks); + } + GNUNET_JSON_parse_free (spec); + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /kyc-check request. + * + * @param cls the `struct TALER_EXCHANGE_KycCheckHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_kyc_check_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_KycCheckHandle *kch = cls; + const json_t *j = response; + struct TALER_EXCHANGE_KycStatus ks = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code + }; + + kch->job = NULL; + switch (response_code) + { + case 0: + ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + { + if (GNUNET_OK != + parse_account_status (kch, + j, + &ks, + &ks.details.ok)) + { + GNUNET_break_op (0); + ks.hr.http_status = 0; + ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + TALER_EXCHANGE_kyc_check_cancel (kch); + return; + } + case MHD_HTTP_ACCEPTED: + { + if (GNUNET_OK != + parse_account_status (kch, + j, + &ks, + &ks.details.accepted)) + { + GNUNET_break_op (0); + ks.hr.http_status = 0; + ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + TALER_EXCHANGE_kyc_check_cancel (kch); + return; + } + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_BAD_REQUEST: + ks.hr.ec = TALER_JSON_get_error_code (j); + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_FORBIDDEN: + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ( + "expected_account_pub", + &ks.details.forbidden.expected_account_pub), + TALER_JSON_spec_ec ("code", + &ks.hr.ec), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + ks.hr.http_status = 0; + ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + break; + } + case MHD_HTTP_NOT_FOUND: + ks.hr.ec = TALER_JSON_get_error_code (j); + break; + case MHD_HTTP_CONFLICT: + ks.hr.ec = TALER_JSON_get_error_code (j); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + ks.hr.ec = TALER_JSON_get_error_code (j); + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + ks.hr.ec = TALER_JSON_get_error_code (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange kyc_check\n", + (unsigned int) response_code, + (int) ks.hr.ec); + break; + } + kch->cb (kch->cb_cls, + &ks); + TALER_EXCHANGE_kyc_check_cancel (kch); +} + + +struct TALER_EXCHANGE_KycCheckHandle * +TALER_EXCHANGE_kyc_check ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const struct TALER_NormalizedPaytoHashP *h_payto, + const union TALER_AccountPrivateKeyP *account_priv, + uint64_t known_rule_gen, + enum TALER_EXCHANGE_KycLongPollTarget lpt, + struct GNUNET_TIME_Relative timeout, + TALER_EXCHANGE_KycStatusCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_KycCheckHandle *kch; + CURL *eh; + char arg_str[128]; + char timeout_ms[32]; + char lpt_str[32]; + char krg_str[32]; + struct curl_slist *job_headers = NULL; + unsigned long long tms; + + { + char *hps; + + hps = GNUNET_STRINGS_data_to_string_alloc ( + h_payto, + sizeof (*h_payto)); + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "kyc-check/%s", + hps); + GNUNET_free (hps); + } + tms = timeout.rel_value_us + / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us; + GNUNET_snprintf (timeout_ms, + sizeof (timeout_ms), + "%llu", + tms); + GNUNET_snprintf (krg_str, + sizeof (krg_str), + "%llu", + (unsigned long long) known_rule_gen); + GNUNET_snprintf (lpt_str, + sizeof (lpt_str), + "%d", + (int) lpt); + kch = GNUNET_new (struct TALER_EXCHANGE_KycCheckHandle); + kch->cb = cb; + kch->cb_cls = cb_cls; + kch->url + = TALER_url_join ( + url, + arg_str, + "timeout_ms", + GNUNET_TIME_relative_is_zero (timeout) + ? NULL + : timeout_ms, + "min_rule", + 0 == known_rule_gen + ? NULL + : krg_str, + "lpt", + TALER_EXCHANGE_KLPT_NONE == lpt + ? NULL + : lpt_str, + NULL); + if (NULL == kch->url) + { + GNUNET_free (kch); + return NULL; + } + eh = TALER_EXCHANGE_curl_easy_get_ (kch->url); + if (NULL == eh) + { + GNUNET_break (0); + GNUNET_free (kch->url); + GNUNET_free (kch); + return NULL; + } + if (0 != tms) + { + GNUNET_break (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_TIMEOUT_MS, + (long) (tms + 500L))); + } + job_headers + = curl_slist_append ( + job_headers, + "Content-Type: application/json"); + { + union TALER_AccountPublicKeyP account_pub; + union TALER_AccountSignatureP account_sig; + char *sig_hdr; + char *pub_hdr; + char *hdr; + + GNUNET_CRYPTO_eddsa_key_get_public ( + &account_priv->reserve_priv.eddsa_priv, + &account_pub.reserve_pub.eddsa_pub); + TALER_account_kyc_auth_sign (account_priv, + &account_sig); + + sig_hdr = GNUNET_STRINGS_data_to_string_alloc ( + &account_sig, + sizeof (account_sig)); + GNUNET_asprintf (&hdr, + "%s: %s", + TALER_HTTP_HEADER_ACCOUNT_OWNER_SIGNATURE, + sig_hdr); + GNUNET_free (sig_hdr); + job_headers = curl_slist_append (job_headers, + hdr); + GNUNET_free (hdr); + + pub_hdr = GNUNET_STRINGS_data_to_string_alloc ( + &account_pub, + sizeof (account_pub)); + GNUNET_asprintf (&hdr, + "%s: %s", + TALER_HTTP_HEADER_ACCOUNT_OWNER_PUBKEY, + pub_hdr); + GNUNET_free (pub_hdr); + job_headers = curl_slist_append (job_headers, + hdr); + GNUNET_free (hdr); + if (NULL == job_headers) + { + GNUNET_break (0); + curl_easy_cleanup (eh); + return NULL; + } + } + kch->job + = GNUNET_CURL_job_add2 (ctx, + eh, + job_headers, + &handle_kyc_check_finished, + kch); + curl_slist_free_all (job_headers); + return kch; +} + + +void +TALER_EXCHANGE_kyc_check_cancel (struct TALER_EXCHANGE_KycCheckHandle *kch) +{ + if (NULL != kch->job) + { + GNUNET_CURL_job_cancel (kch->job); + kch->job = NULL; + } + GNUNET_free (kch->url); + GNUNET_free (kch); +} + + +/* end of exchange_api_kyc_check.c */ diff --git a/src/lib/exchange_api_get-kyc-info-ACCESS_TOKEN.c b/src/lib/exchange_api_get-kyc-info-ACCESS_TOKEN.c @@ -0,0 +1,392 @@ +/* + This file is part of TALER + Copyright (C) 2015-2023 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_get-kyc-info-ACCESS_TOKEN.c + * @brief Implementation of the /kyc-info/$AT request + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_exchange_service.h" +#include "taler/taler_json_lib.h" +#include "exchange_api_handle.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A /kyc-info Handle + */ +struct TALER_EXCHANGE_KycInfoHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_KycInfoCallback kyc_info_cb; + + /** + * Closure for @e cb. + */ + void *kyc_info_cb_cls; + +}; + + +/** + * Parse the provided kyc_infoage data from the "200 OK" response + * for one of the coins. + * + * @param[in,out] lh kyc_info handle (callback may be zero'ed out) + * @param json json reply with the data for one coin + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +static enum GNUNET_GenericReturnValue +parse_kyc_info_ok (struct TALER_EXCHANGE_KycInfoHandle *lh, + const json_t *json) +{ + const json_t *jrequirements = NULL; + const json_t *jvoluntary_checks = NULL; + struct TALER_EXCHANGE_KycProcessClientInformation lr = { + .hr.reply = json, + .hr.http_status = MHD_HTTP_OK + }; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_array_const ("requirements", + &jrequirements), + GNUNET_JSON_spec_bool ("is_and_combinator", + &lr.details.ok.is_and_combinator), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_object_const ("voluntary_checks", + &jvoluntary_checks), + NULL), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + lr.details.ok.vci_length + = (unsigned int) json_object_size (jvoluntary_checks); + if ( ((size_t) lr.details.ok.vci_length) + != json_object_size (jvoluntary_checks)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + lr.details.ok.requirements_length + = json_array_size (jrequirements); + if ( ((size_t) lr.details.ok.requirements_length) + != json_array_size (jrequirements)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + { + struct TALER_EXCHANGE_VoluntaryCheckInformation vci[ + GNUNET_NZL (lr.details.ok.vci_length)]; + struct TALER_EXCHANGE_RequirementInformation requirements[ + GNUNET_NZL (lr.details.ok.requirements_length)]; + const char *name; + const json_t *jreq; + const json_t *jvc; + size_t off; + + memset (vci, + 0, + sizeof (vci)); + memset (requirements, + 0, + sizeof (requirements)); + + json_array_foreach ((json_t *) jrequirements, off, jreq) + { + struct TALER_EXCHANGE_RequirementInformation *req = &requirements[off]; + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_string ("form", + &req->form), + GNUNET_JSON_spec_string ("description", + &req->description), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_object_const ("description_i18n", + &req->description_i18n), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("id", + &req->id), + NULL), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (jreq, + ispec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + GNUNET_assert (off == lr.details.ok.requirements_length); + + off = 0; + json_object_foreach ((json_t *) jvoluntary_checks, name, jvc) + { + struct TALER_EXCHANGE_VoluntaryCheckInformation *vc = &vci[off++]; + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_string ("description", + &vc->description), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_object_const ("description_i18n", + &vc->description_i18n), + NULL), + GNUNET_JSON_spec_end () + }; + + vc->name = name; + if (GNUNET_OK != + GNUNET_JSON_parse (jvc, + ispec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + GNUNET_assert (off == lr.details.ok.vci_length); + + lr.details.ok.vci = vci; + lr.details.ok.requirements = requirements; + lh->kyc_info_cb (lh->kyc_info_cb_cls, + &lr); + lh->kyc_info_cb = NULL; + return GNUNET_OK; + } +} + + +/** + * Function called when we're done processing the + * HTTP /kyc-info/$AT request. + * + * @param cls the `struct TALER_EXCHANGE_KycInfoHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_kyc_info_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_KycInfoHandle *lh = cls; + const json_t *j = response; + struct TALER_EXCHANGE_KycProcessClientInformation lr = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code + }; + + lh->job = NULL; + switch (response_code) + { + case 0: + lr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + parse_kyc_info_ok (lh, + j)) + { + GNUNET_break_op (0); + lr.hr.http_status = 0; + lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + GNUNET_assert (NULL == lh->kyc_info_cb); + TALER_EXCHANGE_kyc_info_cancel (lh); + return; + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_BAD_REQUEST: + lr.hr.ec = TALER_JSON_get_error_code (j); + lr.hr.hint = TALER_JSON_get_error_hint (j); + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_FORBIDDEN: + lr.hr.ec = TALER_JSON_get_error_code (j); + lr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_NOT_FOUND: + lr.hr.ec = TALER_JSON_get_error_code (j); + lr.hr.hint = TALER_JSON_get_error_hint (j); + /* Nothing really to verify, exchange says this coin was not melted; we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + lr.hr.ec = TALER_JSON_get_error_code (j); + lr.hr.hint = TALER_JSON_get_error_hint (j); + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + lr.hr.ec = TALER_JSON_get_error_code (j); + lr.hr.hint = TALER_JSON_get_error_hint (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange /kyc-info\n", + (unsigned int) response_code, + (int) lr.hr.ec); + break; + } + if (NULL != lh->kyc_info_cb) + lh->kyc_info_cb (lh->kyc_info_cb_cls, + &lr); + TALER_EXCHANGE_kyc_info_cancel (lh); +} + + +struct TALER_EXCHANGE_KycInfoHandle * +TALER_EXCHANGE_kyc_info ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const struct TALER_AccountAccessTokenP *token, + const char *if_none_match, + struct GNUNET_TIME_Relative timeout, + TALER_EXCHANGE_KycInfoCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_KycInfoHandle *lh; + CURL *eh; + char arg_str[sizeof (struct TALER_AccountAccessTokenP) * 2 + 32]; + unsigned int tms + = (unsigned int) timeout.rel_value_us + / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us; + struct curl_slist *job_headers = NULL; + + { + char at_str[sizeof (struct TALER_AccountAccessTokenP) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string ( + token, + sizeof (*token), + at_str, + sizeof (at_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "kyc-info/%s", + at_str); + } + lh = GNUNET_new (struct TALER_EXCHANGE_KycInfoHandle); + lh->kyc_info_cb = cb; + lh->kyc_info_cb_cls = cb_cls; + { + char timeout_str[32]; + + GNUNET_snprintf (timeout_str, + sizeof (timeout_str), + "%u", + tms); + lh->url = TALER_url_join (url, + arg_str, + "timeout_ms", + (0 == tms) + ? NULL + : timeout_str, + NULL); + } + if (NULL == lh->url) + { + GNUNET_free (lh); + return NULL; + } + eh = TALER_EXCHANGE_curl_easy_get_ (lh->url); + if (NULL == eh) + { + GNUNET_break (0); + GNUNET_free (lh->url); + GNUNET_free (lh); + return NULL; + } + if (0 != tms) + { + GNUNET_break (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_TIMEOUT_MS, + (long) (tms + 100L))); + } + + job_headers = curl_slist_append (job_headers, + "Content-Type: application/json"); + if (NULL != if_none_match) + { + char *hdr; + + GNUNET_asprintf (&hdr, + "%s: %s", + MHD_HTTP_HEADER_IF_NONE_MATCH, + if_none_match); + job_headers = curl_slist_append (job_headers, + hdr); + GNUNET_free (hdr); + } + lh->job = GNUNET_CURL_job_add2 (ctx, + eh, + job_headers, + &handle_kyc_info_finished, + lh); + curl_slist_free_all (job_headers); + return lh; +} + + +void +TALER_EXCHANGE_kyc_info_cancel (struct TALER_EXCHANGE_KycInfoHandle *lh) +{ + if (NULL != lh->job) + { + GNUNET_CURL_job_cancel (lh->job); + lh->job = NULL; + } + + GNUNET_free (lh->url); + GNUNET_free (lh); +} + + +/* end of exchange_api_kyc_info.c */ diff --git a/src/lib/exchange_api_get-kyc-proof-PROVIDER_NAME.c b/src/lib/exchange_api_get-kyc-proof-PROVIDER_NAME.c @@ -0,0 +1,217 @@ +/* + This file is part of TALER + Copyright (C) 2021, 2022 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_get-kyc-proof-PROVIDER_NAME.c + * @brief Implementation of the /kyc-proof request + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <microhttpd.h> /* just for HTTP proof codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_exchange_service.h" +#include "taler/taler_json_lib.h" +#include "exchange_api_handle.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A ``/kyc-proof`` handle + */ +struct TALER_EXCHANGE_KycProofHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Handle to our CURL request. + */ + CURL *eh; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_KycProofCallback cb; + + /** + * Closure for @e cb. + */ + void *cb_cls; + +}; + + +/** + * Function called when we're done processing the + * HTTP /kyc-proof request. + * + * @param cls the `struct TALER_EXCHANGE_KycProofHandle` + * @param response_code HTTP response code, 0 on error + * @param body response body + * @param body_size number of bytes in @a body + */ +static void +handle_kyc_proof_finished (void *cls, + long response_code, + const void *body, + size_t body_size) +{ + struct TALER_EXCHANGE_KycProofHandle *kph = cls; + struct TALER_EXCHANGE_KycProofResponse kpr = { + .hr.http_status = (unsigned int) response_code + }; + + (void) body; + (void) body_size; + kph->job = NULL; + switch (response_code) + { + case 0: + break; + case MHD_HTTP_SEE_OTHER: + { + char *redirect_url; + + GNUNET_assert (CURLE_OK == + curl_easy_getinfo (kph->eh, + CURLINFO_REDIRECT_URL, + &redirect_url)); + kpr.details.found.redirect_url = redirect_url; + break; + } + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_UNAUTHORIZED: + break; + case MHD_HTTP_FORBIDDEN: + break; + case MHD_HTTP_NOT_FOUND: + break; + case MHD_HTTP_BAD_GATEWAY: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + case MHD_HTTP_GATEWAY_TIMEOUT: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u for exchange kyc_proof\n", + (unsigned int) response_code); + break; + } + kph->cb (kph->cb_cls, + &kpr); + TALER_EXCHANGE_kyc_proof_cancel (kph); +} + + +struct TALER_EXCHANGE_KycProofHandle * +TALER_EXCHANGE_kyc_proof ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const struct TALER_NormalizedPaytoHashP *h_payto, + const char *logic, + const char *args, + TALER_EXCHANGE_KycProofCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_KycProofHandle *kph; + char *arg_str; + + if (NULL == args) + args = ""; + else + GNUNET_assert (args[0] == '&'); + { + char hstr[sizeof (*h_payto) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string (h_payto, + sizeof (*h_payto), + hstr, + sizeof (hstr)); + *end = '\0'; + GNUNET_asprintf (&arg_str, + "kyc-proof/%s?state=%s%s", + logic, + hstr, + args); + } + kph = GNUNET_new (struct TALER_EXCHANGE_KycProofHandle); + kph->cb = cb; + kph->cb_cls = cb_cls; + kph->url = TALER_url_join (url, + arg_str, + NULL); + GNUNET_free (arg_str); + if (NULL == kph->url) + { + GNUNET_free (kph); + return NULL; + } + kph->eh = TALER_EXCHANGE_curl_easy_get_ (kph->url); + if (NULL == kph->eh) + { + GNUNET_break (0); + GNUNET_free (kph->url); + GNUNET_free (kph); + return NULL; + } + /* disable location following, we want to learn the + result of a 303 redirect! */ + GNUNET_assert (CURLE_OK == + curl_easy_setopt (kph->eh, + CURLOPT_FOLLOWLOCATION, + 0L)); + kph->job = GNUNET_CURL_job_add_raw (ctx, + kph->eh, + NULL, + &handle_kyc_proof_finished, + kph); + return kph; +} + + +void +TALER_EXCHANGE_kyc_proof_cancel (struct TALER_EXCHANGE_KycProofHandle *kph) +{ + if (NULL != kph->job) + { + GNUNET_CURL_job_cancel (kph->job); + kph->job = NULL; + } + GNUNET_free (kph->url); + GNUNET_free (kph); +} + + +/* end of exchange_api_kyc_proof.c */ diff --git a/src/lib/exchange_api_get-management-keys.c b/src/lib/exchange_api_get-management-keys.c @@ -0,0 +1,427 @@ +/* + This file is part of TALER + Copyright (C) 2015-2023 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_get-management-keys.c + * @brief functions to obtain future online keys of the exchange + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include "taler/taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include <microhttpd.h> +#include "taler/taler_exchange_service.h" +#include "exchange_api_curl_defaults.h" +#include "taler/taler_signatures.h" +#include "taler/taler_curl_lib.h" +#include "taler/taler_util.h" +#include "taler/taler_json_lib.h" + +/** + * Set to 1 for extra debug logging. + */ +#define DEBUG 0 + + +/** + * @brief Handle for a GET /management/keys request. + */ +struct TALER_EXCHANGE_GetManagementKeysHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_ManagementGetKeysCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; +}; + + +/** + * Handle the case that the response was of type #MHD_HTTP_OK. + * + * @param[in,out] gh request handle + * @param response the response + * @return #GNUNET_OK if the response was well-formed + */ +static enum GNUNET_GenericReturnValue +handle_ok (struct TALER_EXCHANGE_GetManagementKeysHandle *gh, + const json_t *response) +{ + struct TALER_EXCHANGE_ManagementGetKeysResponse gkr = { + .hr.http_status = MHD_HTTP_OK, + .hr.reply = response, + }; + struct TALER_EXCHANGE_FutureKeys *fk + = &gkr.details.ok.keys; + const json_t *sk; + const json_t *dk; + bool ok; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_array_const ("future_denoms", + &dk), + GNUNET_JSON_spec_array_const ("future_signkeys", + &sk), + GNUNET_JSON_spec_fixed_auto ("master_pub", + &fk->master_pub), + GNUNET_JSON_spec_fixed_auto ("denom_secmod_public_key", + &fk->denom_secmod_public_key), + GNUNET_JSON_spec_fixed_auto ("denom_secmod_cs_public_key", + &fk->denom_secmod_cs_public_key), + GNUNET_JSON_spec_fixed_auto ("signkey_secmod_public_key", + &fk->signkey_secmod_public_key), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (response, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + fk->num_sign_keys = json_array_size (sk); + fk->num_denom_keys = json_array_size (dk); + fk->sign_keys = GNUNET_new_array ( + fk->num_sign_keys, + struct TALER_EXCHANGE_FutureSigningPublicKey); + fk->denom_keys = GNUNET_new_array ( + fk->num_denom_keys, + struct TALER_EXCHANGE_FutureDenomPublicKey); + ok = true; + for (unsigned int i = 0; i<fk->num_sign_keys; i++) + { + json_t *j = json_array_get (sk, + i); + struct TALER_EXCHANGE_FutureSigningPublicKey *sign_key + = &fk->sign_keys[i]; + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_fixed_auto ("key", + &sign_key->key), + GNUNET_JSON_spec_fixed_auto ("signkey_secmod_sig", + &sign_key->signkey_secmod_sig), + GNUNET_JSON_spec_timestamp ("stamp_start", + &sign_key->valid_from), + GNUNET_JSON_spec_timestamp ("stamp_expire", + &sign_key->valid_until), + GNUNET_JSON_spec_timestamp ("stamp_end", + &sign_key->valid_legal), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + ispec, + NULL, NULL)) + { + GNUNET_break_op (0); + ok = false; + break; + } + { + struct GNUNET_TIME_Relative duration + = GNUNET_TIME_absolute_get_difference (sign_key->valid_from.abs_time, + sign_key->valid_until.abs_time); + + if (GNUNET_OK != + TALER_exchange_secmod_eddsa_verify ( + &sign_key->key, + sign_key->valid_from, + duration, + &fk->signkey_secmod_public_key, + &sign_key->signkey_secmod_sig)) + { + GNUNET_break_op (0); + ok = false; + break; + } + } + } + for (unsigned int i = 0; i<fk->num_denom_keys; i++) + { + json_t *j = json_array_get (dk, + i); + struct TALER_EXCHANGE_FutureDenomPublicKey *denom_key + = &fk->denom_keys[i]; + const char *section_name; + struct GNUNET_JSON_Specification ispec[] = { + TALER_JSON_spec_amount_any ("value", + &denom_key->value), + GNUNET_JSON_spec_timestamp ("stamp_start", + &denom_key->valid_from), + GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw", + &denom_key->withdraw_valid_until), + GNUNET_JSON_spec_timestamp ("stamp_expire_deposit", + &denom_key->expire_deposit), + GNUNET_JSON_spec_timestamp ("stamp_expire_legal", + &denom_key->expire_legal), + TALER_JSON_spec_denom_pub ("denom_pub", + &denom_key->key), + TALER_JSON_spec_amount_any ("fee_withdraw", + &denom_key->fee_withdraw), + TALER_JSON_spec_amount_any ("fee_deposit", + &denom_key->fee_deposit), + TALER_JSON_spec_amount_any ("fee_refresh", + &denom_key->fee_refresh), + TALER_JSON_spec_amount_any ("fee_refund", + &denom_key->fee_refund), + GNUNET_JSON_spec_fixed_auto ("denom_secmod_sig", + &denom_key->denom_secmod_sig), + GNUNET_JSON_spec_string ("section_name", + &section_name), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + ispec, + NULL, NULL)) + { + GNUNET_break_op (0); +#if DEBUG + json_dumpf (j, + stderr, + JSON_INDENT (2)); +#endif + ok = false; + break; + } + + { + struct TALER_DenominationHashP h_denom_pub; + struct GNUNET_TIME_Relative duration + = GNUNET_TIME_absolute_get_difference ( + denom_key->valid_from.abs_time, + denom_key->withdraw_valid_until.abs_time); + + TALER_denom_pub_hash (&denom_key->key, + &h_denom_pub); + switch (denom_key->key.bsign_pub_key->cipher) + { + case GNUNET_CRYPTO_BSA_RSA: + { + struct TALER_RsaPubHashP h_rsa; + + TALER_rsa_pub_hash ( + denom_key->key.bsign_pub_key->details.rsa_public_key, + &h_rsa); + if (GNUNET_OK != + TALER_exchange_secmod_rsa_verify (&h_rsa, + section_name, + denom_key->valid_from, + duration, + &fk->denom_secmod_public_key, + &denom_key->denom_secmod_sig)) + { + GNUNET_break_op (0); + ok = false; + break; + } + } + break; + case GNUNET_CRYPTO_BSA_CS: + { + struct TALER_CsPubHashP h_cs; + + TALER_cs_pub_hash ( + &denom_key->key.bsign_pub_key->details.cs_public_key, + &h_cs); + if (GNUNET_OK != + TALER_exchange_secmod_cs_verify (&h_cs, + section_name, + denom_key->valid_from, + duration, + &fk->denom_secmod_cs_public_key, + &denom_key->denom_secmod_sig)) + { + GNUNET_break_op (0); + ok = false; + break; + } + } + break; + default: + GNUNET_break_op (0); + ok = false; + break; + } + } + if (! ok) + break; + } + if (ok) + { + gh->cb (gh->cb_cls, + &gkr); + } + for (unsigned int i = 0; i<fk->num_denom_keys; i++) + TALER_denom_pub_free (&fk->denom_keys[i].key); + GNUNET_free (fk->sign_keys); + GNUNET_free (fk->denom_keys); + return (ok) ? GNUNET_OK : GNUNET_SYSERR; +} + + +/** + * Function called when we're done processing the + * HTTP GET /management/keys request. + * + * @param cls the `struct TALER_EXCHANGE_GetManagementKeysHandle *` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_get_keys_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_GetManagementKeysHandle *gh = cls; + const json_t *json = response; + struct TALER_EXCHANGE_ManagementGetKeysResponse gkr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json + }; + + gh->job = NULL; + switch (response_code) + { + case MHD_HTTP_OK: + if (GNUNET_OK == + handle_ok (gh, + response)) + { + gh->cb = NULL; + } + else + { + response_code = 0; + } + break; + case MHD_HTTP_NOT_FOUND: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Server did not find handler at `%s'. Did you configure the correct exchange base URL?\n", + gh->url); + if (NULL != json) + { + gkr.hr.ec = TALER_JSON_get_error_code (json); + gkr.hr.hint = TALER_JSON_get_error_hint (json); + } + else + { + gkr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + gkr.hr.hint = TALER_ErrorCode_get_hint (gkr.hr.ec); + } + break; + default: + /* unexpected response code */ + if (NULL != json) + { + gkr.hr.ec = TALER_JSON_get_error_code (json); + gkr.hr.hint = TALER_JSON_get_error_hint (json); + } + else + { + gkr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + gkr.hr.hint = TALER_ErrorCode_get_hint (gkr.hr.ec); + } + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange management get keys\n", + (unsigned int) response_code, + (int) gkr.hr.ec); + break; + } + if (NULL != gh->cb) + { + gh->cb (gh->cb_cls, + &gkr); + gh->cb = NULL; + } + TALER_EXCHANGE_get_management_keys_cancel (gh); +}; + + +struct TALER_EXCHANGE_GetManagementKeysHandle * +TALER_EXCHANGE_get_management_keys (struct GNUNET_CURL_Context *ctx, + const char *url, + TALER_EXCHANGE_ManagementGetKeysCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_GetManagementKeysHandle *gh; + CURL *eh; + + gh = GNUNET_new (struct TALER_EXCHANGE_GetManagementKeysHandle); + gh->cb = cb; + gh->cb_cls = cb_cls; + gh->ctx = ctx; + gh->url = TALER_url_join (url, + "management/keys", + NULL); + if (NULL == gh->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (gh); + return NULL; + } + eh = TALER_EXCHANGE_curl_easy_get_ (gh->url); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", + gh->url); + gh->job = GNUNET_CURL_job_add (ctx, + eh, + &handle_get_keys_finished, + gh); + if (NULL == gh->job) + { + TALER_EXCHANGE_get_management_keys_cancel (gh); + return NULL; + } + return gh; +} + + +void +TALER_EXCHANGE_get_management_keys_cancel ( + struct TALER_EXCHANGE_GetManagementKeysHandle *gh) +{ + if (NULL != gh->job) + { + GNUNET_CURL_job_cancel (gh->job); + gh->job = NULL; + } + GNUNET_free (gh->url); + GNUNET_free (gh); +} diff --git a/src/lib/exchange_api_get-purses-PURSE_PUB-merge.c b/src/lib/exchange_api_get-purses-PURSE_PUB-merge.c @@ -0,0 +1,300 @@ +/* + This file is part of TALER + Copyright (C) 2022-2023 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_get-purses-PURSE_PUB-merge.c + * @brief Implementation of the /purses/ GET request + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_json_lib.h" +#include "taler/taler_exchange_service.h" +#include "exchange_api_handle.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A Contract Get Handle + */ +struct TALER_EXCHANGE_PurseGetHandle +{ + + /** + * The keys of the exchange this request handle will use + */ + struct TALER_EXCHANGE_Keys *keys; + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_PurseGetCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + +}; + + +/** + * Function called when we're done processing the + * HTTP /purses/$PID GET request. + * + * @param cls the `struct TALER_EXCHANGE_PurseGetHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_purse_get_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_PurseGetHandle *pgh = cls; + const json_t *j = response; + struct TALER_EXCHANGE_PurseGetResponse dr = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code + }; + + pgh->job = NULL; + switch (response_code) + { + case 0: + dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + { + bool no_merge = false; + bool no_deposit = false; + struct TALER_ExchangePublicKeyP exchange_pub; + struct TALER_ExchangeSignatureP exchange_sig; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_timestamp ("merge_timestamp", + &dr.details.ok.merge_timestamp), + &no_merge), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_timestamp ("deposit_timestamp", + &dr.details.ok.deposit_timestamp), + &no_deposit), + TALER_JSON_spec_amount_any ("balance", + &dr.details.ok.balance), + GNUNET_JSON_spec_timestamp ("purse_expiration", + &dr.details.ok.purse_expiration), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &exchange_pub), + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &exchange_sig), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (pgh->keys, + &exchange_pub)) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_EXCHANGE_PURSES_GET_INVALID_SIGNATURE_BY_EXCHANGE; + break; + } + if (GNUNET_OK != + TALER_exchange_online_purse_status_verify ( + dr.details.ok.merge_timestamp, + dr.details.ok.deposit_timestamp, + &dr.details.ok.balance, + &exchange_pub, + &exchange_sig)) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_EXCHANGE_PURSES_GET_INVALID_SIGNATURE_BY_EXCHANGE; + break; + } + pgh->cb (pgh->cb_cls, + &dr); + TALER_EXCHANGE_purse_get_cancel (pgh); + return; + } + case MHD_HTTP_BAD_REQUEST: + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_FORBIDDEN: + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + /* Nothing really to verify, exchange says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + /* Exchange does not know about transaction; + we should pass the reply to the application */ + break; + case MHD_HTTP_GONE: + /* purse expired */ + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange GET purses\n", + (unsigned int) response_code, + (int) dr.hr.ec); + GNUNET_break_op (0); + break; + } + pgh->cb (pgh->cb_cls, + &dr); + TALER_EXCHANGE_purse_get_cancel (pgh); +} + + +struct TALER_EXCHANGE_PurseGetHandle * +TALER_EXCHANGE_purse_get ( + struct GNUNET_CURL_Context *ctx, + const char *url, + struct TALER_EXCHANGE_Keys *keys, + const struct TALER_PurseContractPublicKeyP *purse_pub, + struct GNUNET_TIME_Relative timeout, + bool wait_for_merge, + TALER_EXCHANGE_PurseGetCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_PurseGetHandle *pgh; + CURL *eh; + char arg_str[sizeof (*purse_pub) * 2 + 64]; + unsigned int tms + = (unsigned int) timeout.rel_value_us + / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us; + + pgh = GNUNET_new (struct TALER_EXCHANGE_PurseGetHandle); + pgh->cb = cb; + pgh->cb_cls = cb_cls; + { + char cpub_str[sizeof (*purse_pub) * 2]; + char *end; + char timeout_str[32]; + + end = GNUNET_STRINGS_data_to_string (purse_pub, + sizeof (*purse_pub), + cpub_str, + sizeof (cpub_str)); + *end = '\0'; + GNUNET_snprintf (timeout_str, + sizeof (timeout_str), + "%u", + tms); + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "purses/%s/%s", + cpub_str, + wait_for_merge + ? "merge" + : "deposit"); + pgh->url = TALER_url_join (url, + arg_str, + "timeout_ms", + (0 == tms) + ? NULL + : timeout_str, + NULL); + } + if (NULL == pgh->url) + { + GNUNET_free (pgh); + return NULL; + } + eh = TALER_EXCHANGE_curl_easy_get_ (pgh->url); + if (NULL == eh) + { + GNUNET_break (0); + GNUNET_free (pgh->url); + GNUNET_free (pgh); + return NULL; + } + if (0 != tms) + { + GNUNET_break (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_TIMEOUT_MS, + (long) (tms + 100L))); + } + pgh->job = GNUNET_CURL_job_add (ctx, + eh, + &handle_purse_get_finished, + pgh); + pgh->keys = TALER_EXCHANGE_keys_incref (keys); + return pgh; +} + + +void +TALER_EXCHANGE_purse_get_cancel ( + struct TALER_EXCHANGE_PurseGetHandle *pgh) +{ + if (NULL != pgh->job) + { + GNUNET_CURL_job_cancel (pgh->job); + pgh->job = NULL; + } + GNUNET_free (pgh->url); + TALER_EXCHANGE_keys_decref (pgh->keys); + GNUNET_free (pgh); +} + + +/* end of exchange_api_purses_get.c */ diff --git a/src/lib/exchange_api_get-reserves-RESERVE_PUB-history.c b/src/lib/exchange_api_get-reserves-RESERVE_PUB-history.c @@ -0,0 +1,1258 @@ +/* + This file is part of TALER + 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 General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_get-reserves-RESERVE_PUB-history.c + * @brief Implementation of the POST /reserves/$RESERVE_PUB/history requests + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP history codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_exchange_service.h" +#include "taler/taler_json_lib.h" +#include "exchange_api_handle.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A /reserves/$RID/history Handle + */ +struct TALER_EXCHANGE_ReservesHistoryHandle +{ + + /** + * The keys of the exchange this request handle will use + */ + struct TALER_EXCHANGE_Keys *keys; + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Context for #TEH_curl_easy_post(). Keeps the data that must + * persist for Curl to make the upload. + */ + struct TALER_CURL_PostContext post_ctx; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_ReservesHistoryCallback cb; + + /** + * Public key of the reserve we are querying. + */ + struct TALER_ReservePublicKeyP reserve_pub; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Where to store the etag (if any). + */ + uint64_t etag; + +}; + + +/** + * Context for history entry helpers. + */ +struct HistoryParseContext +{ + + /** + * Keys of the exchange we use. + */ + const struct TALER_EXCHANGE_Keys *keys; + + /** + * Our reserve public key. + */ + const struct TALER_ReservePublicKeyP *reserve_pub; + + /** + * Array of UUIDs. + */ + struct GNUNET_HashCode *uuids; + + /** + * Where to sum up total inbound amounts. + */ + struct TALER_Amount *total_in; + + /** + * Where to sum up total outbound amounts. + */ + struct TALER_Amount *total_out; + + /** + * Number of entries already used in @e uuids. + */ + unsigned int uuid_off; +}; + + +/** + * Type of a function called to parse a reserve history + * entry @a rh. + * + * @param[in,out] rh where to write the result + * @param[in,out] uc UUID context for duplicate detection + * @param transaction the transaction to parse + * @return #GNUNET_OK on success + */ +typedef enum GNUNET_GenericReturnValue +(*ParseHelper)(struct TALER_EXCHANGE_ReserveHistoryEntry *rh, + struct HistoryParseContext *uc, + const json_t *transaction); + + +/** + * Parse "credit" reserve history entry. + * + * @param[in,out] rh entry to parse + * @param uc our context + * @param transaction the transaction to parse + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +parse_credit (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, + struct HistoryParseContext *uc, + const json_t *transaction) +{ + struct TALER_FullPayto wire_uri; + uint64_t wire_reference; + struct GNUNET_TIME_Timestamp timestamp; + struct GNUNET_JSON_Specification withdraw_spec[] = { + GNUNET_JSON_spec_uint64 ("wire_reference", + &wire_reference), + GNUNET_JSON_spec_timestamp ("timestamp", + &timestamp), + TALER_JSON_spec_full_payto_uri ("sender_account_url", + &wire_uri), + GNUNET_JSON_spec_end () + }; + + rh->type = TALER_EXCHANGE_RTT_CREDIT; + if (0 > + TALER_amount_add (uc->total_in, + uc->total_in, + &rh->amount)) + { + /* overflow in history already!? inconceivable! Bad exchange! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + withdraw_spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + rh->details.in_details.sender_url.full_payto + = GNUNET_strdup (wire_uri.full_payto); + rh->details.in_details.wire_reference = wire_reference; + rh->details.in_details.timestamp = timestamp; + return GNUNET_OK; +} + + +/** + * Parse "withdraw" reserve history entry. + * + * @param[in,out] rh entry to parse + * @param uc our context + * @param transaction the transaction to parse + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +parse_withdraw (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, + struct HistoryParseContext *uc, + const json_t *transaction) +{ + uint16_t num_coins; + struct TALER_Amount withdraw_fee; + struct TALER_Amount withdraw_amount; + struct TALER_Amount amount_without_fee; + uint8_t max_age = 0; + uint8_t noreveal_index = 0; + struct TALER_HashBlindedPlanchetsP planchets_h; + struct TALER_HashBlindedPlanchetsP selected_h; + struct TALER_ReserveSignatureP reserve_sig; + struct TALER_BlindingMasterSeedP blinding_seed; + struct TALER_DenominationHashP *denom_pub_hashes; + size_t num_denom_pub_hashes; + bool no_max_age; + bool no_noreveal_index; + bool no_blinding_seed; + bool no_selected_h; + struct GNUNET_JSON_Specification withdraw_spec[] = { + GNUNET_JSON_spec_fixed_auto ("reserve_sig", + &reserve_sig), + GNUNET_JSON_spec_uint16 ("num_coins", + &num_coins), + GNUNET_JSON_spec_fixed_auto ("planchets_h", + &planchets_h), + TALER_JSON_spec_amount_any ("amount", + &withdraw_amount), + TALER_JSON_spec_amount_any ("withdraw_fee", + &withdraw_fee), + TALER_JSON_spec_array_of_denom_pub_h ("denom_pub_hashes", + &num_denom_pub_hashes, + &denom_pub_hashes), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("selected_h", + &selected_h), + &no_selected_h), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint8 ("max_age", + &max_age), + &no_max_age), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("blinding_seed", + &blinding_seed), + &no_blinding_seed), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint8 ("noreveal_index", + &noreveal_index), + &no_noreveal_index), + GNUNET_JSON_spec_end () + }; + + rh->type = TALER_EXCHANGE_RTT_WITHDRAWAL; + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + withdraw_spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + if ((no_max_age != no_noreveal_index) || + (no_max_age != no_selected_h)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (withdraw_spec); + return GNUNET_SYSERR; + } + rh->details.withdraw.age_restricted = ! no_max_age; + + if (num_coins != num_denom_pub_hashes) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (withdraw_spec); + return GNUNET_SYSERR; + } + + /* Check that the signature is a valid withdraw request */ + if (0>TALER_amount_subtract ( + &amount_without_fee, + &withdraw_amount, + &withdraw_fee)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (withdraw_spec); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != + TALER_wallet_withdraw_verify ( + &amount_without_fee, + &withdraw_fee, + &planchets_h, + no_blinding_seed ? NULL : &blinding_seed, + no_max_age ? NULL : &uc->keys->age_mask, + no_max_age ? 0 : max_age, + uc->reserve_pub, + &reserve_sig)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (withdraw_spec); + return GNUNET_SYSERR; + } + + rh->details.withdraw.num_coins = num_coins; + rh->details.withdraw.age_restricted = ! no_max_age; + rh->details.withdraw.max_age = max_age; + rh->details.withdraw.planchets_h = planchets_h; + rh->details.withdraw.selected_h = selected_h; + rh->details.withdraw.noreveal_index = noreveal_index; + rh->details.withdraw.no_blinding_seed = no_blinding_seed; + if (! no_blinding_seed) + rh->details.withdraw.blinding_seed = blinding_seed; + + /* check that withdraw fee matches expectations! */ + { + const struct TALER_EXCHANGE_Keys *key_state; + struct TALER_Amount fee_acc; + struct TALER_Amount amount_acc; + + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (withdraw_amount.currency, + &fee_acc)); + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (withdraw_amount.currency, + &amount_acc)); + + key_state = uc->keys; + + /* accumulate the withdraw fees */ + for (size_t i=0; i < num_coins; i++) + { + const struct TALER_EXCHANGE_DenomPublicKey *dki; + + dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state, + &denom_pub_hashes[i]); + if (NULL == dki) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (withdraw_spec); + return GNUNET_SYSERR; + } + GNUNET_assert (0 <= + TALER_amount_add (&fee_acc, + &fee_acc, + &dki->fees.withdraw)); + GNUNET_assert (0 <= + TALER_amount_add (&amount_acc, + &amount_acc, + &dki->value)); + } + + if ( (GNUNET_YES != + TALER_amount_cmp_currency (&fee_acc, + &withdraw_fee)) || + (0 != + TALER_amount_cmp (&amount_acc, + &amount_without_fee)) ) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (withdraw_spec); + return GNUNET_SYSERR; + } + rh->details.withdraw.fee = withdraw_fee; + } + + #pragma message "is out_authorization_sig still needed? Not set anywhere" + rh->details.withdraw.out_authorization_sig + = json_object_get (transaction, + "signature"); + /* Check check that the same withdraw transaction + isn't listed twice by the exchange. We use the + "uuid" array to remember the hashes of all + signatures, and compare the hashes to find + duplicates. */ + GNUNET_CRYPTO_hash (&reserve_sig, + sizeof (reserve_sig), + &uc->uuids[uc->uuid_off]); + for (unsigned int i = 0; i<uc->uuid_off; i++) + { + if (0 == GNUNET_memcmp (&uc->uuids[uc->uuid_off], + &uc->uuids[i])) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (withdraw_spec); + return GNUNET_SYSERR; + } + } + uc->uuid_off++; + + if (0 > + TALER_amount_add (uc->total_out, + uc->total_out, + &rh->amount)) + { + /* overflow in history already!? inconceivable! Bad exchange! */ + GNUNET_break_op (0); + GNUNET_JSON_parse_free (withdraw_spec); + return GNUNET_SYSERR; + } + GNUNET_JSON_parse_free (withdraw_spec); + return GNUNET_OK; +} + + +/** + * Parse "recoup" reserve history entry. + * + * @param[in,out] rh entry to parse + * @param uc our context + * @param transaction the transaction to parse + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +parse_recoup (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, + struct HistoryParseContext *uc, + const json_t *transaction) +{ + const struct TALER_EXCHANGE_Keys *key_state; + struct GNUNET_JSON_Specification recoup_spec[] = { + GNUNET_JSON_spec_fixed_auto ("coin_pub", + &rh->details.recoup_details.coin_pub), + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &rh->details.recoup_details.exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &rh->details.recoup_details.exchange_pub), + GNUNET_JSON_spec_timestamp ("timestamp", + &rh->details.recoup_details.timestamp), + GNUNET_JSON_spec_end () + }; + + rh->type = TALER_EXCHANGE_RTT_RECOUP; + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + recoup_spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + key_state = uc->keys; + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (key_state, + &rh->details. + recoup_details.exchange_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_exchange_online_confirm_recoup_verify ( + rh->details.recoup_details.timestamp, + &rh->amount, + &rh->details.recoup_details.coin_pub, + uc->reserve_pub, + &rh->details.recoup_details.exchange_pub, + &rh->details.recoup_details.exchange_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (0 > + TALER_amount_add (uc->total_in, + uc->total_in, + &rh->amount)) + { + /* overflow in history already!? inconceivable! Bad exchange! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Parse "closing" reserve history entry. + * + * @param[in,out] rh entry to parse + * @param uc our context + * @param transaction the transaction to parse + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +parse_closing (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, + struct HistoryParseContext *uc, + const json_t *transaction) +{ + const struct TALER_EXCHANGE_Keys *key_state; + struct TALER_FullPayto receiver_uri; + struct GNUNET_JSON_Specification closing_spec[] = { + TALER_JSON_spec_full_payto_uri ( + "receiver_account_details", + &receiver_uri), + GNUNET_JSON_spec_fixed_auto ("wtid", + &rh->details.close_details.wtid), + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &rh->details.close_details.exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &rh->details.close_details.exchange_pub), + TALER_JSON_spec_amount_any ("closing_fee", + &rh->details.close_details.fee), + GNUNET_JSON_spec_timestamp ("timestamp", + &rh->details.close_details.timestamp), + GNUNET_JSON_spec_end () + }; + + rh->type = TALER_EXCHANGE_RTT_CLOSING; + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + closing_spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + key_state = uc->keys; + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key ( + key_state, + &rh->details.close_details.exchange_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_exchange_online_reserve_closed_verify ( + rh->details.close_details.timestamp, + &rh->amount, + &rh->details.close_details.fee, + receiver_uri, + &rh->details.close_details.wtid, + uc->reserve_pub, + &rh->details.close_details.exchange_pub, + &rh->details.close_details.exchange_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (0 > + TALER_amount_add (uc->total_out, + uc->total_out, + &rh->amount)) + { + /* overflow in history already!? inconceivable! Bad exchange! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + rh->details.close_details.receiver_account_details.full_payto + = GNUNET_strdup (receiver_uri.full_payto); + return GNUNET_OK; +} + + +/** + * Parse "merge" reserve history entry. + * + * @param[in,out] rh entry to parse + * @param uc our context + * @param transaction the transaction to parse + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +parse_merge (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, + struct HistoryParseContext *uc, + const json_t *transaction) +{ + uint32_t flags32; + struct GNUNET_JSON_Specification merge_spec[] = { + GNUNET_JSON_spec_fixed_auto ("h_contract_terms", + &rh->details.merge_details.h_contract_terms), + GNUNET_JSON_spec_fixed_auto ("merge_pub", + &rh->details.merge_details.merge_pub), + GNUNET_JSON_spec_fixed_auto ("purse_pub", + &rh->details.merge_details.purse_pub), + GNUNET_JSON_spec_uint32 ("min_age", + &rh->details.merge_details.min_age), + GNUNET_JSON_spec_uint32 ("flags", + &flags32), + GNUNET_JSON_spec_fixed_auto ("reserve_sig", + &rh->details.merge_details.reserve_sig), + TALER_JSON_spec_amount_any ("purse_fee", + &rh->details.merge_details.purse_fee), + GNUNET_JSON_spec_timestamp ("merge_timestamp", + &rh->details.merge_details.merge_timestamp), + GNUNET_JSON_spec_timestamp ("purse_expiration", + &rh->details.merge_details.purse_expiration), + GNUNET_JSON_spec_bool ("merged", + &rh->details.merge_details.merged), + GNUNET_JSON_spec_end () + }; + + rh->type = TALER_EXCHANGE_RTT_MERGE; + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + merge_spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + rh->details.merge_details.flags = + (enum TALER_WalletAccountMergeFlags) flags32; + if (GNUNET_OK != + TALER_wallet_account_merge_verify ( + rh->details.merge_details.merge_timestamp, + &rh->details.merge_details.purse_pub, + rh->details.merge_details.purse_expiration, + &rh->details.merge_details.h_contract_terms, + &rh->amount, + &rh->details.merge_details.purse_fee, + rh->details.merge_details.min_age, + rh->details.merge_details.flags, + uc->reserve_pub, + &rh->details.merge_details.reserve_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (rh->details.merge_details.merged) + { + if (0 > + TALER_amount_add (uc->total_in, + uc->total_in, + &rh->amount)) + { + /* overflow in history already!? inconceivable! Bad exchange! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + else + { + if (0 > + TALER_amount_add (uc->total_out, + uc->total_out, + &rh->details.merge_details.purse_fee)) + { + /* overflow in history already!? inconceivable! Bad exchange! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + return GNUNET_OK; +} + + +/** + * Parse "open" reserve open entry. + * + * @param[in,out] rh entry to parse + * @param uc our context + * @param transaction the transaction to parse + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +parse_open (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, + struct HistoryParseContext *uc, + const json_t *transaction) +{ + struct GNUNET_JSON_Specification open_spec[] = { + GNUNET_JSON_spec_fixed_auto ("reserve_sig", + &rh->details.open_request.reserve_sig), + TALER_JSON_spec_amount_any ("open_payment", + &rh->details.open_request.reserve_payment), + GNUNET_JSON_spec_uint32 ("requested_min_purses", + &rh->details.open_request.purse_limit), + GNUNET_JSON_spec_timestamp ("request_timestamp", + &rh->details.open_request.request_timestamp), + GNUNET_JSON_spec_timestamp ("requested_expiration", + &rh->details.open_request.reserve_expiration), + GNUNET_JSON_spec_end () + }; + + rh->type = TALER_EXCHANGE_RTT_OPEN; + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + open_spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_wallet_reserve_open_verify ( + &rh->amount, + rh->details.open_request.request_timestamp, + rh->details.open_request.reserve_expiration, + rh->details.open_request.purse_limit, + uc->reserve_pub, + &rh->details.open_request.reserve_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (0 > + TALER_amount_add (uc->total_out, + uc->total_out, + &rh->amount)) + { + /* overflow in history already!? inconceivable! Bad exchange! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Parse "close" reserve close entry. + * + * @param[in,out] rh entry to parse + * @param uc our context + * @param transaction the transaction to parse + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +parse_close (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, + struct HistoryParseContext *uc, + const json_t *transaction) +{ + struct GNUNET_JSON_Specification close_spec[] = { + GNUNET_JSON_spec_fixed_auto ("reserve_sig", + &rh->details.close_request.reserve_sig), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("h_payto", + &rh->details.close_request. + target_account_h_payto), + NULL), + GNUNET_JSON_spec_timestamp ("request_timestamp", + &rh->details.close_request.request_timestamp), + GNUNET_JSON_spec_end () + }; + + rh->type = TALER_EXCHANGE_RTT_CLOSE; + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + close_spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + /* force amount to invalid */ + memset (&rh->amount, + 0, + sizeof (rh->amount)); + if (GNUNET_OK != + TALER_wallet_reserve_close_verify ( + rh->details.close_request.request_timestamp, + &rh->details.close_request.target_account_h_payto, + uc->reserve_pub, + &rh->details.close_request.reserve_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +static void +free_reserve_history ( + unsigned int len, + struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static len]) +{ + for (unsigned int i = 0; i<len; i++) + { + struct TALER_EXCHANGE_ReserveHistoryEntry *rhi = &rhistory[i]; + + switch (rhi->type) + { + case TALER_EXCHANGE_RTT_CREDIT: + GNUNET_free (rhi->details.in_details.sender_url.full_payto); + break; + case TALER_EXCHANGE_RTT_WITHDRAWAL: + break; + case TALER_EXCHANGE_RTT_RECOUP: + break; + case TALER_EXCHANGE_RTT_CLOSING: + break; + case TALER_EXCHANGE_RTT_MERGE: + break; + case TALER_EXCHANGE_RTT_OPEN: + break; + case TALER_EXCHANGE_RTT_CLOSE: + GNUNET_free (rhi->details.close_details + .receiver_account_details.full_payto); + break; + } + } + GNUNET_free (rhistory); +} + + +/** + * Parse history given in JSON format and return it in binary + * format. + * + * @param keys exchange keys + * @param history JSON array with the history + * @param reserve_pub public key of the reserve to inspect + * @param currency currency we expect the balance to be in + * @param[out] total_in set to value of credits to reserve + * @param[out] total_out set to value of debits from reserve + * @param history_length number of entries in @a history + * @param[out] rhistory array of length @a history_length, set to the + * parsed history entries + * @return #GNUNET_OK if history was valid and @a rhistory and @a balance + * were set, + * #GNUNET_SYSERR if there was a protocol violation in @a history + */ +static enum GNUNET_GenericReturnValue +parse_reserve_history ( + const struct TALER_EXCHANGE_Keys *keys, + const json_t *history, + const struct TALER_ReservePublicKeyP *reserve_pub, + const char *currency, + struct TALER_Amount *total_in, + struct TALER_Amount *total_out, + unsigned int history_length, + struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static history_length]) +{ + const struct + { + const char *type; + ParseHelper helper; + } map[] = { + { "CREDIT", &parse_credit }, + { "WITHDRAW", &parse_withdraw }, + { "RECOUP", &parse_recoup }, + { "MERGE", &parse_merge }, + { "CLOSING", &parse_closing }, + { "OPEN", &parse_open }, + { "CLOSE", &parse_close }, + { NULL, NULL } + }; + struct GNUNET_HashCode uuid[history_length]; + struct HistoryParseContext uc = { + .keys = keys, + .reserve_pub = reserve_pub, + .uuids = uuid, + .total_in = total_in, + .total_out = total_out + }; + + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (currency, + total_in)); + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (currency, + total_out)); + for (unsigned int off = 0; off<history_length; off++) + { + struct TALER_EXCHANGE_ReserveHistoryEntry *rh = &rhistory[off]; + json_t *transaction; + struct TALER_Amount amount; + const char *type; + struct GNUNET_JSON_Specification hist_spec[] = { + GNUNET_JSON_spec_string ("type", + &type), + TALER_JSON_spec_amount_any ("amount", + &amount), + /* 'wire' and 'signature' are optional depending on 'type'! */ + GNUNET_JSON_spec_end () + }; + bool found = false; + + transaction = json_array_get (history, + off); + if (GNUNET_OK != + GNUNET_JSON_parse (transaction, + hist_spec, + NULL, NULL)) + { + GNUNET_break_op (0); + json_dumpf (transaction, + stderr, + JSON_INDENT (2)); + return GNUNET_SYSERR; + } + rh->amount = amount; + if (GNUNET_YES != + TALER_amount_cmp_currency (&amount, + total_in)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + for (unsigned int i = 0; NULL != map[i].type; i++) + { + if (0 == strcasecmp (map[i].type, + type)) + { + found = true; + if (GNUNET_OK != + map[i].helper (rh, + &uc, + transaction)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + break; + } + } + if (! found) + { + /* unexpected 'type', protocol incompatibility, complain! */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + return GNUNET_OK; +} + + +/** + * Handle HTTP header received by curl. + * + * @param buffer one line of HTTP header data + * @param size size of an item + * @param nitems number of items passed + * @param userdata our `struct TALER_EXCHANGE_ReservesHistoryHandle *` + * @return `size * nitems` + */ +static size_t +handle_header (char *buffer, + size_t size, + size_t nitems, + void *userdata) +{ + struct TALER_EXCHANGE_ReservesHistoryHandle *rhh = userdata; + size_t total = size * nitems; + char *ndup; + const char *hdr_type; + char *hdr_val; + char *sp; + + ndup = GNUNET_strndup (buffer, + total); + hdr_type = strtok_r (ndup, + ":", + &sp); + if (NULL == hdr_type) + { + GNUNET_free (ndup); + return total; + } + hdr_val = strtok_r (NULL, + "\n\r", + &sp); + if (NULL == hdr_val) + { + GNUNET_free (ndup); + return total; + } + if (' ' == *hdr_val) + hdr_val++; + if (0 == strcasecmp (hdr_type, + MHD_HTTP_HEADER_ETAG)) + { + unsigned long long tval; + char dummy; + + if (1 != + sscanf (hdr_val, + "\"%llu\"%c", + &tval, + &dummy)) + { + GNUNET_break_op (0); + GNUNET_free (ndup); + return 0; + } + rhh->etag = (uint64_t) tval; + } + GNUNET_free (ndup); + return total; +} + + +/** + * We received an #MHD_HTTP_OK history code. Handle the JSON + * response. + * + * @param rsh handle of the request + * @param j JSON response + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +handle_reserves_history_ok (struct TALER_EXCHANGE_ReservesHistoryHandle *rsh, + const json_t *j) +{ + const json_t *history; + unsigned int len; + struct TALER_EXCHANGE_ReserveHistory rs = { + .hr.reply = j, + .hr.http_status = MHD_HTTP_OK, + .details.ok.etag = rsh->etag + }; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount_any ("balance", + &rs.details.ok.balance), + GNUNET_JSON_spec_array_const ("history", + &history), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + len = json_array_size (history); + { + struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory; + + rhistory = GNUNET_new_array (len, + struct TALER_EXCHANGE_ReserveHistoryEntry); + if (GNUNET_OK != + parse_reserve_history (rsh->keys, + history, + &rsh->reserve_pub, + rs.details.ok.balance.currency, + &rs.details.ok.total_in, + &rs.details.ok.total_out, + len, + rhistory)) + { + GNUNET_break_op (0); + free_reserve_history (len, + rhistory); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + if (NULL != rsh->cb) + { + rs.details.ok.history = rhistory; + rs.details.ok.history_len = len; + rsh->cb (rsh->cb_cls, + &rs); + rsh->cb = NULL; + } + free_reserve_history (len, + rhistory); + } + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /reserves/$RID/history request. + * + * @param cls the `struct TALER_EXCHANGE_ReservesHistoryHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_reserves_history_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_ReservesHistoryHandle *rsh = cls; + const json_t *j = response; + struct TALER_EXCHANGE_ReserveHistory rs = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code + }; + + rsh->job = NULL; + switch (response_code) + { + case 0: + rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + handle_reserves_history_ok (rsh, + j)) + { + rs.hr.http_status = 0; + rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + GNUNET_break (0); + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_FORBIDDEN: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + GNUNET_break (0); + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for reserves history\n", + (unsigned int) response_code, + (int) rs.hr.ec); + break; + } + if (NULL != rsh->cb) + { + rsh->cb (rsh->cb_cls, + &rs); + rsh->cb = NULL; + } + TALER_EXCHANGE_reserves_history_cancel (rsh); +} + + +struct TALER_EXCHANGE_ReservesHistoryHandle * +TALER_EXCHANGE_reserves_history ( + struct GNUNET_CURL_Context *ctx, + const char *url, + struct TALER_EXCHANGE_Keys *keys, + const struct TALER_ReservePrivateKeyP *reserve_priv, + uint64_t start_off, + TALER_EXCHANGE_ReservesHistoryCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_ReservesHistoryHandle *rsh; + CURL *eh; + char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 64]; + struct curl_slist *job_headers; + + rsh = GNUNET_new (struct TALER_EXCHANGE_ReservesHistoryHandle); + rsh->cb = cb; + rsh->cb_cls = cb_cls; + GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, + &rsh->reserve_pub.eddsa_pub); + { + char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string ( + &rsh->reserve_pub, + sizeof (rsh->reserve_pub), + pub_str, + sizeof (pub_str)); + *end = '\0'; + if (0 != start_off) + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "reserves/%s/history?start=%llu", + pub_str, + (unsigned long long) start_off); + else + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "reserves/%s/history", + pub_str); + } + rsh->url = TALER_url_join (url, + arg_str, + NULL); + if (NULL == rsh->url) + { + GNUNET_free (rsh); + return NULL; + } + eh = TALER_EXCHANGE_curl_easy_get_ (rsh->url); + if (NULL == eh) + { + GNUNET_break (0); + GNUNET_free (rsh->url); + GNUNET_free (rsh); + return NULL; + } + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_HEADERFUNCTION, + &handle_header)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_HEADERDATA, + rsh)); + { + struct TALER_ReserveSignatureP reserve_sig; + char *sig_hdr; + char *hdr; + + TALER_wallet_reserve_history_sign (start_off, + reserve_priv, + &reserve_sig); + + sig_hdr = GNUNET_STRINGS_data_to_string_alloc ( + &reserve_sig, + sizeof (reserve_sig)); + GNUNET_asprintf (&hdr, + "%s: %s", + TALER_RESERVE_HISTORY_SIGNATURE_HEADER, + sig_hdr); + GNUNET_free (sig_hdr); + job_headers = curl_slist_append (NULL, + hdr); + GNUNET_free (hdr); + if (NULL == job_headers) + { + GNUNET_break (0); + curl_easy_cleanup (eh); + return NULL; + } + } + + rsh->keys = TALER_EXCHANGE_keys_incref (keys); + rsh->job = GNUNET_CURL_job_add2 (ctx, + eh, + job_headers, + &handle_reserves_history_finished, + rsh); + curl_slist_free_all (job_headers); + return rsh; +} + + +void +TALER_EXCHANGE_reserves_history_cancel ( + struct TALER_EXCHANGE_ReservesHistoryHandle *rsh) +{ + if (NULL != rsh->job) + { + GNUNET_CURL_job_cancel (rsh->job); + rsh->job = NULL; + } + TALER_curl_easy_post_finished (&rsh->post_ctx); + GNUNET_free (rsh->url); + TALER_EXCHANGE_keys_decref (rsh->keys); + GNUNET_free (rsh); +} + + +/* end of exchange_api_reserves_history.c */ diff --git a/src/lib/exchange_api_get-reserves-RESERVE_PUB.c b/src/lib/exchange_api_get-reserves-RESERVE_PUB.c @@ -0,0 +1,279 @@ +/* + This file is part of TALER + Copyright (C) 2014-2022 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_get-reserves-RESERVE_PUB.c + * @brief Implementation of the GET /reserves/$RESERVE_PUB requests + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_exchange_service.h" +#include "taler/taler_json_lib.h" +#include "exchange_api_handle.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A /reserves/ GET Handle + */ +struct TALER_EXCHANGE_ReservesGetHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_ReservesGetCallback cb; + + /** + * Public key of the reserve we are querying. + */ + struct TALER_ReservePublicKeyP reserve_pub; + + /** + * Closure for @a cb. + */ + void *cb_cls; + +}; + + +/** + * We received an #MHD_HTTP_OK status code. Handle the JSON + * response. + * + * @param rgh handle of the request + * @param j JSON response + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +handle_reserves_get_ok (struct TALER_EXCHANGE_ReservesGetHandle *rgh, + const json_t *j) +{ + struct TALER_EXCHANGE_ReserveSummary rs = { + .hr.reply = j, + .hr.http_status = MHD_HTTP_OK + }; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount_any ("balance", + &rs.details.ok.balance), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ( + "last_origin", + (const char **) &rs.details.ok.last_origin.full_payto), + NULL), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + rgh->cb (rgh->cb_cls, + &rs); + rgh->cb = NULL; + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /reserves/ GET request. + * + * @param cls the `struct TALER_EXCHANGE_ReservesGetHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_reserves_get_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_ReservesGetHandle *rgh = cls; + const json_t *j = response; + struct TALER_EXCHANGE_ReserveSummary rs = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code + }; + + rgh->job = NULL; + switch (response_code) + { + case 0: + rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + handle_reserves_get_ok (rgh, + j)) + { + rs.hr.http_status = 0; + rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for GET %s\n", + (unsigned int) response_code, + (int) rs.hr.ec, + rgh->url); + break; + } + if (NULL != rgh->cb) + { + rgh->cb (rgh->cb_cls, + &rs); + rgh->cb = NULL; + } + TALER_EXCHANGE_reserves_get_cancel (rgh); +} + + +struct TALER_EXCHANGE_ReservesGetHandle * +TALER_EXCHANGE_reserves_get ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const struct TALER_ReservePublicKeyP *reserve_pub, + struct GNUNET_TIME_Relative timeout, + TALER_EXCHANGE_ReservesGetCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_ReservesGetHandle *rgh; + CURL *eh; + char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 16 + 32]; + unsigned int tms + = (unsigned int) timeout.rel_value_us + / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us; + + { + char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; + char *end; + char timeout_str[32]; + + end = GNUNET_STRINGS_data_to_string ( + reserve_pub, + sizeof (*reserve_pub), + pub_str, + sizeof (pub_str)); + *end = '\0'; + GNUNET_snprintf (timeout_str, + sizeof (timeout_str), + "%u", + tms); + if (0 == tms) + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "reserves/%s", + pub_str); + else + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "reserves/%s?timeout_ms=%s", + pub_str, + timeout_str); + } + rgh = GNUNET_new (struct TALER_EXCHANGE_ReservesGetHandle); + rgh->cb = cb; + rgh->cb_cls = cb_cls; + rgh->reserve_pub = *reserve_pub; + rgh->url = TALER_url_join (url, + arg_str, + NULL); + if (NULL == rgh->url) + { + GNUNET_free (rgh); + return NULL; + } + eh = TALER_EXCHANGE_curl_easy_get_ (rgh->url); + if (NULL == eh) + { + GNUNET_break (0); + GNUNET_free (rgh->url); + GNUNET_free (rgh); + return NULL; + } + if (0 != tms) + { + GNUNET_break (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_TIMEOUT_MS, + (long) (tms + 100L))); + } + rgh->job = GNUNET_CURL_job_add (ctx, + eh, + &handle_reserves_get_finished, + rgh); + return rgh; +} + + +void +TALER_EXCHANGE_reserves_get_cancel ( + struct TALER_EXCHANGE_ReservesGetHandle *rgh) +{ + if (NULL != rgh->job) + { + GNUNET_CURL_job_cancel (rgh->job); + rgh->job = NULL; + } + GNUNET_free (rgh->url); + GNUNET_free (rgh); +} + + +/* end of exchange_api_reserves_get.c */ diff --git a/src/lib/exchange_api_get-reserves-attest-RESERVE_PUB.c b/src/lib/exchange_api_get-reserves-attest-RESERVE_PUB.c @@ -0,0 +1,276 @@ +/* + This file is part of TALER + Copyright (C) 2014-2022 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_get-reserves-attest-RESERVE_PUB.c + * @brief Implementation of the GET_ATTESTABLE /reserves/$RESERVE_PUB requests + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_exchange_service.h" +#include "taler/taler_json_lib.h" +#include "exchange_api_handle.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A /reserves/ GET_ATTESTABLE Handle + */ +struct TALER_EXCHANGE_ReservesGetAttestHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_ReservesGetAttestCallback cb; + + /** + * Public key of the reserve we are querying. + */ + struct TALER_ReservePublicKeyP reserve_pub; + + /** + * Closure for @a cb. + */ + void *cb_cls; + +}; + + +/** + * We received an #MHD_HTTP_OK status code. Handle the JSON + * response. + * + * @param rgah handle of the request + * @param j JSON response + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +handle_reserves_get_attestable_ok ( + struct TALER_EXCHANGE_ReservesGetAttestHandle *rgah, + const json_t *j) +{ + struct TALER_EXCHANGE_ReserveGetAttestResult rs = { + .hr.reply = j, + .hr.http_status = MHD_HTTP_OK + }; + const json_t *details; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_array_const ("details", + &details), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + { + unsigned int dlen = json_array_size (details); + const char *attributes[GNUNET_NZL (dlen)]; + + for (unsigned int i = 0; i<dlen; i++) + { + json_t *detail = json_array_get (details, + i); + attributes[i] = json_string_value (detail); + if (NULL == attributes[i]) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + rs.details.ok.attributes_length = dlen; + rs.details.ok.attributes = attributes; + rgah->cb (rgah->cb_cls, + &rs); + rgah->cb = NULL; + } + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP GET /reserves-attest/$RID request. + * + * @param cls the `struct TALER_EXCHANGE_ReservesGetAttestableHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_reserves_get_attestable_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_ReservesGetAttestHandle *rgah = cls; + const json_t *j = response; + struct TALER_EXCHANGE_ReserveGetAttestResult rs = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code + }; + + rgah->job = NULL; + switch (response_code) + { + case 0: + rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + handle_reserves_get_attestable_ok (rgah, + j)) + { + rs.hr.http_status = 0; + rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_CONFLICT: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for reserves get_attestable\n", + (unsigned int) response_code, + (int) rs.hr.ec); + break; + } + if (NULL != rgah->cb) + { + rgah->cb (rgah->cb_cls, + &rs); + rgah->cb = NULL; + } + TALER_EXCHANGE_reserves_get_attestable_cancel (rgah); +} + + +struct TALER_EXCHANGE_ReservesGetAttestHandle * +TALER_EXCHANGE_reserves_get_attestable ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const struct TALER_ReservePublicKeyP *reserve_pub, + TALER_EXCHANGE_ReservesGetAttestCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_ReservesGetAttestHandle *rgah; + CURL *eh; + char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32]; + + { + char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string ( + reserve_pub, + sizeof (*reserve_pub), + pub_str, + sizeof (pub_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "reserves-attest/%s", + pub_str); + } + rgah = GNUNET_new (struct TALER_EXCHANGE_ReservesGetAttestHandle); + rgah->cb = cb; + rgah->cb_cls = cb_cls; + rgah->reserve_pub = *reserve_pub; + rgah->url = TALER_url_join (url, + arg_str, + NULL); + if (NULL == rgah->url) + { + GNUNET_free (rgah); + return NULL; + } + eh = TALER_EXCHANGE_curl_easy_get_ (rgah->url); + if (NULL == eh) + { + GNUNET_break (0); + GNUNET_free (rgah->url); + GNUNET_free (rgah); + return NULL; + } + rgah->job = GNUNET_CURL_job_add (ctx, + eh, + &handle_reserves_get_attestable_finished, + rgah); + return rgah; +} + + +void +TALER_EXCHANGE_reserves_get_attestable_cancel ( + struct TALER_EXCHANGE_ReservesGetAttestHandle *rgah) +{ + if (NULL != rgah->job) + { + GNUNET_CURL_job_cancel (rgah->job); + rgah->job = NULL; + } + GNUNET_free (rgah->url); + GNUNET_free (rgah); +} + + +/* end of exchange_api_reserves_get_attestable.c */ diff --git a/src/lib/exchange_api_get-transfers-WTID.c b/src/lib/exchange_api_get-transfers-WTID.c @@ -0,0 +1,400 @@ +/* + This file is part of TALER + 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 General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_get-transfers-WTID.c + * @brief Implementation of the GET /transfers/ request + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_exchange_service.h" +#include "taler/taler_json_lib.h" +#include "exchange_api_handle.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A /transfers/ GET Handle + */ +struct TALER_EXCHANGE_TransfersGetHandle +{ + + /** + * The keys of the exchange this request handle will use + */ + struct TALER_EXCHANGE_Keys *keys; + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_TransfersGetCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + +}; + + +/** + * We got a #MHD_HTTP_OK response for the /transfers/ request. + * Check that the response is well-formed and if it is, call the + * callback. If not, return an error code. + * + * This code is very similar to + * merchant_api_track_transfer.c::check_transfers_get_response_ok. + * Any changes should likely be reflected there as well. + * + * @param wdh handle to the operation + * @param json response we got + * @return #GNUNET_OK if we are done and all is well, + * #GNUNET_SYSERR if the response was bogus + */ +static enum GNUNET_GenericReturnValue +check_transfers_get_response_ok ( + struct TALER_EXCHANGE_TransfersGetHandle *wdh, + const json_t *json) +{ + const json_t *details_j; + struct TALER_Amount total_expected; + struct TALER_MerchantPublicKeyP merchant_pub; + struct TALER_EXCHANGE_TransfersGetResponse tgr = { + .hr.reply = json, + .hr.http_status = MHD_HTTP_OK + }; + struct TALER_EXCHANGE_TransferData *td + = &tgr.details.ok.td; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount_any ("total", + &td->total_amount), + TALER_JSON_spec_amount_any ("wire_fee", + &td->wire_fee), + GNUNET_JSON_spec_fixed_auto ("merchant_pub", + &merchant_pub), + GNUNET_JSON_spec_fixed_auto ("h_payto", + &td->h_payto), + GNUNET_JSON_spec_timestamp ("execution_time", + &td->execution_time), + GNUNET_JSON_spec_array_const ("deposits", + &details_j), + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &td->exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &td->exchange_pub), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_amount_set_zero (td->total_amount.currency, + &total_expected)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key ( + wdh->keys, + &td->exchange_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + td->details_length = json_array_size (details_j); + { + struct GNUNET_HashContext *hash_context; + struct TALER_TrackTransferDetails *details; + + details = GNUNET_new_array (td->details_length, + struct TALER_TrackTransferDetails); + td->details = details; + hash_context = GNUNET_CRYPTO_hash_context_start (); + for (unsigned int i = 0; i<td->details_length; i++) + { + struct TALER_TrackTransferDetails *detail = &details[i]; + struct json_t *detail_j = json_array_get (details_j, i); + struct GNUNET_JSON_Specification spec_detail[] = { + GNUNET_JSON_spec_fixed_auto ("h_contract_terms", + &detail->h_contract_terms), + GNUNET_JSON_spec_fixed_auto ("coin_pub", &detail->coin_pub), + TALER_JSON_spec_amount ("deposit_value", + total_expected.currency, + &detail->coin_value), + TALER_JSON_spec_amount ("deposit_fee", + total_expected.currency, + &detail->coin_fee), + GNUNET_JSON_spec_mark_optional ( + TALER_JSON_spec_amount ("refund_total", + total_expected.currency, + &detail->refund_total), + NULL), + GNUNET_JSON_spec_end () + }; + + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (td->total_amount.currency, + &detail->refund_total)); + if ( (GNUNET_OK != + GNUNET_JSON_parse (detail_j, + spec_detail, + NULL, NULL)) || + (0 > + TALER_amount_add (&total_expected, + &total_expected, + &detail->coin_value)) || + (0 > + TALER_amount_subtract (&total_expected, + &total_expected, + &detail->coin_fee)) ) + { + GNUNET_break_op (0); + GNUNET_CRYPTO_hash_context_abort (hash_context); + GNUNET_free (details); + return GNUNET_SYSERR; + } + /* build up big hash for signature checking later */ + TALER_exchange_online_wire_deposit_append ( + hash_context, + &detail->h_contract_terms, + td->execution_time, + &detail->coin_pub, + &detail->coin_value, + &detail->coin_fee); + } + /* Check signature */ + GNUNET_CRYPTO_hash_context_finish (hash_context, + &td->h_details); + if (GNUNET_OK != + TALER_exchange_online_wire_deposit_verify ( + &td->total_amount, + &td->wire_fee, + &merchant_pub, + &td->h_payto, + &td->h_details, + &td->exchange_pub, + &td->exchange_sig)) + { + GNUNET_break_op (0); + GNUNET_free (details); + return GNUNET_SYSERR; + } + if (0 > + TALER_amount_subtract (&total_expected, + &total_expected, + &td->wire_fee)) + { + GNUNET_break_op (0); + GNUNET_free (details); + return GNUNET_SYSERR; + } + if (0 != + TALER_amount_cmp (&total_expected, + &td->total_amount)) + { + GNUNET_break_op (0); + GNUNET_free (details); + return GNUNET_SYSERR; + } + wdh->cb (wdh->cb_cls, + &tgr); + GNUNET_free (details); + } + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /transfers/ request. + * + * @param cls the `struct TALER_EXCHANGE_TransfersGetHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_transfers_get_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_TransfersGetHandle *wdh = cls; + const json_t *j = response; + struct TALER_EXCHANGE_TransfersGetResponse tgr = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code + }; + + wdh->job = NULL; + switch (response_code) + { + case 0: + tgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + if (GNUNET_OK == + check_transfers_get_response_ok (wdh, + j)) + { + TALER_EXCHANGE_transfers_get_cancel (wdh); + return; + } + GNUNET_break_op (0); + tgr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + tgr.hr.http_status = 0; + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + tgr.hr.ec = TALER_JSON_get_error_code (j); + tgr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_FORBIDDEN: + /* Nothing really to verify, exchange says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + tgr.hr.ec = TALER_JSON_get_error_code (j); + tgr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_NOT_FOUND: + /* Exchange does not know about transaction; + we should pass the reply to the application */ + tgr.hr.ec = TALER_JSON_get_error_code (j); + tgr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + tgr.hr.ec = TALER_JSON_get_error_code (j); + tgr.hr.hint = TALER_JSON_get_error_hint (j); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + tgr.hr.ec = TALER_JSON_get_error_code (j); + tgr.hr.hint = TALER_JSON_get_error_hint (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for transfers get\n", + (unsigned int) response_code, + (int) tgr.hr.ec); + break; + } + wdh->cb (wdh->cb_cls, + &tgr); + TALER_EXCHANGE_transfers_get_cancel (wdh); +} + + +struct TALER_EXCHANGE_TransfersGetHandle * +TALER_EXCHANGE_transfers_get ( + struct GNUNET_CURL_Context *ctx, + const char *url, + struct TALER_EXCHANGE_Keys *keys, + const struct TALER_WireTransferIdentifierRawP *wtid, + TALER_EXCHANGE_TransfersGetCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_TransfersGetHandle *wdh; + CURL *eh; + char arg_str[sizeof (struct TALER_WireTransferIdentifierRawP) * 2 + 32]; + + wdh = GNUNET_new (struct TALER_EXCHANGE_TransfersGetHandle); + wdh->cb = cb; + wdh->cb_cls = cb_cls; + + { + char wtid_str[sizeof (struct TALER_WireTransferIdentifierRawP) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string (wtid, + sizeof (struct + TALER_WireTransferIdentifierRawP), + wtid_str, + sizeof (wtid_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "transfers/%s", + wtid_str); + } + wdh->url = TALER_url_join (url, + arg_str, + NULL); + if (NULL == wdh->url) + { + GNUNET_free (wdh); + return NULL; + } + eh = TALER_EXCHANGE_curl_easy_get_ (wdh->url); + if (NULL == eh) + { + GNUNET_break (0); + GNUNET_free (wdh->url); + GNUNET_free (wdh); + return NULL; + } + wdh->keys = TALER_EXCHANGE_keys_incref (keys); + wdh->job = GNUNET_CURL_job_add_with_ct_json (ctx, + eh, + &handle_transfers_get_finished, + wdh); + return wdh; +} + + +/** + * Cancel wire deposits request. This function cannot be used on a request + * handle if a response is already served for it. + * + * @param wdh the wire deposits request handle + */ +void +TALER_EXCHANGE_transfers_get_cancel ( + struct TALER_EXCHANGE_TransfersGetHandle *wdh) +{ + if (NULL != wdh->job) + { + GNUNET_CURL_job_cancel (wdh->job); + wdh->job = NULL; + } + GNUNET_free (wdh->url); + TALER_EXCHANGE_keys_decref (wdh->keys); + GNUNET_free (wdh); +} + + +/* end of exchange_api_transfers_get.c */ diff --git a/src/lib/exchange_api_get_aml_measures.c b/src/lib/exchange_api_get_aml_measures.c @@ -1,650 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2023, 2024 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_get_aml_measures.c - * @brief Implementation of the GET /aml/$OFFICER_PUB/measures request - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_exchange_service.h" -#include "taler/taler_json_lib.h" -#include "exchange_api_handle.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * Scrap buffer of temporary arrays. - */ -struct Scrap -{ - /** - * Kept in DLL. - */ - struct Scrap *next; - - /** - * Kept in DLL. - */ - struct Scrap *prev; - - /** - * Pointer to our allocation. - */ - const char **ptr; -}; - - -/** - * @brief A GET /aml/$OFFICER_PUB/measures Handle - */ -struct TALER_EXCHANGE_AmlGetMeasuresHandle -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_AmlMeasuresCallback measures_cb; - - /** - * Closure for @e measures_cb. - */ - void *measures_cb_cls; - - /** - * HTTP headers for the job. - */ - struct curl_slist *job_headers; - - /** - * Head of scrap list. - */ - struct Scrap *scrap_head; - - /** - * Tail of scrap list. - */ - struct Scrap *scrap_tail; -}; - - -/** - * Parse AML measures. - * - * @param jroots JSON object with measure data - * @param[out] roots where to write the result - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -parse_aml_roots ( - const json_t *jroots, - struct TALER_EXCHANGE_AvailableAmlMeasures *roots) -{ - const json_t *obj; - const char *name; - size_t idx = 0; - - json_object_foreach ((json_t *) jroots, name, obj) - { - struct TALER_EXCHANGE_AvailableAmlMeasures *root - = &roots[idx++]; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("check_name", - &root->check_name), - GNUNET_JSON_spec_string ("prog_name", - &root->prog_name), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_object_const ("context", - &root->context), - NULL), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (obj, - spec, - NULL, - NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - root->measure_name = name; - } - return GNUNET_OK; -} - - -/** - * Create array of length @a len in scrap book. - * - * @param[in,out] lh context for allocations - * @param len length of array - * @return scrap array - */ -static const char ** -make_scrap ( - struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh, - unsigned int len) -{ - struct Scrap *s = GNUNET_new (struct Scrap); - - s->ptr = GNUNET_new_array (len, - const char *); - GNUNET_CONTAINER_DLL_insert (lh->scrap_head, - lh->scrap_tail, - s); - return s->ptr; -} - - -/** - * Free all scrap space. - * - * @param[in,out] lh scrap context - */ -static void -free_scrap (struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh) -{ - struct Scrap *s; - - while (NULL != (s = lh->scrap_head)) - { - GNUNET_CONTAINER_DLL_remove (lh->scrap_head, - lh->scrap_tail, - s); - GNUNET_free (s->ptr); - GNUNET_free (s); - } -} - - -/** - * Convert JSON array of strings to string array. - * - * @param j JSON array to convert - * @param[out] a array to initialize - * @return true on success - */ -static bool -j_to_a (const json_t *j, - const char **a) -{ - const json_t *e; - size_t idx; - - json_array_foreach ((json_t *) j, idx, e) - { - if (NULL == (a[idx] = json_string_value (e))) - return false; - } - return true; -} - - -/** - * Parse AML programs. - * - * @param[in,out] lh context for allocations - * @param jprogs JSON object with program data - * @param[out] progs where to write the result - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -parse_aml_programs ( - struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh, - const json_t *jprogs, - struct TALER_EXCHANGE_AvailableAmlPrograms *progs) -{ - const json_t *obj; - const char *name; - size_t idx = 0; - - json_object_foreach ((json_t *) jprogs, name, obj) - { - struct TALER_EXCHANGE_AvailableAmlPrograms *prog - = &progs[idx++]; - const json_t *jcontext; - const json_t *jinputs; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("description", - &prog->description), - GNUNET_JSON_spec_array_const ("context", - &jcontext), - GNUNET_JSON_spec_array_const ("inputs", - &jinputs), - GNUNET_JSON_spec_end () - }; - unsigned int len; - const char **ptr; - - if (GNUNET_OK != - GNUNET_JSON_parse (obj, - spec, - NULL, - NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - prog->prog_name = name; - prog->contexts_length - = (unsigned int) json_array_size (jcontext); - prog->inputs_length - = (unsigned int) json_array_size (jinputs); - len = prog->contexts_length + prog->inputs_length; - if ( ((unsigned long long) len) != - (unsigned long long) json_array_size (jcontext) - + (unsigned long long) json_array_size (jinputs) ) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - ptr = make_scrap (lh, - len); - prog->contexts = ptr; - if (! j_to_a (jcontext, - prog->contexts)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - prog->inputs = &ptr[prog->contexts_length]; - if (! j_to_a (jinputs, - prog->inputs)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - } - return GNUNET_OK; -} - - -/** - * Parse AML measures. - * - * @param[in,out] lh context for allocations - * @param jchecks JSON object with measure data - * @param[out] checks where to write the result - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -parse_aml_checks ( - struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh, - const json_t *jchecks, - struct TALER_EXCHANGE_AvailableKycChecks *checks) -{ - const json_t *obj; - const char *name; - size_t idx = 0; - - json_object_foreach ((json_t *) jchecks, name, obj) - { - struct TALER_EXCHANGE_AvailableKycChecks *check - = &checks[idx++]; - const json_t *jrequires; - const json_t *joutputs; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("description", - &check->description), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_object_const ("description_i18n", - &check->description_i18n), - NULL), - GNUNET_JSON_spec_array_const ("requires", - &jrequires), - GNUNET_JSON_spec_array_const ("outputs", - &joutputs), - GNUNET_JSON_spec_string ("fallback", - &check->fallback), - GNUNET_JSON_spec_end () - }; - unsigned int len; - const char **ptr; - - if (GNUNET_OK != - GNUNET_JSON_parse (obj, - spec, - NULL, - NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - check->check_name = name; - - check->requires_length - = (unsigned int) json_array_size (jrequires); - check->outputs_length - = (unsigned int) json_array_size (joutputs); - len = check->requires_length + check->outputs_length; - if ( ((unsigned long long) len) != - (unsigned long long) json_array_size (jrequires) - + (unsigned long long) json_array_size (joutputs) ) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - ptr = make_scrap (lh, - len); - check->requires = ptr; - if (! j_to_a (jrequires, - check->requires)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - check->outputs = &ptr[check->requires_length]; - if (! j_to_a (joutputs, - check->outputs)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - } - return GNUNET_OK; -} - - -/** - * Parse the provided decision data from the "200 OK" response. - * - * @param[in,out] lh handle (callback may be zero'ed out) - * @param json json reply with the data for one coin - * @return #GNUNET_OK on success, #GNUNET_SYSERR on error - */ -static enum GNUNET_GenericReturnValue -parse_measures_ok (struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh, - const json_t *json) -{ - struct TALER_EXCHANGE_AmlGetMeasuresResponse lr = { - .hr.reply = json, - .hr.http_status = MHD_HTTP_OK - }; - const json_t *jroots; - const json_t *jprograms; - const json_t *jchecks; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_object_const ("roots", - &jroots), - GNUNET_JSON_spec_object_const ("programs", - &jprograms), - GNUNET_JSON_spec_object_const ("checks", - &jchecks), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, - NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - lr.details.ok.roots_length - = (unsigned int) json_object_size (jroots); - lr.details.ok.programs_length - = (unsigned int) json_object_size (jprograms); - lr.details.ok.checks_length - = (unsigned int) json_object_size (jchecks); - if ( ( ((size_t) lr.details.ok.roots_length) - != json_object_size (jroots)) || - ( ((size_t) lr.details.ok.programs_length) - != json_object_size (jprograms)) || - ( ((size_t) lr.details.ok.checks_length) - != json_object_size (jchecks)) ) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - { - struct TALER_EXCHANGE_AvailableAmlMeasures roots[ - GNUNET_NZL (lr.details.ok.roots_length)]; - struct TALER_EXCHANGE_AvailableAmlPrograms progs[ - GNUNET_NZL (lr.details.ok.programs_length)]; - struct TALER_EXCHANGE_AvailableKycChecks checks[ - GNUNET_NZL (lr.details.ok.checks_length)]; - enum GNUNET_GenericReturnValue ret; - - memset (roots, - 0, - sizeof (roots)); - memset (progs, - 0, - sizeof (progs)); - memset (checks, - 0, - sizeof (checks)); - lr.details.ok.roots = roots; - lr.details.ok.programs = progs; - lr.details.ok.checks = checks; - ret = parse_aml_roots (jroots, - roots); - if (GNUNET_OK == ret) - ret = parse_aml_programs (lh, - jprograms, - progs); - if (GNUNET_OK == ret) - ret = parse_aml_checks (lh, - jchecks, - checks); - if (GNUNET_OK == ret) - { - lh->measures_cb (lh->measures_cb_cls, - &lr); - lh->measures_cb = NULL; - } - free_scrap (lh); - return ret; - } -} - - -/** - * Function called when we're done processing the - * HTTP /aml/$OFFICER_PUB/measures request. - * - * @param cls the `struct TALER_EXCHANGE_AmlGetMeasuresHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_lookup_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh = cls; - const json_t *j = response; - struct TALER_EXCHANGE_AmlGetMeasuresResponse lr = { - .hr.reply = j, - .hr.http_status = (unsigned int) response_code - }; - - lh->job = NULL; - switch (response_code) - { - case 0: - lr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - if (GNUNET_OK != - parse_measures_ok (lh, - j)) - { - GNUNET_break_op (0); - lr.hr.http_status = 0; - lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - GNUNET_assert (NULL == lh->measures_cb); - TALER_EXCHANGE_aml_get_measures_cancel (lh); - return; - case MHD_HTTP_NO_CONTENT: - break; - case MHD_HTTP_BAD_REQUEST: - lr.hr.ec = TALER_JSON_get_error_code (j); - lr.hr.hint = TALER_JSON_get_error_hint (j); - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - break; - case MHD_HTTP_FORBIDDEN: - lr.hr.ec = TALER_JSON_get_error_code (j); - lr.hr.hint = TALER_JSON_get_error_hint (j); - /* Nothing really to verify, exchange says this coin was not melted; we - should pass the JSON reply to the application */ - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - lr.hr.ec = TALER_JSON_get_error_code (j); - lr.hr.hint = TALER_JSON_get_error_hint (j); - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - lr.hr.ec = TALER_JSON_get_error_code (j); - lr.hr.hint = TALER_JSON_get_error_hint (j); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for get AML measures\n", - (unsigned int) response_code, - (int) lr.hr.ec); - break; - } - if (NULL != lh->measures_cb) - lh->measures_cb (lh->measures_cb_cls, - &lr); - TALER_EXCHANGE_aml_get_measures_cancel (lh); -} - - -struct TALER_EXCHANGE_AmlGetMeasuresHandle * -TALER_EXCHANGE_aml_get_measures ( - struct GNUNET_CURL_Context *ctx, - const char *exchange_url, - const struct TALER_AmlOfficerPrivateKeyP *officer_priv, - TALER_EXCHANGE_AmlMeasuresCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh; - CURL *eh; - struct TALER_AmlOfficerPublicKeyP officer_pub; - struct TALER_AmlOfficerSignatureP officer_sig; - char arg_str[sizeof (struct TALER_AmlOfficerPublicKeyP) * 2 - + 32]; - - GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv, - &officer_pub.eddsa_pub); - TALER_officer_aml_query_sign (officer_priv, - &officer_sig); - { - char pub_str[sizeof (officer_pub) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string ( - &officer_pub, - sizeof (officer_pub), - pub_str, - sizeof (pub_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "aml/%s/measures", - pub_str); - } - lh = GNUNET_new (struct TALER_EXCHANGE_AmlGetMeasuresHandle); - lh->measures_cb = cb; - lh->measures_cb_cls = cb_cls; - lh->url = TALER_url_join (exchange_url, - arg_str, - NULL); - if (NULL == lh->url) - { - GNUNET_free (lh); - return NULL; - } - eh = TALER_EXCHANGE_curl_easy_get_ (lh->url); - if (NULL == eh) - { - GNUNET_break (0); - GNUNET_free (lh->url); - GNUNET_free (lh); - return NULL; - } - { - char *hdr; - char sig_str[sizeof (officer_sig) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string ( - &officer_sig, - sizeof (officer_sig), - sig_str, - sizeof (sig_str)); - *end = '\0'; - - GNUNET_asprintf (&hdr, - "%s: %s", - TALER_AML_OFFICER_SIGNATURE_HEADER, - sig_str); - lh->job_headers = curl_slist_append (NULL, - hdr); - GNUNET_free (hdr); - lh->job_headers = curl_slist_append (lh->job_headers, - "Content-type: application/json"); - lh->job = GNUNET_CURL_job_add2 (ctx, - eh, - lh->job_headers, - &handle_lookup_finished, - lh); - } - return lh; -} - - -void -TALER_EXCHANGE_aml_get_measures_cancel ( - struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh) -{ - if (NULL != lh->job) - { - GNUNET_CURL_job_cancel (lh->job); - lh->job = NULL; - } - curl_slist_free_all (lh->job_headers); - GNUNET_free (lh->url); - GNUNET_free (lh); -} - - -/* end of exchange_api_get_aml_measures.c */ diff --git a/src/lib/exchange_api_get_kyc_statistics.c b/src/lib/exchange_api_get_kyc_statistics.c @@ -1,317 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2023, 2024 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_get_kyc_statistics.c - * @brief Implementation of the /aml/$OFFICER_PUB/kyc-statistics/$NAME request - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_exchange_service.h" -#include "taler/taler_json_lib.h" -#include "exchange_api_handle.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * @brief A GET /aml/$OFFICER_PUB/kyc-statistics/$NAME Handle - */ -struct TALER_EXCHANGE_KycGetStatisticsHandle -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_KycStatisticsCallback stats_cb; - - /** - * Closure for @e cb. - */ - void *stats_cb_cls; - - /** - * HTTP headers for the job. - */ - struct curl_slist *job_headers; - -}; - - -/** - * Parse the provided decision data from the "200 OK" response. - * - * @param[in,out] lh handle (callback may be zero'ed out) - * @param json json reply with the data for one coin - * @return #GNUNET_OK on success, #GNUNET_SYSERR on error - */ -static enum GNUNET_GenericReturnValue -parse_stats_ok ( - struct TALER_EXCHANGE_KycGetStatisticsHandle *lh, - const json_t *json) -{ - struct TALER_EXCHANGE_KycGetStatisticsResponse lr = { - .hr.reply = json, - .hr.http_status = MHD_HTTP_OK - }; - uint64_t cnt; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_uint64 ("counter", - &cnt), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, - NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - lr.details.ok.counter = (unsigned long long) cnt; - lh->stats_cb (lh->stats_cb_cls, - &lr); - lh->stats_cb = NULL; - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /aml/$OFFICER_PUB/kyc-statistics/$NAME request. - * - * @param cls the `struct TALER_EXCHANGE_KycGetStatisticsHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_lookup_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_KycGetStatisticsHandle *lh = cls; - const json_t *j = response; - struct TALER_EXCHANGE_KycGetStatisticsResponse lr = { - .hr.reply = j, - .hr.http_status = (unsigned int) response_code - }; - - lh->job = NULL; - switch (response_code) - { - case 0: - lr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - if (GNUNET_OK != - parse_stats_ok (lh, - j)) - { - GNUNET_break_op (0); - lr.hr.http_status = 0; - lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - GNUNET_assert (NULL == lh->stats_cb); - TALER_EXCHANGE_kyc_get_statistics_cancel (lh); - return; - case MHD_HTTP_NO_CONTENT: - break; - case MHD_HTTP_BAD_REQUEST: - lr.hr.ec = TALER_JSON_get_error_code (j); - lr.hr.hint = TALER_JSON_get_error_hint (j); - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - break; - case MHD_HTTP_FORBIDDEN: - lr.hr.ec = TALER_JSON_get_error_code (j); - lr.hr.hint = TALER_JSON_get_error_hint (j); - /* Nothing really to verify, exchange says this coin was not melted; we - should pass the JSON reply to the application */ - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - lr.hr.ec = TALER_JSON_get_error_code (j); - lr.hr.hint = TALER_JSON_get_error_hint (j); - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - lr.hr.ec = TALER_JSON_get_error_code (j); - lr.hr.hint = TALER_JSON_get_error_hint (j); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for GET KYC statistics\n", - (unsigned int) response_code, - (int) lr.hr.ec); - break; - } - if (NULL != lh->stats_cb) - lh->stats_cb (lh->stats_cb_cls, - &lr); - TALER_EXCHANGE_kyc_get_statistics_cancel (lh); -} - - -struct TALER_EXCHANGE_KycGetStatisticsHandle * -TALER_EXCHANGE_kyc_get_statistics ( - struct GNUNET_CURL_Context *ctx, - const char *exchange_url, - const char *name, - struct GNUNET_TIME_Timestamp start_date, - struct GNUNET_TIME_Timestamp end_date, - const struct TALER_AmlOfficerPrivateKeyP *officer_priv, - TALER_EXCHANGE_KycStatisticsCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_KycGetStatisticsHandle *lh; - CURL *eh; - struct TALER_AmlOfficerPublicKeyP officer_pub; - struct TALER_AmlOfficerSignatureP officer_sig; - char arg_str[sizeof (struct TALER_AmlOfficerPublicKeyP) * 2 - + 32]; - char sd_str[32]; - char ed_str[32]; - const char *sd = NULL; - const char *ed = NULL; - - if (! GNUNET_TIME_absolute_is_zero (start_date.abs_time)) - { - unsigned long long sec; - - sec = (unsigned long long) GNUNET_TIME_timestamp_to_s (start_date); - GNUNET_snprintf (sd_str, - sizeof (sd_str), - "%llu", - sec); - sd = sd_str; - } - if (! GNUNET_TIME_absolute_is_never (end_date.abs_time)) - { - unsigned long long sec; - - sec = (unsigned long long) GNUNET_TIME_timestamp_to_s (end_date); - GNUNET_snprintf (ed_str, - sizeof (ed_str), - "%llu", - sec); - ed = ed_str; - } - GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv, - &officer_pub.eddsa_pub); - TALER_officer_aml_query_sign (officer_priv, - &officer_sig); - { - char pub_str[sizeof (officer_pub) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string ( - &officer_pub, - sizeof (officer_pub), - pub_str, - sizeof (pub_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "aml/%s/kyc-statistics/%s", - pub_str, - name); - } - lh = GNUNET_new (struct TALER_EXCHANGE_KycGetStatisticsHandle); - lh->stats_cb = cb; - lh->stats_cb_cls = cb_cls; - lh->url = TALER_url_join (exchange_url, - arg_str, - "start_date", - sd, - "end_date", - ed, - NULL); - if (NULL == lh->url) - { - GNUNET_free (lh); - return NULL; - } - eh = TALER_EXCHANGE_curl_easy_get_ (lh->url); - if (NULL == eh) - { - GNUNET_break (0); - GNUNET_free (lh->url); - GNUNET_free (lh); - return NULL; - } - { - char *hdr; - char sig_str[sizeof (officer_sig) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string ( - &officer_sig, - sizeof (officer_sig), - sig_str, - sizeof (sig_str)); - *end = '\0'; - - GNUNET_asprintf (&hdr, - "%s: %s", - TALER_AML_OFFICER_SIGNATURE_HEADER, - sig_str); - lh->job_headers = curl_slist_append (NULL, - hdr); - GNUNET_free (hdr); - lh->job_headers = curl_slist_append (lh->job_headers, - "Content-type: application/json"); - lh->job = GNUNET_CURL_job_add2 (ctx, - eh, - lh->job_headers, - &handle_lookup_finished, - lh); - } - return lh; -} - - -void -TALER_EXCHANGE_kyc_get_statistics_cancel ( - struct TALER_EXCHANGE_KycGetStatisticsHandle *lh) -{ - if (NULL != lh->job) - { - GNUNET_CURL_job_cancel (lh->job); - lh->job = NULL; - } - curl_slist_free_all (lh->job_headers); - GNUNET_free (lh->url); - GNUNET_free (lh); -} - - -/* end of exchange_api_get_kyc_statistics.c */ diff --git a/src/lib/exchange_api_kyc_check.c b/src/lib/exchange_api_kyc_check.c @@ -1,415 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2021-2024 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_kyc_check.c - * @brief Implementation of the /kyc-check request - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include <microhttpd.h> /* just for HTTP check codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_exchange_service.h" -#include "taler/taler_json_lib.h" -#include "exchange_api_handle.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * @brief A ``/kyc-check`` handle - */ -struct TALER_EXCHANGE_KycCheckHandle -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_KycStatusCallback cb; - - /** - * Closure for @e cb. - */ - void *cb_cls; - -}; - - -static enum GNUNET_GenericReturnValue -parse_account_status ( - struct TALER_EXCHANGE_KycCheckHandle *kch, - const json_t *j, - struct TALER_EXCHANGE_KycStatus *ks, - struct TALER_EXCHANGE_AccountKycStatus *status) -{ - const json_t *limits = NULL; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_bool ("aml_review", - &status->aml_review), - GNUNET_JSON_spec_uint64 ("rule_gen", - &status->rule_gen), - GNUNET_JSON_spec_fixed_auto ("access_token", - &status->access_token), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_array_const ("limits", - &limits), - NULL), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if ( (NULL != limits) && - (0 != json_array_size (limits)) ) - { - size_t limit_length = json_array_size (limits); - struct TALER_EXCHANGE_AccountLimit ala[GNUNET_NZL (limit_length)]; - size_t i; - json_t *limit; - - json_array_foreach (limits, i, limit) - { - struct TALER_EXCHANGE_AccountLimit *al = &ala[i]; - struct GNUNET_JSON_Specification ispec[] = { - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_bool ("soft_limit", - &al->soft_limit), - NULL), - GNUNET_JSON_spec_relative_time ("timeframe", - &al->timeframe), - TALER_JSON_spec_kycte ("operation_type", - &al->operation_type), - TALER_JSON_spec_amount_any ("threshold", - &al->threshold), - GNUNET_JSON_spec_end () - }; - - al->soft_limit = false; - if (GNUNET_OK != - GNUNET_JSON_parse (limit, - ispec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - } - status->limits = ala; - status->limits_length = limit_length; - kch->cb (kch->cb_cls, - ks); - } - else - { - kch->cb (kch->cb_cls, - ks); - } - GNUNET_JSON_parse_free (spec); - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /kyc-check request. - * - * @param cls the `struct TALER_EXCHANGE_KycCheckHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_kyc_check_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_KycCheckHandle *kch = cls; - const json_t *j = response; - struct TALER_EXCHANGE_KycStatus ks = { - .hr.reply = j, - .hr.http_status = (unsigned int) response_code - }; - - kch->job = NULL; - switch (response_code) - { - case 0: - ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - { - if (GNUNET_OK != - parse_account_status (kch, - j, - &ks, - &ks.details.ok)) - { - GNUNET_break_op (0); - ks.hr.http_status = 0; - ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - } - TALER_EXCHANGE_kyc_check_cancel (kch); - return; - } - case MHD_HTTP_ACCEPTED: - { - if (GNUNET_OK != - parse_account_status (kch, - j, - &ks, - &ks.details.accepted)) - { - GNUNET_break_op (0); - ks.hr.http_status = 0; - ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - } - TALER_EXCHANGE_kyc_check_cancel (kch); - return; - } - case MHD_HTTP_NO_CONTENT: - break; - case MHD_HTTP_BAD_REQUEST: - ks.hr.ec = TALER_JSON_get_error_code (j); - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - break; - case MHD_HTTP_FORBIDDEN: - { - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ( - "expected_account_pub", - &ks.details.forbidden.expected_account_pub), - TALER_JSON_spec_ec ("code", - &ks.hr.ec), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - ks.hr.http_status = 0; - ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - } - break; - } - case MHD_HTTP_NOT_FOUND: - ks.hr.ec = TALER_JSON_get_error_code (j); - break; - case MHD_HTTP_CONFLICT: - ks.hr.ec = TALER_JSON_get_error_code (j); - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - ks.hr.ec = TALER_JSON_get_error_code (j); - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - ks.hr.ec = TALER_JSON_get_error_code (j); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange kyc_check\n", - (unsigned int) response_code, - (int) ks.hr.ec); - break; - } - kch->cb (kch->cb_cls, - &ks); - TALER_EXCHANGE_kyc_check_cancel (kch); -} - - -struct TALER_EXCHANGE_KycCheckHandle * -TALER_EXCHANGE_kyc_check ( - struct GNUNET_CURL_Context *ctx, - const char *url, - const struct TALER_NormalizedPaytoHashP *h_payto, - const union TALER_AccountPrivateKeyP *account_priv, - uint64_t known_rule_gen, - enum TALER_EXCHANGE_KycLongPollTarget lpt, - struct GNUNET_TIME_Relative timeout, - TALER_EXCHANGE_KycStatusCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_KycCheckHandle *kch; - CURL *eh; - char arg_str[128]; - char timeout_ms[32]; - char lpt_str[32]; - char krg_str[32]; - struct curl_slist *job_headers = NULL; - unsigned long long tms; - - { - char *hps; - - hps = GNUNET_STRINGS_data_to_string_alloc ( - h_payto, - sizeof (*h_payto)); - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "kyc-check/%s", - hps); - GNUNET_free (hps); - } - tms = timeout.rel_value_us - / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us; - GNUNET_snprintf (timeout_ms, - sizeof (timeout_ms), - "%llu", - tms); - GNUNET_snprintf (krg_str, - sizeof (krg_str), - "%llu", - (unsigned long long) known_rule_gen); - GNUNET_snprintf (lpt_str, - sizeof (lpt_str), - "%d", - (int) lpt); - kch = GNUNET_new (struct TALER_EXCHANGE_KycCheckHandle); - kch->cb = cb; - kch->cb_cls = cb_cls; - kch->url - = TALER_url_join ( - url, - arg_str, - "timeout_ms", - GNUNET_TIME_relative_is_zero (timeout) - ? NULL - : timeout_ms, - "min_rule", - 0 == known_rule_gen - ? NULL - : krg_str, - "lpt", - TALER_EXCHANGE_KLPT_NONE == lpt - ? NULL - : lpt_str, - NULL); - if (NULL == kch->url) - { - GNUNET_free (kch); - return NULL; - } - eh = TALER_EXCHANGE_curl_easy_get_ (kch->url); - if (NULL == eh) - { - GNUNET_break (0); - GNUNET_free (kch->url); - GNUNET_free (kch); - return NULL; - } - if (0 != tms) - { - GNUNET_break (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_TIMEOUT_MS, - (long) (tms + 500L))); - } - job_headers - = curl_slist_append ( - job_headers, - "Content-Type: application/json"); - { - union TALER_AccountPublicKeyP account_pub; - union TALER_AccountSignatureP account_sig; - char *sig_hdr; - char *pub_hdr; - char *hdr; - - GNUNET_CRYPTO_eddsa_key_get_public ( - &account_priv->reserve_priv.eddsa_priv, - &account_pub.reserve_pub.eddsa_pub); - TALER_account_kyc_auth_sign (account_priv, - &account_sig); - - sig_hdr = GNUNET_STRINGS_data_to_string_alloc ( - &account_sig, - sizeof (account_sig)); - GNUNET_asprintf (&hdr, - "%s: %s", - TALER_HTTP_HEADER_ACCOUNT_OWNER_SIGNATURE, - sig_hdr); - GNUNET_free (sig_hdr); - job_headers = curl_slist_append (job_headers, - hdr); - GNUNET_free (hdr); - - pub_hdr = GNUNET_STRINGS_data_to_string_alloc ( - &account_pub, - sizeof (account_pub)); - GNUNET_asprintf (&hdr, - "%s: %s", - TALER_HTTP_HEADER_ACCOUNT_OWNER_PUBKEY, - pub_hdr); - GNUNET_free (pub_hdr); - job_headers = curl_slist_append (job_headers, - hdr); - GNUNET_free (hdr); - if (NULL == job_headers) - { - GNUNET_break (0); - curl_easy_cleanup (eh); - return NULL; - } - } - kch->job - = GNUNET_CURL_job_add2 (ctx, - eh, - job_headers, - &handle_kyc_check_finished, - kch); - curl_slist_free_all (job_headers); - return kch; -} - - -void -TALER_EXCHANGE_kyc_check_cancel (struct TALER_EXCHANGE_KycCheckHandle *kch) -{ - if (NULL != kch->job) - { - GNUNET_CURL_job_cancel (kch->job); - kch->job = NULL; - } - GNUNET_free (kch->url); - GNUNET_free (kch); -} - - -/* end of exchange_api_kyc_check.c */ diff --git a/src/lib/exchange_api_kyc_info.c b/src/lib/exchange_api_kyc_info.c @@ -1,392 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2015-2023 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_kyc_info.c - * @brief Implementation of the /kyc-info/$AT request - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_exchange_service.h" -#include "taler/taler_json_lib.h" -#include "exchange_api_handle.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * @brief A /kyc-info Handle - */ -struct TALER_EXCHANGE_KycInfoHandle -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_KycInfoCallback kyc_info_cb; - - /** - * Closure for @e cb. - */ - void *kyc_info_cb_cls; - -}; - - -/** - * Parse the provided kyc_infoage data from the "200 OK" response - * for one of the coins. - * - * @param[in,out] lh kyc_info handle (callback may be zero'ed out) - * @param json json reply with the data for one coin - * @return #GNUNET_OK on success, #GNUNET_SYSERR on error - */ -static enum GNUNET_GenericReturnValue -parse_kyc_info_ok (struct TALER_EXCHANGE_KycInfoHandle *lh, - const json_t *json) -{ - const json_t *jrequirements = NULL; - const json_t *jvoluntary_checks = NULL; - struct TALER_EXCHANGE_KycProcessClientInformation lr = { - .hr.reply = json, - .hr.http_status = MHD_HTTP_OK - }; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_array_const ("requirements", - &jrequirements), - GNUNET_JSON_spec_bool ("is_and_combinator", - &lr.details.ok.is_and_combinator), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_object_const ("voluntary_checks", - &jvoluntary_checks), - NULL), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - lr.details.ok.vci_length - = (unsigned int) json_object_size (jvoluntary_checks); - if ( ((size_t) lr.details.ok.vci_length) - != json_object_size (jvoluntary_checks)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - lr.details.ok.requirements_length - = json_array_size (jrequirements); - if ( ((size_t) lr.details.ok.requirements_length) - != json_array_size (jrequirements)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - { - struct TALER_EXCHANGE_VoluntaryCheckInformation vci[ - GNUNET_NZL (lr.details.ok.vci_length)]; - struct TALER_EXCHANGE_RequirementInformation requirements[ - GNUNET_NZL (lr.details.ok.requirements_length)]; - const char *name; - const json_t *jreq; - const json_t *jvc; - size_t off; - - memset (vci, - 0, - sizeof (vci)); - memset (requirements, - 0, - sizeof (requirements)); - - json_array_foreach ((json_t *) jrequirements, off, jreq) - { - struct TALER_EXCHANGE_RequirementInformation *req = &requirements[off]; - struct GNUNET_JSON_Specification ispec[] = { - GNUNET_JSON_spec_string ("form", - &req->form), - GNUNET_JSON_spec_string ("description", - &req->description), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_object_const ("description_i18n", - &req->description_i18n), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("id", - &req->id), - NULL), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (jreq, - ispec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - } - GNUNET_assert (off == lr.details.ok.requirements_length); - - off = 0; - json_object_foreach ((json_t *) jvoluntary_checks, name, jvc) - { - struct TALER_EXCHANGE_VoluntaryCheckInformation *vc = &vci[off++]; - struct GNUNET_JSON_Specification ispec[] = { - GNUNET_JSON_spec_string ("description", - &vc->description), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_object_const ("description_i18n", - &vc->description_i18n), - NULL), - GNUNET_JSON_spec_end () - }; - - vc->name = name; - if (GNUNET_OK != - GNUNET_JSON_parse (jvc, - ispec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - } - GNUNET_assert (off == lr.details.ok.vci_length); - - lr.details.ok.vci = vci; - lr.details.ok.requirements = requirements; - lh->kyc_info_cb (lh->kyc_info_cb_cls, - &lr); - lh->kyc_info_cb = NULL; - return GNUNET_OK; - } -} - - -/** - * Function called when we're done processing the - * HTTP /kyc-info/$AT request. - * - * @param cls the `struct TALER_EXCHANGE_KycInfoHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_kyc_info_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_KycInfoHandle *lh = cls; - const json_t *j = response; - struct TALER_EXCHANGE_KycProcessClientInformation lr = { - .hr.reply = j, - .hr.http_status = (unsigned int) response_code - }; - - lh->job = NULL; - switch (response_code) - { - case 0: - lr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - if (GNUNET_OK != - parse_kyc_info_ok (lh, - j)) - { - GNUNET_break_op (0); - lr.hr.http_status = 0; - lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - GNUNET_assert (NULL == lh->kyc_info_cb); - TALER_EXCHANGE_kyc_info_cancel (lh); - return; - case MHD_HTTP_NO_CONTENT: - break; - case MHD_HTTP_BAD_REQUEST: - lr.hr.ec = TALER_JSON_get_error_code (j); - lr.hr.hint = TALER_JSON_get_error_hint (j); - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - break; - case MHD_HTTP_FORBIDDEN: - lr.hr.ec = TALER_JSON_get_error_code (j); - lr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_NOT_FOUND: - lr.hr.ec = TALER_JSON_get_error_code (j); - lr.hr.hint = TALER_JSON_get_error_hint (j); - /* Nothing really to verify, exchange says this coin was not melted; we - should pass the JSON reply to the application */ - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - lr.hr.ec = TALER_JSON_get_error_code (j); - lr.hr.hint = TALER_JSON_get_error_hint (j); - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - lr.hr.ec = TALER_JSON_get_error_code (j); - lr.hr.hint = TALER_JSON_get_error_hint (j); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange /kyc-info\n", - (unsigned int) response_code, - (int) lr.hr.ec); - break; - } - if (NULL != lh->kyc_info_cb) - lh->kyc_info_cb (lh->kyc_info_cb_cls, - &lr); - TALER_EXCHANGE_kyc_info_cancel (lh); -} - - -struct TALER_EXCHANGE_KycInfoHandle * -TALER_EXCHANGE_kyc_info ( - struct GNUNET_CURL_Context *ctx, - const char *url, - const struct TALER_AccountAccessTokenP *token, - const char *if_none_match, - struct GNUNET_TIME_Relative timeout, - TALER_EXCHANGE_KycInfoCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_KycInfoHandle *lh; - CURL *eh; - char arg_str[sizeof (struct TALER_AccountAccessTokenP) * 2 + 32]; - unsigned int tms - = (unsigned int) timeout.rel_value_us - / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us; - struct curl_slist *job_headers = NULL; - - { - char at_str[sizeof (struct TALER_AccountAccessTokenP) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string ( - token, - sizeof (*token), - at_str, - sizeof (at_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "kyc-info/%s", - at_str); - } - lh = GNUNET_new (struct TALER_EXCHANGE_KycInfoHandle); - lh->kyc_info_cb = cb; - lh->kyc_info_cb_cls = cb_cls; - { - char timeout_str[32]; - - GNUNET_snprintf (timeout_str, - sizeof (timeout_str), - "%u", - tms); - lh->url = TALER_url_join (url, - arg_str, - "timeout_ms", - (0 == tms) - ? NULL - : timeout_str, - NULL); - } - if (NULL == lh->url) - { - GNUNET_free (lh); - return NULL; - } - eh = TALER_EXCHANGE_curl_easy_get_ (lh->url); - if (NULL == eh) - { - GNUNET_break (0); - GNUNET_free (lh->url); - GNUNET_free (lh); - return NULL; - } - if (0 != tms) - { - GNUNET_break (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_TIMEOUT_MS, - (long) (tms + 100L))); - } - - job_headers = curl_slist_append (job_headers, - "Content-Type: application/json"); - if (NULL != if_none_match) - { - char *hdr; - - GNUNET_asprintf (&hdr, - "%s: %s", - MHD_HTTP_HEADER_IF_NONE_MATCH, - if_none_match); - job_headers = curl_slist_append (job_headers, - hdr); - GNUNET_free (hdr); - } - lh->job = GNUNET_CURL_job_add2 (ctx, - eh, - job_headers, - &handle_kyc_info_finished, - lh); - curl_slist_free_all (job_headers); - return lh; -} - - -void -TALER_EXCHANGE_kyc_info_cancel (struct TALER_EXCHANGE_KycInfoHandle *lh) -{ - if (NULL != lh->job) - { - GNUNET_CURL_job_cancel (lh->job); - lh->job = NULL; - } - - GNUNET_free (lh->url); - GNUNET_free (lh); -} - - -/* end of exchange_api_kyc_info.c */ diff --git a/src/lib/exchange_api_kyc_proof.c b/src/lib/exchange_api_kyc_proof.c @@ -1,217 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2021, 2022 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_kyc_proof.c - * @brief Implementation of the /kyc-proof request - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include <microhttpd.h> /* just for HTTP proof codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_exchange_service.h" -#include "taler/taler_json_lib.h" -#include "exchange_api_handle.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * @brief A ``/kyc-proof`` handle - */ -struct TALER_EXCHANGE_KycProofHandle -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Handle to our CURL request. - */ - CURL *eh; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_KycProofCallback cb; - - /** - * Closure for @e cb. - */ - void *cb_cls; - -}; - - -/** - * Function called when we're done processing the - * HTTP /kyc-proof request. - * - * @param cls the `struct TALER_EXCHANGE_KycProofHandle` - * @param response_code HTTP response code, 0 on error - * @param body response body - * @param body_size number of bytes in @a body - */ -static void -handle_kyc_proof_finished (void *cls, - long response_code, - const void *body, - size_t body_size) -{ - struct TALER_EXCHANGE_KycProofHandle *kph = cls; - struct TALER_EXCHANGE_KycProofResponse kpr = { - .hr.http_status = (unsigned int) response_code - }; - - (void) body; - (void) body_size; - kph->job = NULL; - switch (response_code) - { - case 0: - break; - case MHD_HTTP_SEE_OTHER: - { - char *redirect_url; - - GNUNET_assert (CURLE_OK == - curl_easy_getinfo (kph->eh, - CURLINFO_REDIRECT_URL, - &redirect_url)); - kpr.details.found.redirect_url = redirect_url; - break; - } - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - break; - case MHD_HTTP_UNAUTHORIZED: - break; - case MHD_HTTP_FORBIDDEN: - break; - case MHD_HTTP_NOT_FOUND: - break; - case MHD_HTTP_BAD_GATEWAY: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - case MHD_HTTP_GATEWAY_TIMEOUT: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u for exchange kyc_proof\n", - (unsigned int) response_code); - break; - } - kph->cb (kph->cb_cls, - &kpr); - TALER_EXCHANGE_kyc_proof_cancel (kph); -} - - -struct TALER_EXCHANGE_KycProofHandle * -TALER_EXCHANGE_kyc_proof ( - struct GNUNET_CURL_Context *ctx, - const char *url, - const struct TALER_NormalizedPaytoHashP *h_payto, - const char *logic, - const char *args, - TALER_EXCHANGE_KycProofCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_KycProofHandle *kph; - char *arg_str; - - if (NULL == args) - args = ""; - else - GNUNET_assert (args[0] == '&'); - { - char hstr[sizeof (*h_payto) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string (h_payto, - sizeof (*h_payto), - hstr, - sizeof (hstr)); - *end = '\0'; - GNUNET_asprintf (&arg_str, - "kyc-proof/%s?state=%s%s", - logic, - hstr, - args); - } - kph = GNUNET_new (struct TALER_EXCHANGE_KycProofHandle); - kph->cb = cb; - kph->cb_cls = cb_cls; - kph->url = TALER_url_join (url, - arg_str, - NULL); - GNUNET_free (arg_str); - if (NULL == kph->url) - { - GNUNET_free (kph); - return NULL; - } - kph->eh = TALER_EXCHANGE_curl_easy_get_ (kph->url); - if (NULL == kph->eh) - { - GNUNET_break (0); - GNUNET_free (kph->url); - GNUNET_free (kph); - return NULL; - } - /* disable location following, we want to learn the - result of a 303 redirect! */ - GNUNET_assert (CURLE_OK == - curl_easy_setopt (kph->eh, - CURLOPT_FOLLOWLOCATION, - 0L)); - kph->job = GNUNET_CURL_job_add_raw (ctx, - kph->eh, - NULL, - &handle_kyc_proof_finished, - kph); - return kph; -} - - -void -TALER_EXCHANGE_kyc_proof_cancel (struct TALER_EXCHANGE_KycProofHandle *kph) -{ - if (NULL != kph->job) - { - GNUNET_CURL_job_cancel (kph->job); - kph->job = NULL; - } - GNUNET_free (kph->url); - GNUNET_free (kph); -} - - -/* end of exchange_api_kyc_proof.c */ diff --git a/src/lib/exchange_api_kyc_start.c b/src/lib/exchange_api_kyc_start.c @@ -1,220 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2024 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_kyc_start.c - * @brief functions to start a KYC process - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include "taler/taler_json_lib.h" -#include <microhttpd.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_exchange_service.h" -#include "exchange_api_curl_defaults.h" -#include "taler/taler_signatures.h" -#include "taler/taler_curl_lib.h" -#include "taler/taler_json_lib.h" - - -struct TALER_EXCHANGE_KycStartHandle -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Minor context that holds body and headers. - */ - struct TALER_CURL_PostContext post_ctx; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_KycStartCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Reference to the execution context. - */ - struct GNUNET_CURL_Context *ctx; -}; - - -/** - * Function called when we're done processing the - * HTTP POST /kyc-start/$ID request. - * - * @param cls the `struct TALER_EXCHANGE_KycStartHandle *` - * @param response_code HTTP response code, 0 on error - * @param response response body, NULL if not in JSON - */ -static void -handle_kyc_start_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_KycStartHandle *wh = cls; - const json_t *json = response; - struct TALER_EXCHANGE_KycStartResponse adr = { - .hr.http_status = (unsigned int) response_code, - .hr.reply = json - }; - - wh->job = NULL; - switch (response_code) - { - case 0: - /* no reply */ - adr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - adr.hr.hint = "server offline?"; - break; - case MHD_HTTP_OK: - { - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ( - "redirect_url", - &adr.details.ok.redirect_url), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - adr.hr.http_status = 0; - adr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - } - break; - case MHD_HTTP_NOT_FOUND: - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - adr.hr.ec = TALER_JSON_get_error_code (json); - adr.hr.hint = TALER_JSON_get_error_hint (json); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange AML decision\n", - (unsigned int) response_code, - (int) adr.hr.ec); - break; - } - if (NULL != wh->cb) - { - wh->cb (wh->cb_cls, - &adr); - wh->cb = NULL; - } - TALER_EXCHANGE_kyc_start_cancel (wh); -} - - -struct TALER_EXCHANGE_KycStartHandle * -TALER_EXCHANGE_kyc_start ( - struct GNUNET_CURL_Context *ctx, - const char *url, - const char *id, - TALER_EXCHANGE_KycStartCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_KycStartHandle *wh; - CURL *eh; - json_t *body; - - wh = GNUNET_new (struct TALER_EXCHANGE_KycStartHandle); - wh->cb = cb; - wh->cb_cls = cb_cls; - wh->ctx = ctx; - { - char *path; - - GNUNET_asprintf (&path, - "kyc-start/%s", - id); - wh->url = TALER_url_join (url, - path, - NULL); - GNUNET_free (path); - } - if (NULL == wh->url) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Could not construct request URL.\n"); - GNUNET_free (wh); - return NULL; - } - body = json_object (); /* as per spec: empty! */ - GNUNET_assert (NULL != body); - eh = TALER_EXCHANGE_curl_easy_get_ (wh->url); - if ( (NULL == eh) || - (GNUNET_OK != - TALER_curl_easy_post (&wh->post_ctx, - eh, - body)) ) - { - GNUNET_break (0); - if (NULL != eh) - curl_easy_cleanup (eh); - json_decref (body); - GNUNET_free (wh->url); - return NULL; - } - json_decref (body); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Requesting URL '%s'\n", - wh->url); - wh->job = GNUNET_CURL_job_add2 (ctx, - eh, - wh->post_ctx.headers, - &handle_kyc_start_finished, - wh); - if (NULL == wh->job) - { - TALER_EXCHANGE_kyc_start_cancel (wh); - return NULL; - } - return wh; -} - - -void -TALER_EXCHANGE_kyc_start_cancel ( - struct TALER_EXCHANGE_KycStartHandle *wh) -{ - if (NULL != wh->job) - { - GNUNET_CURL_job_cancel (wh->job); - wh->job = NULL; - } - TALER_curl_easy_post_finished (&wh->post_ctx); - GNUNET_free (wh->url); - GNUNET_free (wh); -} diff --git a/src/lib/exchange_api_kyc_wallet.c b/src/lib/exchange_api_kyc_wallet.c @@ -1,257 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2021 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_kyc_wallet.c - * @brief Implementation of the /kyc-wallet request - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include <microhttpd.h> /* just for HTTP wallet codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_exchange_service.h" -#include "taler/taler_json_lib.h" -#include "exchange_api_handle.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * @brief A ``/kyc-wallet`` handle - */ -struct TALER_EXCHANGE_KycWalletHandle -{ - - /** - * Context for #TEH_curl_easy_post(). Keeps the data that must - * persist for Curl to make the upload. - */ - struct TALER_CURL_PostContext ctx; - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_KycWalletCallback cb; - - /** - * Closure for @e cb. - */ - void *cb_cls; - -}; - - -/** - * Function called when we're done processing the - * HTTP /kyc-wallet request. - * - * @param cls the `struct TALER_EXCHANGE_KycWalletHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_kyc_wallet_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_KycWalletHandle *kwh = cls; - const json_t *j = response; - struct TALER_EXCHANGE_WalletKycResponse ks = { - .hr.reply = j, - .hr.http_status = (unsigned int) response_code - }; - - kwh->job = NULL; - switch (response_code) - { - case 0: - ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - { - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_mark_optional ( - TALER_JSON_spec_amount_any ( - "next_threshold", - &ks.details.ok.next_threshold), - NULL), - GNUNET_JSON_spec_timestamp ( - "expiration_time", - &ks.details.ok.expiration_time), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - ks.hr.http_status = 0; - ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - } - break; - } - case MHD_HTTP_NO_CONTENT: - break; - case MHD_HTTP_BAD_REQUEST: - ks.hr.ec = TALER_JSON_get_error_code (j); - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - break; - case MHD_HTTP_FORBIDDEN: - ks.hr.ec = TALER_JSON_get_error_code (j); - break; - case MHD_HTTP_NOT_FOUND: - ks.hr.ec = TALER_JSON_get_error_code (j); - break; - case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: - { - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ( - "h_payto", - &ks.details.unavailable_for_legal_reasons.h_payto), - GNUNET_JSON_spec_uint64 ( - "requirement_row", - &ks.details.unavailable_for_legal_reasons.requirement_row), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - ks.hr.http_status = 0; - ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - } - break; - } - case MHD_HTTP_INTERNAL_SERVER_ERROR: - ks.hr.ec = TALER_JSON_get_error_code (j); - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - ks.hr.ec = TALER_JSON_get_error_code (j); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange /kyc-wallet\n", - (unsigned int) response_code, - (int) ks.hr.ec); - break; - } - kwh->cb (kwh->cb_cls, - &ks); - TALER_EXCHANGE_kyc_wallet_cancel (kwh); -} - - -struct TALER_EXCHANGE_KycWalletHandle * -TALER_EXCHANGE_kyc_wallet ( - struct GNUNET_CURL_Context *ctx, - const char *url, - const struct TALER_ReservePrivateKeyP *reserve_priv, - const struct TALER_Amount *balance, - TALER_EXCHANGE_KycWalletCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_KycWalletHandle *kwh; - CURL *eh; - json_t *req; - struct TALER_ReservePublicKeyP reserve_pub; - struct TALER_ReserveSignatureP reserve_sig; - - GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, - &reserve_pub.eddsa_pub); - TALER_wallet_account_setup_sign (reserve_priv, - balance, - &reserve_sig); - req = GNUNET_JSON_PACK ( - TALER_JSON_pack_amount ("balance", - balance), - GNUNET_JSON_pack_data_auto ("reserve_pub", - &reserve_pub), - GNUNET_JSON_pack_data_auto ("reserve_sig", - &reserve_sig)); - GNUNET_assert (NULL != req); - kwh = GNUNET_new (struct TALER_EXCHANGE_KycWalletHandle); - kwh->cb = cb; - kwh->cb_cls = cb_cls; - kwh->url = TALER_url_join (url, - "kyc-wallet", - NULL); - if (NULL == kwh->url) - { - json_decref (req); - GNUNET_free (kwh); - return NULL; - } - eh = TALER_EXCHANGE_curl_easy_get_ (kwh->url); - if ( (NULL == eh) || - (GNUNET_OK != - TALER_curl_easy_post (&kwh->ctx, - eh, - req)) ) - { - GNUNET_break (0); - if (NULL != eh) - curl_easy_cleanup (eh); - json_decref (req); - GNUNET_free (kwh->url); - GNUNET_free (kwh); - return NULL; - } - json_decref (req); - kwh->job = GNUNET_CURL_job_add2 (ctx, - eh, - kwh->ctx.headers, - &handle_kyc_wallet_finished, - kwh); - return kwh; -} - - -void -TALER_EXCHANGE_kyc_wallet_cancel (struct TALER_EXCHANGE_KycWalletHandle *kwh) -{ - if (NULL != kwh->job) - { - GNUNET_CURL_job_cancel (kwh->job); - kwh->job = NULL; - } - GNUNET_free (kwh->url); - TALER_curl_easy_post_finished (&kwh->ctx); - GNUNET_free (kwh); -} - - -/* end of exchange_api_kyc_wallet.c */ diff --git a/src/lib/exchange_api_lookup_aml_decisions.c b/src/lib/exchange_api_lookup_aml_decisions.c @@ -1,626 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2023, 2024 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_lookup_aml_decisions.c - * @brief Implementation of the /aml/$OFFICER_PUB/decisions request - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_exchange_service.h" -#include "taler/taler_json_lib.h" -#include "exchange_api_handle.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * @brief A GET /aml/$OFFICER_PUB/decisions Handle - */ -struct TALER_EXCHANGE_LookupAmlDecisions -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_LookupAmlDecisionsCallback decisions_cb; - - /** - * Closure for @e cb. - */ - void *decisions_cb_cls; - - /** - * HTTP headers for the job. - */ - struct curl_slist *job_headers; - - /** - * Array with measure information. - */ - struct TALER_EXCHANGE_MeasureInformation *mip; - - /** - * Array with rule information. - */ - struct TALER_EXCHANGE_KycRule *rp; - - /** - * Array with all the measures (of all the rules!). - */ - const char **mp; -}; - - -/** - * Parse AML limits array. - * - * @param[in,out] lh handle to use for allocations - * @param jlimits JSON array with AML rules - * @param[out] ds where to write the result - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -parse_limits (struct TALER_EXCHANGE_LookupAmlDecisions *lh, - const json_t *jlimits, - struct TALER_EXCHANGE_AmlDecision *ds) -{ - struct TALER_EXCHANGE_LegitimizationRuleSet *limits - = &ds->limits; - const json_t *jrules; - const json_t *jmeasures; - size_t mip_len; - size_t rule_len; - size_t total; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_timestamp ("expiration_time", - &limits->expiration_time), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("successor_measure", - &limits->successor_measure), - NULL), - GNUNET_JSON_spec_array_const ("rules", - &jrules), - GNUNET_JSON_spec_object_const ("custom_measures", - &jmeasures), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (jlimits, - spec, - NULL, - NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - mip_len = json_object_size (jmeasures); - lh->mip = GNUNET_new_array (mip_len, - struct TALER_EXCHANGE_MeasureInformation); - limits->measures = lh->mip; - limits->measures_length = mip_len; - - { - const char *measure_name; - const json_t *jmeasure; - - json_object_foreach ((json_t*) jmeasures, - measure_name, - jmeasure) - { - struct TALER_EXCHANGE_MeasureInformation *mi - = &lh->mip[--mip_len]; - struct GNUNET_JSON_Specification ispec[] = { - GNUNET_JSON_spec_string ("check_name", - &mi->check_name), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("prog_name", - &mi->prog_name), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_object_const ("context", - &mi->context), - NULL), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (jmeasure, - ispec, - NULL, - NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - mi->measure_name = measure_name; - } - } - - total = 0; - - { - const json_t *rule; - size_t idx; - - json_array_foreach ((json_t *) jrules, - idx, - rule) - { - total += json_array_size (json_object_get (rule, - "measures")); - } - } - - rule_len = json_array_size (jrules); - lh->rp = GNUNET_new_array (rule_len, - struct TALER_EXCHANGE_KycRule); - lh->mp = GNUNET_new_array (total, - const char *); - - { - const json_t *rule; - size_t idx; - - json_array_foreach ((json_t *) jrules, - idx, - rule) - { - const json_t *smeasures; - struct TALER_EXCHANGE_KycRule *r - = &lh->rp[--rule_len]; - struct GNUNET_JSON_Specification ispec[] = { - TALER_JSON_spec_kycte ("operation_type", - &r->operation_type), - TALER_JSON_spec_amount_any ("threshold", - &r->threshold), - GNUNET_JSON_spec_relative_time ("timeframe", - &r->timeframe), - GNUNET_JSON_spec_array_const ("measures", - &smeasures), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_bool ("exposed", - &r->exposed), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_bool ("is_and_combinator", - &r->is_and_combinator), - NULL), - GNUNET_JSON_spec_uint32 ("display_priority", - &r->display_priority), - GNUNET_JSON_spec_end () - }; - size_t mlen; - - if (GNUNET_OK != - GNUNET_JSON_parse (rule, - ispec, - NULL, - NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - mlen = json_array_size (smeasures); - GNUNET_assert (mlen <= total); - total -= mlen; - - { - size_t midx; - const json_t *smeasure; - - json_array_foreach (smeasures, - midx, - smeasure) - { - const char *sval = json_string_value (smeasure); - - if (NULL == sval) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - lh->mp[total + midx] = sval; - if (0 == strcasecmp (sval, - "verboten")) - r->verboten = true; - } - } - r->measures = &lh->mp[total]; - r->measures_length = r->verboten ? 0 : total; - } - } - return GNUNET_OK; -} - - -/** - * Parse AML decision summary array. - * - * @param[in,out] lh handle to use for allocations - * @param decisions JSON array with AML decision summaries - * @param[out] decision_ar where to write the result - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -parse_aml_decisions ( - struct TALER_EXCHANGE_LookupAmlDecisions *lh, - const json_t *decisions, - struct TALER_EXCHANGE_AmlDecision *decision_ar) -{ - json_t *obj; - size_t idx; - - json_array_foreach (decisions, idx, obj) - { - struct TALER_EXCHANGE_AmlDecision *decision = &decision_ar[idx]; - const json_t *jlimits; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("h_payto", - &decision->h_payto), - GNUNET_JSON_spec_uint64 ("rowid", - &decision->rowid), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("justification", - &decision->justification), - NULL), - GNUNET_JSON_spec_timestamp ("decision_time", - &decision->decision_time), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_object_const ("properties", - &decision->jproperties), - NULL), - GNUNET_JSON_spec_object_const ("limits", - &jlimits), - GNUNET_JSON_spec_bool ("to_investigate", - &decision->to_investigate), - GNUNET_JSON_spec_bool ("is_active", - &decision->is_active), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (obj, - spec, - NULL, - NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - parse_limits (lh, - jlimits, - decision)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - } - return GNUNET_OK; -} - - -/** - * Parse the provided decision data from the "200 OK" response. - * - * @param[in,out] lh handle (callback may be zero'ed out) - * @param json json reply with the data for one coin - * @return #GNUNET_OK on success, #GNUNET_SYSERR on error - */ -static enum GNUNET_GenericReturnValue -parse_decisions_ok (struct TALER_EXCHANGE_LookupAmlDecisions *lh, - const json_t *json) -{ - struct TALER_EXCHANGE_AmlDecisionsResponse lr = { - .hr.reply = json, - .hr.http_status = MHD_HTTP_OK - }; - const json_t *records; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_array_const ("records", - &records), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, - NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - lr.details.ok.decisions_length - = json_array_size (records); - { - struct TALER_EXCHANGE_AmlDecision decisions[ - GNUNET_NZL (lr.details.ok.decisions_length)]; - enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR; - - memset (decisions, - 0, - sizeof (decisions)); - lr.details.ok.decisions = decisions; - ret = parse_aml_decisions (lh, - records, - decisions); - if (GNUNET_OK == ret) - { - lh->decisions_cb (lh->decisions_cb_cls, - &lr); - lh->decisions_cb = NULL; - } - GNUNET_free (lh->mip); - GNUNET_free (lh->rp); - GNUNET_free (lh->mp); - return ret; - } -} - - -/** - * Function called when we're done processing the - * HTTP /aml/$OFFICER_PUB/decisions request. - * - * @param cls the `struct TALER_EXCHANGE_LookupAmlDecisions` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_lookup_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_LookupAmlDecisions *lh = cls; - const json_t *j = response; - struct TALER_EXCHANGE_AmlDecisionsResponse lr = { - .hr.reply = j, - .hr.http_status = (unsigned int) response_code - }; - - lh->job = NULL; - switch (response_code) - { - case 0: - lr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - if (GNUNET_OK != - parse_decisions_ok (lh, - j)) - { - GNUNET_break_op (0); - lr.hr.http_status = 0; - lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - GNUNET_assert (NULL == lh->decisions_cb); - TALER_EXCHANGE_lookup_aml_decisions_cancel (lh); - return; - case MHD_HTTP_NO_CONTENT: - break; - case MHD_HTTP_BAD_REQUEST: - json_dumpf (j, - stderr, - JSON_INDENT (2)); - lr.hr.ec = TALER_JSON_get_error_code (j); - lr.hr.hint = TALER_JSON_get_error_hint (j); - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - break; - case MHD_HTTP_FORBIDDEN: - lr.hr.ec = TALER_JSON_get_error_code (j); - lr.hr.hint = TALER_JSON_get_error_hint (j); - /* Nothing really to verify, exchange says this coin was not melted; we - should pass the JSON reply to the application */ - break; - case MHD_HTTP_NOT_FOUND: - lr.hr.ec = TALER_JSON_get_error_code (j); - lr.hr.hint = TALER_JSON_get_error_hint (j); - /* Nothing really to verify, exchange says this coin was not melted; we - should pass the JSON reply to the application */ - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - lr.hr.ec = TALER_JSON_get_error_code (j); - lr.hr.hint = TALER_JSON_get_error_hint (j); - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - lr.hr.ec = TALER_JSON_get_error_code (j); - lr.hr.hint = TALER_JSON_get_error_hint (j); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for lookup AML decisions\n", - (unsigned int) response_code, - (int) lr.hr.ec); - break; - } - if (NULL != lh->decisions_cb) - lh->decisions_cb (lh->decisions_cb_cls, - &lr); - TALER_EXCHANGE_lookup_aml_decisions_cancel (lh); -} - - -struct TALER_EXCHANGE_LookupAmlDecisions * -TALER_EXCHANGE_lookup_aml_decisions ( - struct GNUNET_CURL_Context *ctx, - const char *exchange_url, - const struct TALER_NormalizedPaytoHashP *h_payto, - enum TALER_EXCHANGE_YesNoAll investigation_only, - enum TALER_EXCHANGE_YesNoAll active_only, - uint64_t offset, - int64_t limit, - const struct TALER_AmlOfficerPrivateKeyP *officer_priv, - TALER_EXCHANGE_LookupAmlDecisionsCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_LookupAmlDecisions *lh; - CURL *eh; - struct TALER_AmlOfficerPublicKeyP officer_pub; - struct TALER_AmlOfficerSignatureP officer_sig; - char arg_str[sizeof (struct TALER_AmlOfficerPublicKeyP) * 2 - + 32]; - - GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv, - &officer_pub.eddsa_pub); - TALER_officer_aml_query_sign (officer_priv, - &officer_sig); - { - char pub_str[sizeof (officer_pub) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string ( - &officer_pub, - sizeof (officer_pub), - pub_str, - sizeof (pub_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "aml/%s/decisions", - pub_str); - } - lh = GNUNET_new (struct TALER_EXCHANGE_LookupAmlDecisions); - lh->decisions_cb = cb; - lh->decisions_cb_cls = cb_cls; - { - char limit_s[24]; - char offset_s[24]; - char payto_s[sizeof (*h_payto) * 2]; - char *end; - - if (NULL != h_payto) - { - end = GNUNET_STRINGS_data_to_string ( - h_payto, - sizeof (*h_payto), - payto_s, - sizeof (payto_s)); - *end = '\0'; - } - GNUNET_snprintf (limit_s, - sizeof (limit_s), - "%lld", - (long long) limit); - GNUNET_snprintf (offset_s, - sizeof (offset_s), - "%llu", - (unsigned long long) offset); - lh->url = TALER_url_join ( - exchange_url, - arg_str, - "limit", - limit_s, - "offset", - ( ( (limit < 0) && (UINT64_MAX == offset) ) || - ( (limit > 0) && (0 == offset) ) ) - ? NULL - : offset_s, - "h_payto", - NULL != h_payto - ? payto_s - : NULL, - "active", - TALER_EXCHANGE_YNA_ALL != active_only - ? TALER_yna_to_string (active_only) - : NULL, - "investigation", - TALER_EXCHANGE_YNA_ALL != investigation_only - ? TALER_yna_to_string (investigation_only) - : NULL, - NULL); - } - if (NULL == lh->url) - { - GNUNET_free (lh); - return NULL; - } - eh = TALER_EXCHANGE_curl_easy_get_ (lh->url); - if (NULL == eh) - { - GNUNET_break (0); - GNUNET_free (lh->url); - GNUNET_free (lh); - return NULL; - } - { - char *hdr; - char sig_str[sizeof (officer_sig) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string ( - &officer_sig, - sizeof (officer_sig), - sig_str, - sizeof (sig_str)); - *end = '\0'; - - GNUNET_asprintf (&hdr, - "%s: %s", - TALER_AML_OFFICER_SIGNATURE_HEADER, - sig_str); - lh->job_headers = curl_slist_append (NULL, - hdr); - GNUNET_free (hdr); - lh->job_headers = curl_slist_append (lh->job_headers, - "Content-type: application/json"); - lh->job = GNUNET_CURL_job_add2 (ctx, - eh, - lh->job_headers, - &handle_lookup_finished, - lh); - } - return lh; -} - - -void -TALER_EXCHANGE_lookup_aml_decisions_cancel ( - struct TALER_EXCHANGE_LookupAmlDecisions *lh) -{ - if (NULL != lh->job) - { - GNUNET_CURL_job_cancel (lh->job); - lh->job = NULL; - } - curl_slist_free_all (lh->job_headers); - GNUNET_free (lh->url); - GNUNET_free (lh); -} - - -/* end of exchange_api_lookup_aml_decisions.c */ diff --git a/src/lib/exchange_api_lookup_kyc_attributes.c b/src/lib/exchange_api_lookup_kyc_attributes.c @@ -1,386 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2023, 2024 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_lookup_kyc_attributes.c - * @brief Implementation of the /aml/$OFFICER_PUB/attributes request - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_exchange_service.h" -#include "taler/taler_json_lib.h" -#include "exchange_api_handle.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * @brief A GET /aml/$OFFICER_PUB/attributes Handle - */ -struct TALER_EXCHANGE_LookupKycAttributes -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_LookupKycAttributesCallback attributes_cb; - - /** - * Closure for @e cb. - */ - void *attributes_cb_cls; - - /** - * HTTP headers for the job. - */ - struct curl_slist *job_headers; - -}; - - -/** - * Parse AML decision summary array. - * - * @param[in,out] lh handle to use for allocations - * @param jdetails JSON array with AML decision summaries - * @param[out] detail_ar where to write the result - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -parse_kyc_attributes ( - struct TALER_EXCHANGE_LookupKycAttributes *lh, - const json_t *jdetails, - struct TALER_EXCHANGE_KycAttributeDetail *detail_ar) -{ - json_t *obj; - size_t idx; - - json_array_foreach (jdetails, idx, obj) - { - struct TALER_EXCHANGE_KycAttributeDetail *detail - = &detail_ar[idx]; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_uint64 ("rowid", - &detail->row_id), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("provider_name", - &detail->provider_name), - NULL), - GNUNET_JSON_spec_object_const ("attributes", - &detail->attributes), - GNUNET_JSON_spec_timestamp ("collection_time", - &detail->collection_time), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (obj, - spec, - NULL, - NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - } - return GNUNET_OK; -} - - -/** - * Parse the provided decision data from the "200 OK" response. - * - * @param[in,out] lh handle (callback may be zero'ed out) - * @param json json reply with the data for one coin - * @return #GNUNET_OK on success, #GNUNET_SYSERR on error - */ -static enum GNUNET_GenericReturnValue -parse_attributes_ok (struct TALER_EXCHANGE_LookupKycAttributes *lh, - const json_t *json) -{ - struct TALER_EXCHANGE_KycAttributesResponse lr = { - .hr.reply = json, - .hr.http_status = MHD_HTTP_OK - }; - const json_t *jdetails; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_array_const ("details", - &jdetails), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, - NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - lr.details.ok.kyc_attributes_length - = json_array_size (jdetails); - { - struct TALER_EXCHANGE_KycAttributeDetail details[ - GNUNET_NZL (lr.details.ok.kyc_attributes_length)]; - enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR; - - memset (details, - 0, - sizeof (details)); - lr.details.ok.kyc_attributes = details; - ret = parse_kyc_attributes (lh, - jdetails, - details); - if (GNUNET_OK == ret) - { - lh->attributes_cb (lh->attributes_cb_cls, - &lr); - lh->attributes_cb = NULL; - } - return ret; - } -} - - -/** - * Function called when we're done processing the - * HTTP /aml/$OFFICER_PUB/attributes request. - * - * @param cls the `struct TALER_EXCHANGE_LookupKycAttributes` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_lookup_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_LookupKycAttributes *lh = cls; - const json_t *j = response; - struct TALER_EXCHANGE_KycAttributesResponse lr = { - .hr.reply = j, - .hr.http_status = (unsigned int) response_code - }; - - lh->job = NULL; - switch (response_code) - { - case 0: - lr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - if (GNUNET_OK != - parse_attributes_ok (lh, - j)) - { - GNUNET_break_op (0); - lr.hr.http_status = 0; - lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - GNUNET_assert (NULL == lh->attributes_cb); - TALER_EXCHANGE_lookup_kyc_attributes_cancel (lh); - return; - case MHD_HTTP_NO_CONTENT: - break; - case MHD_HTTP_BAD_REQUEST: - lr.hr.ec = TALER_JSON_get_error_code (j); - lr.hr.hint = TALER_JSON_get_error_hint (j); - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - break; - case MHD_HTTP_FORBIDDEN: - lr.hr.ec = TALER_JSON_get_error_code (j); - lr.hr.hint = TALER_JSON_get_error_hint (j); - /* Nothing really to verify, exchange says this coin was not melted; we - should pass the JSON reply to the application */ - break; - case MHD_HTTP_NOT_FOUND: - lr.hr.ec = TALER_JSON_get_error_code (j); - lr.hr.hint = TALER_JSON_get_error_hint (j); - /* Nothing really to verify, exchange says this coin was not melted; we - should pass the JSON reply to the application */ - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - lr.hr.ec = TALER_JSON_get_error_code (j); - lr.hr.hint = TALER_JSON_get_error_hint (j); - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - lr.hr.ec = TALER_JSON_get_error_code (j); - lr.hr.hint = TALER_JSON_get_error_hint (j); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for lookup AML attributes\n", - (unsigned int) response_code, - (int) lr.hr.ec); - break; - } - if (NULL != lh->attributes_cb) - lh->attributes_cb (lh->attributes_cb_cls, - &lr); - TALER_EXCHANGE_lookup_kyc_attributes_cancel (lh); -} - - -struct TALER_EXCHANGE_LookupKycAttributes * -TALER_EXCHANGE_lookup_kyc_attributes ( - struct GNUNET_CURL_Context *ctx, - const char *exchange_url, - const struct TALER_NormalizedPaytoHashP *h_payto, - uint64_t offset, - int64_t limit, - const struct TALER_AmlOfficerPrivateKeyP *officer_priv, - TALER_EXCHANGE_LookupKycAttributesCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_LookupKycAttributes *lh; - CURL *eh; - struct TALER_AmlOfficerPublicKeyP officer_pub; - struct TALER_AmlOfficerSignatureP officer_sig; - char arg_str[sizeof (officer_pub) * 2 - + sizeof (*h_payto) * 2 - + 32]; - - GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv, - &officer_pub.eddsa_pub); - TALER_officer_aml_query_sign (officer_priv, - &officer_sig); - - { - char payto_s[sizeof (*h_payto) * 2]; - char pub_str[sizeof (officer_pub) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string ( - h_payto, - sizeof (*h_payto), - payto_s, - sizeof (payto_s)); - *end = '\0'; - end = GNUNET_STRINGS_data_to_string ( - &officer_pub, - sizeof (officer_pub), - pub_str, - sizeof (pub_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "aml/%s/attributes/%s", - pub_str, - payto_s); - } - lh = GNUNET_new (struct TALER_EXCHANGE_LookupKycAttributes); - lh->attributes_cb = cb; - lh->attributes_cb_cls = cb_cls; - { - char limit_s[24]; - char offset_s[24]; - - GNUNET_snprintf (limit_s, - sizeof (limit_s), - "%lld", - (long long) limit); - GNUNET_snprintf (offset_s, - sizeof (offset_s), - "%llu", - (unsigned long long) offset); - lh->url = TALER_url_join ( - exchange_url, - arg_str, - "limit", - limit_s, - "offset", - offset_s, - "h_payto", - NULL); - } - if (NULL == lh->url) - { - GNUNET_free (lh); - return NULL; - } - eh = TALER_EXCHANGE_curl_easy_get_ (lh->url); - if (NULL == eh) - { - GNUNET_break (0); - GNUNET_free (lh->url); - GNUNET_free (lh); - return NULL; - } - { - char *hdr; - char sig_str[sizeof (officer_sig) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string ( - &officer_sig, - sizeof (officer_sig), - sig_str, - sizeof (sig_str)); - *end = '\0'; - - GNUNET_asprintf (&hdr, - "%s: %s", - TALER_AML_OFFICER_SIGNATURE_HEADER, - sig_str); - lh->job_headers = curl_slist_append (NULL, - hdr); - GNUNET_free (hdr); - lh->job_headers = curl_slist_append (lh->job_headers, - "Content-type: application/json"); - lh->job = GNUNET_CURL_job_add2 (ctx, - eh, - lh->job_headers, - &handle_lookup_finished, - lh); - } - return lh; -} - - -void -TALER_EXCHANGE_lookup_kyc_attributes_cancel ( - struct TALER_EXCHANGE_LookupKycAttributes *lh) -{ - if (NULL != lh->job) - { - GNUNET_CURL_job_cancel (lh->job); - lh->job = NULL; - } - curl_slist_free_all (lh->job_headers); - GNUNET_free (lh->url); - GNUNET_free (lh); -} - - -/* end of exchange_api_lookup_kyc_attributes.c */ diff --git a/src/lib/exchange_api_management_add_partner.c b/src/lib/exchange_api_management_add_partner.c @@ -1,219 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2023 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_management_add_partner.c - * @brief functions to add an partner by an AML officer - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include "taler/taler_json_lib.h" -#include <gnunet/gnunet_curl_lib.h> -#include <microhttpd.h> -#include "taler/taler_exchange_service.h" -#include "exchange_api_curl_defaults.h" -#include "taler/taler_signatures.h" -#include "taler/taler_curl_lib.h" -#include "taler/taler_json_lib.h" - - -struct TALER_EXCHANGE_ManagementAddPartner -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Minor context that holds body and headers. - */ - struct TALER_CURL_PostContext post_ctx; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_ManagementAddPartnerCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Reference to the execution context. - */ - struct GNUNET_CURL_Context *ctx; -}; - - -/** - * Function called when we're done processing the - * HTTP POST /management/partners request. - * - * @param cls the `struct TALER_EXCHANGE_ManagementAddPartner *` - * @param response_code HTTP response code, 0 on error - * @param response response body, NULL if not in JSON - */ -static void -handle_add_partner_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_ManagementAddPartner *wh = cls; - const json_t *json = response; - struct TALER_EXCHANGE_ManagementAddPartnerResponse apr = { - .hr.http_status = (unsigned int) response_code, - .hr.reply = json - }; - - wh->job = NULL; - switch (response_code) - { - case 0: - /* no reply */ - apr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - apr.hr.hint = "server offline?"; - break; - case MHD_HTTP_NO_CONTENT: - break; - case MHD_HTTP_FORBIDDEN: - apr.hr.ec = TALER_JSON_get_error_code (json); - apr.hr.hint = TALER_JSON_get_error_hint (json); - break; - case MHD_HTTP_CONFLICT: - apr.hr.ec = TALER_JSON_get_error_code (json); - apr.hr.hint = TALER_JSON_get_error_hint (json); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - apr.hr.ec = TALER_JSON_get_error_code (json); - apr.hr.hint = TALER_JSON_get_error_hint (json); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for adding exchange partner\n", - (unsigned int) response_code, - (int) apr.hr.ec); - break; - } - if (NULL != wh->cb) - { - wh->cb (wh->cb_cls, - &apr); - wh->cb = NULL; - } - TALER_EXCHANGE_management_add_partner_cancel (wh); -} - - -struct TALER_EXCHANGE_ManagementAddPartner * -TALER_EXCHANGE_management_add_partner ( - struct GNUNET_CURL_Context *ctx, - const char *url, - const struct TALER_MasterPublicKeyP *partner_pub, - struct GNUNET_TIME_Timestamp start_date, - struct GNUNET_TIME_Timestamp end_date, - struct GNUNET_TIME_Relative wad_frequency, - const struct TALER_Amount *wad_fee, - const char *partner_base_url, - const struct TALER_MasterSignatureP *master_sig, - TALER_EXCHANGE_ManagementAddPartnerCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_ManagementAddPartner *wh; - CURL *eh; - json_t *body; - - wh = GNUNET_new (struct TALER_EXCHANGE_ManagementAddPartner); - wh->cb = cb; - wh->cb_cls = cb_cls; - wh->ctx = ctx; - wh->url = TALER_url_join (url, - "management/partners", - NULL); - if (NULL == wh->url) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Could not construct request URL.\n"); - GNUNET_free (wh); - return NULL; - } - body = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("partner_base_url", - partner_base_url), - GNUNET_JSON_pack_timestamp ("start_date", - start_date), - GNUNET_JSON_pack_timestamp ("end_date", - end_date), - GNUNET_JSON_pack_time_rel ("wad_frequency", - wad_frequency), - GNUNET_JSON_pack_data_auto ("partner_pub", - &partner_pub), - GNUNET_JSON_pack_data_auto ("master_sig", - &master_sig), - TALER_JSON_pack_amount ("wad_fee", - wad_fee) - ); - eh = TALER_EXCHANGE_curl_easy_get_ (wh->url); - if ( (NULL == eh) || - (GNUNET_OK != - TALER_curl_easy_post (&wh->post_ctx, - eh, - body)) ) - { - GNUNET_break (0); - if (NULL != eh) - curl_easy_cleanup (eh); - json_decref (body); - GNUNET_free (wh->url); - return NULL; - } - json_decref (body); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Requesting URL '%s'\n", - wh->url); - wh->job = GNUNET_CURL_job_add2 (ctx, - eh, - wh->post_ctx.headers, - &handle_add_partner_finished, - wh); - if (NULL == wh->job) - { - TALER_EXCHANGE_management_add_partner_cancel (wh); - return NULL; - } - return wh; -} - - -void -TALER_EXCHANGE_management_add_partner_cancel ( - struct TALER_EXCHANGE_ManagementAddPartner *wh) -{ - if (NULL != wh->job) - { - GNUNET_CURL_job_cancel (wh->job); - wh->job = NULL; - } - TALER_curl_easy_post_finished (&wh->post_ctx); - GNUNET_free (wh->url); - GNUNET_free (wh); -} diff --git a/src/lib/exchange_api_management_auditor_disable.c b/src/lib/exchange_api_management_auditor_disable.c @@ -1,220 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2015-2021 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_management_auditor_disable.c - * @brief functions to disable an auditor - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include "taler/taler_json_lib.h" -#include <gnunet/gnunet_curl_lib.h> -#include <microhttpd.h> -#include "taler/taler_exchange_service.h" -#include "exchange_api_curl_defaults.h" -#include "taler/taler_signatures.h" -#include "taler/taler_curl_lib.h" -#include "taler/taler_json_lib.h" - -/** - * @brief Handle for a POST /management/auditors/$AUDITOR_PUB/disable request. - */ -struct TALER_EXCHANGE_ManagementAuditorDisableHandle -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Minor context that holds body and headers. - */ - struct TALER_CURL_PostContext post_ctx; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_ManagementAuditorDisableCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Reference to the execution context. - */ - struct GNUNET_CURL_Context *ctx; -}; - - -/** - * Function called when we're done processing the - * HTTP /management/auditors/%s/disable request. - * - * @param cls the `struct TALER_EXCHANGE_ManagementAuditorDisableHandle *` - * @param response_code HTTP response code, 0 on error - * @param response response body, NULL if not in JSON - */ -static void -handle_auditor_disable_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_ManagementAuditorDisableHandle *ah = cls; - const json_t *json = response; - struct TALER_EXCHANGE_ManagementAuditorDisableResponse adr = { - .hr.http_status = (unsigned int) response_code, - .hr.reply = json - }; - - ah->job = NULL; - switch (response_code) - { - case MHD_HTTP_NO_CONTENT: - break; - case MHD_HTTP_FORBIDDEN: - adr.hr.ec = TALER_JSON_get_error_code (json); - adr.hr.hint = TALER_JSON_get_error_hint (json); - break; - case MHD_HTTP_NOT_FOUND: - adr.hr.ec = TALER_JSON_get_error_code (json); - adr.hr.hint = TALER_JSON_get_error_hint (json); - break; - case MHD_HTTP_CONFLICT: - adr.hr.ec = TALER_JSON_get_error_code (json); - adr.hr.hint = TALER_JSON_get_error_hint (json); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - adr.hr.ec = TALER_JSON_get_error_code (json); - adr.hr.hint = TALER_JSON_get_error_hint (json); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange management auditor disable\n", - (unsigned int) response_code, - (int) adr.hr.ec); - break; - } - if (NULL != ah->cb) - { - ah->cb (ah->cb_cls, - &adr); - ah->cb = NULL; - } - TALER_EXCHANGE_management_disable_auditor_cancel (ah); -} - - -struct TALER_EXCHANGE_ManagementAuditorDisableHandle * -TALER_EXCHANGE_management_disable_auditor ( - struct GNUNET_CURL_Context *ctx, - const char *url, - const struct TALER_AuditorPublicKeyP *auditor_pub, - struct GNUNET_TIME_Timestamp validity_end, - const struct TALER_MasterSignatureP *master_sig, - TALER_EXCHANGE_ManagementAuditorDisableCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_ManagementAuditorDisableHandle *ah; - CURL *eh; - json_t *body; - - ah = GNUNET_new (struct TALER_EXCHANGE_ManagementAuditorDisableHandle); - ah->cb = cb; - ah->cb_cls = cb_cls; - ah->ctx = ctx; - { - char epub_str[sizeof (*auditor_pub) * 2]; - char arg_str[sizeof (epub_str) + 64]; - char *end; - - end = GNUNET_STRINGS_data_to_string (auditor_pub, - sizeof (*auditor_pub), - epub_str, - sizeof (epub_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "management/auditors/%s/disable", - epub_str); - ah->url = TALER_url_join (url, - arg_str, - NULL); - } - if (NULL == ah->url) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Could not construct request URL.\n"); - GNUNET_free (ah); - return NULL; - } - body = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_data_auto ("master_sig", - master_sig), - GNUNET_JSON_pack_timestamp ("validity_end", - validity_end)); - eh = TALER_EXCHANGE_curl_easy_get_ (ah->url); - if ( (NULL == eh) || - (GNUNET_OK != - TALER_curl_easy_post (&ah->post_ctx, - eh, - body))) - { - GNUNET_break (0); - if (NULL != eh) - curl_easy_cleanup (eh); - json_decref (body); - GNUNET_free (ah->url); - return NULL; - } - json_decref (body); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Requesting URL '%s'\n", - ah->url); - ah->job = GNUNET_CURL_job_add2 (ctx, - eh, - ah->post_ctx.headers, - &handle_auditor_disable_finished, - ah); - if (NULL == ah->job) - { - TALER_EXCHANGE_management_disable_auditor_cancel (ah); - return NULL; - } - return ah; -} - - -void -TALER_EXCHANGE_management_disable_auditor_cancel ( - struct TALER_EXCHANGE_ManagementAuditorDisableHandle *ah) -{ - if (NULL != ah->job) - { - GNUNET_CURL_job_cancel (ah->job); - ah->job = NULL; - } - TALER_curl_easy_post_finished (&ah->post_ctx); - GNUNET_free (ah->url); - GNUNET_free (ah); -} diff --git a/src/lib/exchange_api_management_auditor_enable.c b/src/lib/exchange_api_management_auditor_enable.c @@ -1,227 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2015-2021 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_management_auditor_enable.c - * @brief functions to enable an auditor - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include "taler/taler_json_lib.h" -#include <gnunet/gnunet_curl_lib.h> -#include <microhttpd.h> -#include "taler/taler_exchange_service.h" -#include "exchange_api_curl_defaults.h" -#include "taler/taler_signatures.h" -#include "taler/taler_curl_lib.h" -#include "taler/taler_json_lib.h" - - -/** - * @brief Handle for a POST /management/auditors request. - */ -struct TALER_EXCHANGE_ManagementAuditorEnableHandle -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Minor context that holds body and headers. - */ - struct TALER_CURL_PostContext post_ctx; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_ManagementAuditorEnableCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Reference to the execution context. - */ - struct GNUNET_CURL_Context *ctx; -}; - - -/** - * Function called when we're done processing the - * HTTP POST /management/auditors request. - * - * @param cls the `struct TALER_EXCHANGE_ManagementAuditorEnableHandle *` - * @param response_code HTTP response code, 0 on error - * @param response response body, NULL if not in JSON - */ -static void -handle_auditor_enable_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_ManagementAuditorEnableHandle *ah = cls; - const json_t *json = response; - struct TALER_EXCHANGE_ManagementAuditorEnableResponse auditor_enable_response - = { - .hr.http_status = (unsigned int) response_code, - .hr.reply = json - }; - - ah->job = NULL; - switch (response_code) - { - case MHD_HTTP_NO_CONTENT: - break; - case MHD_HTTP_FORBIDDEN: - auditor_enable_response.hr.ec = TALER_JSON_get_error_code (json); - auditor_enable_response.hr.hint = TALER_JSON_get_error_hint (json); - break; - case MHD_HTTP_NOT_FOUND: - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Server did not find handler at `%s'. Did you configure the correct exchange base URL?\n", - ah->url); - if (NULL != json) - { - auditor_enable_response.hr.ec = TALER_JSON_get_error_code (json); - auditor_enable_response.hr.hint = TALER_JSON_get_error_hint (json); - } - else - { - auditor_enable_response.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - auditor_enable_response.hr.hint = TALER_ErrorCode_get_hint ( - auditor_enable_response.hr.ec); - } - break; - case MHD_HTTP_CONFLICT: - auditor_enable_response.hr.ec = TALER_JSON_get_error_code (json); - auditor_enable_response.hr.hint = TALER_JSON_get_error_hint (json); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - auditor_enable_response.hr.ec = TALER_JSON_get_error_code (json); - auditor_enable_response.hr.hint = TALER_JSON_get_error_hint (json); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange management auditor enable\n", - (unsigned int) response_code, - (int) auditor_enable_response.hr.ec); - break; - } - if (NULL != ah->cb) - { - ah->cb (ah->cb_cls, - &auditor_enable_response); - ah->cb = NULL; - } - TALER_EXCHANGE_management_enable_auditor_cancel (ah); -} - - -struct TALER_EXCHANGE_ManagementAuditorEnableHandle * -TALER_EXCHANGE_management_enable_auditor ( - struct GNUNET_CURL_Context *ctx, - const char *url, - const struct TALER_AuditorPublicKeyP *auditor_pub, - const char *auditor_url, - const char *auditor_name, - struct GNUNET_TIME_Timestamp validity_start, - const struct TALER_MasterSignatureP *master_sig, - TALER_EXCHANGE_ManagementAuditorEnableCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_ManagementAuditorEnableHandle *ah; - CURL *eh; - json_t *body; - - ah = GNUNET_new (struct TALER_EXCHANGE_ManagementAuditorEnableHandle); - ah->cb = cb; - ah->cb_cls = cb_cls; - ah->ctx = ctx; - ah->url = TALER_url_join (url, - "management/auditors", - NULL); - if (NULL == ah->url) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Could not construct request URL.\n"); - GNUNET_free (ah); - return NULL; - } - body = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("auditor_url", - auditor_url), - GNUNET_JSON_pack_string ("auditor_name", - auditor_name), - GNUNET_JSON_pack_data_auto ("auditor_pub", - auditor_pub), - GNUNET_JSON_pack_data_auto ("master_sig", - master_sig), - GNUNET_JSON_pack_timestamp ("validity_start", - validity_start)); - eh = TALER_EXCHANGE_curl_easy_get_ (ah->url); - if ( (NULL == eh) || - (GNUNET_OK != - TALER_curl_easy_post (&ah->post_ctx, - eh, - body)) ) - { - GNUNET_break (0); - json_decref (body); - if (NULL != eh) - curl_easy_cleanup (eh); - GNUNET_free (ah->url); - return NULL; - } - json_decref (body); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Requesting URL '%s'\n", - ah->url); - ah->job = GNUNET_CURL_job_add2 (ctx, - eh, - ah->post_ctx.headers, - &handle_auditor_enable_finished, - ah); - if (NULL == ah->job) - { - TALER_EXCHANGE_management_enable_auditor_cancel (ah); - return NULL; - } - return ah; -} - - -void -TALER_EXCHANGE_management_enable_auditor_cancel ( - struct TALER_EXCHANGE_ManagementAuditorEnableHandle *ah) -{ - if (NULL != ah->job) - { - GNUNET_CURL_job_cancel (ah->job); - ah->job = NULL; - } - TALER_curl_easy_post_finished (&ah->post_ctx); - GNUNET_free (ah->url); - GNUNET_free (ah); -} diff --git a/src/lib/exchange_api_management_drain_profits.c b/src/lib/exchange_api_management_drain_profits.c @@ -1,214 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2020-2023 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_management_drain_profits.c - * @brief functions to set wire fees at an exchange - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include "taler/taler_json_lib.h" -#include <gnunet/gnunet_curl_lib.h> -#include <microhttpd.h> -#include "exchange_api_curl_defaults.h" -#include "taler/taler_exchange_service.h" -#include "taler/taler_signatures.h" -#include "taler/taler_curl_lib.h" -#include "taler/taler_json_lib.h" - - -struct TALER_EXCHANGE_ManagementDrainProfitsHandle -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Minor context that holds body and headers. - */ - struct TALER_CURL_PostContext post_ctx; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_ManagementDrainProfitsCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Reference to the execution context. - */ - struct GNUNET_CURL_Context *ctx; -}; - - -/** - * Function called when we're done processing the - * HTTP /management/drain request. - * - * @param cls the `struct TALER_EXCHANGE_ManagementDrainProfitsHandle *` - * @param response_code HTTP response code, 0 on error - * @param response response body, NULL if not in JSON - */ -static void -handle_drain_profits_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_ManagementDrainProfitsHandle *dp = cls; - const json_t *json = response; - struct TALER_EXCHANGE_ManagementDrainResponse dr = { - .hr.http_status = (unsigned int) response_code, - .hr.reply = json - }; - - dp->job = NULL; - switch (response_code) - { - case MHD_HTTP_NO_CONTENT: - break; - case MHD_HTTP_FORBIDDEN: - dr.hr.ec = TALER_JSON_get_error_code (json); - dr.hr.hint = TALER_JSON_get_error_hint (json); - break; - case MHD_HTTP_CONFLICT: - dr.hr.ec = TALER_JSON_get_error_code (json); - dr.hr.hint = TALER_JSON_get_error_hint (json); - break; - case MHD_HTTP_PRECONDITION_FAILED: - dr.hr.ec = TALER_JSON_get_error_code (json); - dr.hr.hint = TALER_JSON_get_error_hint (json); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - dr.hr.ec = TALER_JSON_get_error_code (json); - dr.hr.hint = TALER_JSON_get_error_hint (json); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange management drain profits\n", - (unsigned int) response_code, - (int) dr.hr.ec); - break; - } - if (NULL != dp->cb) - { - dp->cb (dp->cb_cls, - &dr); - dp->cb = NULL; - } - TALER_EXCHANGE_management_drain_profits_cancel (dp); -} - - -struct TALER_EXCHANGE_ManagementDrainProfitsHandle * -TALER_EXCHANGE_management_drain_profits ( - struct GNUNET_CURL_Context *ctx, - const char *url, - const struct TALER_WireTransferIdentifierRawP *wtid, - const struct TALER_Amount *amount, - struct GNUNET_TIME_Timestamp date, - const char *account_section, - const struct TALER_FullPayto payto_uri, - const struct TALER_MasterSignatureP *master_sig, - TALER_EXCHANGE_ManagementDrainProfitsCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_ManagementDrainProfitsHandle *dp; - CURL *eh; - json_t *body; - - dp = GNUNET_new (struct TALER_EXCHANGE_ManagementDrainProfitsHandle); - dp->cb = cb; - dp->cb_cls = cb_cls; - dp->ctx = ctx; - dp->url = TALER_url_join (url, - "management/drain", - NULL); - if (NULL == dp->url) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Could not construct request URL.\n"); - GNUNET_free (dp); - return NULL; - } - body = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("debit_account_section", - account_section), - TALER_JSON_pack_full_payto ("credit_payto_uri", - payto_uri), - GNUNET_JSON_pack_data_auto ("wtid", - wtid), - GNUNET_JSON_pack_data_auto ("master_sig", - master_sig), - GNUNET_JSON_pack_timestamp ("date", - date), - TALER_JSON_pack_amount ("amount", - amount)); - eh = TALER_EXCHANGE_curl_easy_get_ (dp->url); - if ( (NULL == eh) || - (GNUNET_OK != - TALER_curl_easy_post (&dp->post_ctx, - eh, - body)) ) - { - GNUNET_break (0); - if (NULL != eh) - curl_easy_cleanup (eh); - json_decref (body); - GNUNET_free (dp->url); - return NULL; - } - json_decref (body); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Requesting URL '%s'\n", - dp->url); - dp->job = GNUNET_CURL_job_add2 (ctx, - eh, - dp->post_ctx.headers, - &handle_drain_profits_finished, - dp); - if (NULL == dp->job) - { - TALER_EXCHANGE_management_drain_profits_cancel (dp); - return NULL; - } - return dp; -} - - -void -TALER_EXCHANGE_management_drain_profits_cancel ( - struct TALER_EXCHANGE_ManagementDrainProfitsHandle *dp) -{ - if (NULL != dp->job) - { - GNUNET_CURL_job_cancel (dp->job); - dp->job = NULL; - } - TALER_curl_easy_post_finished (&dp->post_ctx); - GNUNET_free (dp->url); - GNUNET_free (dp); -} diff --git a/src/lib/exchange_api_management_get_keys.c b/src/lib/exchange_api_management_get_keys.c @@ -1,427 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2015-2023 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_management_get_keys.c - * @brief functions to obtain future online keys of the exchange - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include "taler/taler_json_lib.h" -#include <gnunet/gnunet_curl_lib.h> -#include <microhttpd.h> -#include "taler/taler_exchange_service.h" -#include "exchange_api_curl_defaults.h" -#include "taler/taler_signatures.h" -#include "taler/taler_curl_lib.h" -#include "taler/taler_util.h" -#include "taler/taler_json_lib.h" - -/** - * Set to 1 for extra debug logging. - */ -#define DEBUG 0 - - -/** - * @brief Handle for a GET /management/keys request. - */ -struct TALER_EXCHANGE_GetManagementKeysHandle -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_ManagementGetKeysCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Reference to the execution context. - */ - struct GNUNET_CURL_Context *ctx; -}; - - -/** - * Handle the case that the response was of type #MHD_HTTP_OK. - * - * @param[in,out] gh request handle - * @param response the response - * @return #GNUNET_OK if the response was well-formed - */ -static enum GNUNET_GenericReturnValue -handle_ok (struct TALER_EXCHANGE_GetManagementKeysHandle *gh, - const json_t *response) -{ - struct TALER_EXCHANGE_ManagementGetKeysResponse gkr = { - .hr.http_status = MHD_HTTP_OK, - .hr.reply = response, - }; - struct TALER_EXCHANGE_FutureKeys *fk - = &gkr.details.ok.keys; - const json_t *sk; - const json_t *dk; - bool ok; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_array_const ("future_denoms", - &dk), - GNUNET_JSON_spec_array_const ("future_signkeys", - &sk), - GNUNET_JSON_spec_fixed_auto ("master_pub", - &fk->master_pub), - GNUNET_JSON_spec_fixed_auto ("denom_secmod_public_key", - &fk->denom_secmod_public_key), - GNUNET_JSON_spec_fixed_auto ("denom_secmod_cs_public_key", - &fk->denom_secmod_cs_public_key), - GNUNET_JSON_spec_fixed_auto ("signkey_secmod_public_key", - &fk->signkey_secmod_public_key), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (response, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - fk->num_sign_keys = json_array_size (sk); - fk->num_denom_keys = json_array_size (dk); - fk->sign_keys = GNUNET_new_array ( - fk->num_sign_keys, - struct TALER_EXCHANGE_FutureSigningPublicKey); - fk->denom_keys = GNUNET_new_array ( - fk->num_denom_keys, - struct TALER_EXCHANGE_FutureDenomPublicKey); - ok = true; - for (unsigned int i = 0; i<fk->num_sign_keys; i++) - { - json_t *j = json_array_get (sk, - i); - struct TALER_EXCHANGE_FutureSigningPublicKey *sign_key - = &fk->sign_keys[i]; - struct GNUNET_JSON_Specification ispec[] = { - GNUNET_JSON_spec_fixed_auto ("key", - &sign_key->key), - GNUNET_JSON_spec_fixed_auto ("signkey_secmod_sig", - &sign_key->signkey_secmod_sig), - GNUNET_JSON_spec_timestamp ("stamp_start", - &sign_key->valid_from), - GNUNET_JSON_spec_timestamp ("stamp_expire", - &sign_key->valid_until), - GNUNET_JSON_spec_timestamp ("stamp_end", - &sign_key->valid_legal), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - ispec, - NULL, NULL)) - { - GNUNET_break_op (0); - ok = false; - break; - } - { - struct GNUNET_TIME_Relative duration - = GNUNET_TIME_absolute_get_difference (sign_key->valid_from.abs_time, - sign_key->valid_until.abs_time); - - if (GNUNET_OK != - TALER_exchange_secmod_eddsa_verify ( - &sign_key->key, - sign_key->valid_from, - duration, - &fk->signkey_secmod_public_key, - &sign_key->signkey_secmod_sig)) - { - GNUNET_break_op (0); - ok = false; - break; - } - } - } - for (unsigned int i = 0; i<fk->num_denom_keys; i++) - { - json_t *j = json_array_get (dk, - i); - struct TALER_EXCHANGE_FutureDenomPublicKey *denom_key - = &fk->denom_keys[i]; - const char *section_name; - struct GNUNET_JSON_Specification ispec[] = { - TALER_JSON_spec_amount_any ("value", - &denom_key->value), - GNUNET_JSON_spec_timestamp ("stamp_start", - &denom_key->valid_from), - GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw", - &denom_key->withdraw_valid_until), - GNUNET_JSON_spec_timestamp ("stamp_expire_deposit", - &denom_key->expire_deposit), - GNUNET_JSON_spec_timestamp ("stamp_expire_legal", - &denom_key->expire_legal), - TALER_JSON_spec_denom_pub ("denom_pub", - &denom_key->key), - TALER_JSON_spec_amount_any ("fee_withdraw", - &denom_key->fee_withdraw), - TALER_JSON_spec_amount_any ("fee_deposit", - &denom_key->fee_deposit), - TALER_JSON_spec_amount_any ("fee_refresh", - &denom_key->fee_refresh), - TALER_JSON_spec_amount_any ("fee_refund", - &denom_key->fee_refund), - GNUNET_JSON_spec_fixed_auto ("denom_secmod_sig", - &denom_key->denom_secmod_sig), - GNUNET_JSON_spec_string ("section_name", - &section_name), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - ispec, - NULL, NULL)) - { - GNUNET_break_op (0); -#if DEBUG - json_dumpf (j, - stderr, - JSON_INDENT (2)); -#endif - ok = false; - break; - } - - { - struct TALER_DenominationHashP h_denom_pub; - struct GNUNET_TIME_Relative duration - = GNUNET_TIME_absolute_get_difference ( - denom_key->valid_from.abs_time, - denom_key->withdraw_valid_until.abs_time); - - TALER_denom_pub_hash (&denom_key->key, - &h_denom_pub); - switch (denom_key->key.bsign_pub_key->cipher) - { - case GNUNET_CRYPTO_BSA_RSA: - { - struct TALER_RsaPubHashP h_rsa; - - TALER_rsa_pub_hash ( - denom_key->key.bsign_pub_key->details.rsa_public_key, - &h_rsa); - if (GNUNET_OK != - TALER_exchange_secmod_rsa_verify (&h_rsa, - section_name, - denom_key->valid_from, - duration, - &fk->denom_secmod_public_key, - &denom_key->denom_secmod_sig)) - { - GNUNET_break_op (0); - ok = false; - break; - } - } - break; - case GNUNET_CRYPTO_BSA_CS: - { - struct TALER_CsPubHashP h_cs; - - TALER_cs_pub_hash ( - &denom_key->key.bsign_pub_key->details.cs_public_key, - &h_cs); - if (GNUNET_OK != - TALER_exchange_secmod_cs_verify (&h_cs, - section_name, - denom_key->valid_from, - duration, - &fk->denom_secmod_cs_public_key, - &denom_key->denom_secmod_sig)) - { - GNUNET_break_op (0); - ok = false; - break; - } - } - break; - default: - GNUNET_break_op (0); - ok = false; - break; - } - } - if (! ok) - break; - } - if (ok) - { - gh->cb (gh->cb_cls, - &gkr); - } - for (unsigned int i = 0; i<fk->num_denom_keys; i++) - TALER_denom_pub_free (&fk->denom_keys[i].key); - GNUNET_free (fk->sign_keys); - GNUNET_free (fk->denom_keys); - return (ok) ? GNUNET_OK : GNUNET_SYSERR; -} - - -/** - * Function called when we're done processing the - * HTTP GET /management/keys request. - * - * @param cls the `struct TALER_EXCHANGE_GetManagementKeysHandle *` - * @param response_code HTTP response code, 0 on error - * @param response response body, NULL if not in JSON - */ -static void -handle_get_keys_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_GetManagementKeysHandle *gh = cls; - const json_t *json = response; - struct TALER_EXCHANGE_ManagementGetKeysResponse gkr = { - .hr.http_status = (unsigned int) response_code, - .hr.reply = json - }; - - gh->job = NULL; - switch (response_code) - { - case MHD_HTTP_OK: - if (GNUNET_OK == - handle_ok (gh, - response)) - { - gh->cb = NULL; - } - else - { - response_code = 0; - } - break; - case MHD_HTTP_NOT_FOUND: - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Server did not find handler at `%s'. Did you configure the correct exchange base URL?\n", - gh->url); - if (NULL != json) - { - gkr.hr.ec = TALER_JSON_get_error_code (json); - gkr.hr.hint = TALER_JSON_get_error_hint (json); - } - else - { - gkr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - gkr.hr.hint = TALER_ErrorCode_get_hint (gkr.hr.ec); - } - break; - default: - /* unexpected response code */ - if (NULL != json) - { - gkr.hr.ec = TALER_JSON_get_error_code (json); - gkr.hr.hint = TALER_JSON_get_error_hint (json); - } - else - { - gkr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - gkr.hr.hint = TALER_ErrorCode_get_hint (gkr.hr.ec); - } - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange management get keys\n", - (unsigned int) response_code, - (int) gkr.hr.ec); - break; - } - if (NULL != gh->cb) - { - gh->cb (gh->cb_cls, - &gkr); - gh->cb = NULL; - } - TALER_EXCHANGE_get_management_keys_cancel (gh); -}; - - -struct TALER_EXCHANGE_GetManagementKeysHandle * -TALER_EXCHANGE_get_management_keys (struct GNUNET_CURL_Context *ctx, - const char *url, - TALER_EXCHANGE_ManagementGetKeysCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_GetManagementKeysHandle *gh; - CURL *eh; - - gh = GNUNET_new (struct TALER_EXCHANGE_GetManagementKeysHandle); - gh->cb = cb; - gh->cb_cls = cb_cls; - gh->ctx = ctx; - gh->url = TALER_url_join (url, - "management/keys", - NULL); - if (NULL == gh->url) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Could not construct request URL.\n"); - GNUNET_free (gh); - return NULL; - } - eh = TALER_EXCHANGE_curl_easy_get_ (gh->url); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Requesting URL '%s'\n", - gh->url); - gh->job = GNUNET_CURL_job_add (ctx, - eh, - &handle_get_keys_finished, - gh); - if (NULL == gh->job) - { - TALER_EXCHANGE_get_management_keys_cancel (gh); - return NULL; - } - return gh; -} - - -void -TALER_EXCHANGE_get_management_keys_cancel ( - struct TALER_EXCHANGE_GetManagementKeysHandle *gh) -{ - if (NULL != gh->job) - { - GNUNET_CURL_job_cancel (gh->job); - gh->job = NULL; - } - GNUNET_free (gh->url); - GNUNET_free (gh); -} diff --git a/src/lib/exchange_api_management_post_extensions.c b/src/lib/exchange_api_management_post_extensions.c @@ -1,213 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2015-2023 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> - */ -/** - * @file lib/exchange_api_management_post_extensions.c - * @brief functions to handle the settings for extensions (p2p and age restriction) - * @author Özgür Kesim - */ -#include "taler/platform.h" -#include "taler/taler_json_lib.h" -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_extensions.h" -#include "exchange_api_curl_defaults.h" -#include "taler/taler_exchange_service.h" -#include "taler/taler_signatures.h" -#include "taler/taler_curl_lib.h" -#include "taler/taler_json_lib.h" - - -/** - * @brief Handle for a POST /management/extensions request. - */ -struct TALER_EXCHANGE_ManagementPostExtensionsHandle -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Minor context that holds body and headers. - */ - struct TALER_CURL_PostContext post_ctx; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_ManagementPostExtensionsCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Reference to the execution context. - */ - struct GNUNET_CURL_Context *ctx; -}; - - -/** - * Function called when we're done processing the - * HTTP POST /management/extensions request. - * - * @param cls the `struct TALER_EXCHANGE_ManagementPostExtensionsHandle *` - * @param response_code HTTP response code, 0 on error - * @param response response body, NULL if not in JSON - */ -static void -handle_post_extensions_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_ManagementPostExtensionsHandle *ph = cls; - const json_t *json = response; - struct TALER_EXCHANGE_ManagementPostExtensionsResponse per = { - .hr.http_status = (unsigned int) response_code, - .hr.reply = json - }; - - ph->job = NULL; - switch (response_code) - { - case MHD_HTTP_NO_CONTENT: - break; - case MHD_HTTP_FORBIDDEN: - per.hr.ec = TALER_JSON_get_error_code (json); - per.hr.hint = TALER_JSON_get_error_hint (json); - break; - case MHD_HTTP_NOT_FOUND: - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Server did not find handler at `%s'. Did you configure the correct exchange base URL?\n", - ph->url); - if (NULL != json) - { - per.hr.ec = TALER_JSON_get_error_code (json); - per.hr.hint = TALER_JSON_get_error_hint (json); - } - else - { - per.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - per.hr.hint = TALER_ErrorCode_get_hint (per.hr.ec); - } - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - per.hr.ec = TALER_JSON_get_error_code (json); - per.hr.hint = TALER_JSON_get_error_hint (json); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange management post extensions\n", - (unsigned int) response_code, - (int) per.hr.ec); - break; - } - if (NULL != ph->cb) - { - ph->cb (ph->cb_cls, - &per); - ph->cb = NULL; - } - TALER_EXCHANGE_management_post_extensions_cancel (ph); -} - - -struct TALER_EXCHANGE_ManagementPostExtensionsHandle * -TALER_EXCHANGE_management_post_extensions ( - struct GNUNET_CURL_Context *ctx, - const char *url, - const struct TALER_EXCHANGE_ManagementPostExtensionsData *ped, - TALER_EXCHANGE_ManagementPostExtensionsCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_ManagementPostExtensionsHandle *ph; - CURL *eh = NULL; - json_t *body = NULL; - - ph = GNUNET_new (struct TALER_EXCHANGE_ManagementPostExtensionsHandle); - ph->cb = cb; - ph->cb_cls = cb_cls; - ph->ctx = ctx; - ph->url = TALER_url_join (url, - "management/extensions", - NULL); - if (NULL == ph->url) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Could not construct request URL.\n"); - GNUNET_free (ph); - return NULL; - } - - body = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_object_steal ("extensions", - (json_t *) ped->extensions), - GNUNET_JSON_pack_data_auto ("extensions_sig", - &ped->extensions_sig)); - - eh = TALER_EXCHANGE_curl_easy_get_ (ph->url); - if ( (NULL == eh) || - (GNUNET_OK != - TALER_curl_easy_post (&ph->post_ctx, - eh, - body)) ) - { - GNUNET_break (0); - if (NULL != eh) - curl_easy_cleanup (eh); - json_decref (body); - GNUNET_free (ph->url); - return NULL; - } - json_decref (body); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Requesting URL '%s'\n", - ph->url); - ph->job = GNUNET_CURL_job_add2 (ctx, - eh, - ph->post_ctx.headers, - &handle_post_extensions_finished, - ph); - if (NULL == ph->job) - { - TALER_EXCHANGE_management_post_extensions_cancel (ph); - return NULL; - } - return ph; -} - - -void -TALER_EXCHANGE_management_post_extensions_cancel ( - struct TALER_EXCHANGE_ManagementPostExtensionsHandle *ph) -{ - if (NULL != ph->job) - { - GNUNET_CURL_job_cancel (ph->job); - ph->job = NULL; - } - TALER_curl_easy_post_finished (&ph->post_ctx); - GNUNET_free (ph->url); - GNUNET_free (ph); -} diff --git a/src/lib/exchange_api_management_post_keys.c b/src/lib/exchange_api_management_post_keys.c @@ -1,238 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2015-2021 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_management_post_keys.c - * @brief functions to affirm the validity of exchange keys using the master private key - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include "taler/taler_json_lib.h" -#include <gnunet/gnunet_curl_lib.h> -#include <microhttpd.h> -#include "taler/taler_exchange_service.h" -#include "exchange_api_curl_defaults.h" -#include "taler/taler_signatures.h" -#include "taler/taler_curl_lib.h" -#include "taler/taler_json_lib.h" - - -/** - * @brief Handle for a POST /management/keys request. - */ -struct TALER_EXCHANGE_ManagementPostKeysHandle -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Minor context that holds body and headers. - */ - struct TALER_CURL_PostContext post_ctx; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_ManagementPostKeysCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Reference to the execution context. - */ - struct GNUNET_CURL_Context *ctx; -}; - - -/** - * Function called when we're done processing the - * HTTP POST /management/keys request. - * - * @param cls the `struct TALER_EXCHANGE_ManagementPostKeysHandle *` - * @param response_code HTTP response code, 0 on error - * @param response response body, NULL if not in JSON - */ -static void -handle_post_keys_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_ManagementPostKeysHandle *ph = cls; - const json_t *json = response; - struct TALER_EXCHANGE_ManagementPostKeysResponse pkr = { - .hr.http_status = (unsigned int) response_code, - .hr.reply = json - }; - - ph->job = NULL; - switch (response_code) - { - case MHD_HTTP_NO_CONTENT: - break; - case MHD_HTTP_FORBIDDEN: - pkr.hr.ec = TALER_JSON_get_error_code (json); - pkr.hr.hint = TALER_JSON_get_error_hint (json); - break; - case MHD_HTTP_NOT_FOUND: - pkr.hr.ec = TALER_JSON_get_error_code (json); - pkr.hr.hint = TALER_JSON_get_error_hint (json); - break; - case MHD_HTTP_REQUEST_ENTITY_TOO_LARGE: - pkr.hr.ec = TALER_JSON_get_error_code (json); - pkr.hr.hint = TALER_JSON_get_error_hint (json); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - pkr.hr.ec = TALER_JSON_get_error_code (json); - pkr.hr.hint = TALER_JSON_get_error_hint (json); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange management post keys\n", - (unsigned int) response_code, - (int) pkr.hr.ec); - break; - } - if (NULL != ph->cb) - { - ph->cb (ph->cb_cls, - &pkr); - ph->cb = NULL; - } - TALER_EXCHANGE_post_management_keys_cancel (ph); -} - - -struct TALER_EXCHANGE_ManagementPostKeysHandle * -TALER_EXCHANGE_post_management_keys ( - struct GNUNET_CURL_Context *ctx, - const char *url, - const struct TALER_EXCHANGE_ManagementPostKeysData *pkd, - TALER_EXCHANGE_ManagementPostKeysCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_ManagementPostKeysHandle *ph; - CURL *eh; - json_t *body; - json_t *denom_sigs; - json_t *signkey_sigs; - - ph = GNUNET_new (struct TALER_EXCHANGE_ManagementPostKeysHandle); - ph->cb = cb; - ph->cb_cls = cb_cls; - ph->ctx = ctx; - ph->url = TALER_url_join (url, - "management/keys", - NULL); - if (NULL == ph->url) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Could not construct request URL.\n"); - GNUNET_free (ph); - return NULL; - } - denom_sigs = json_array (); - GNUNET_assert (NULL != denom_sigs); - for (unsigned int i = 0; i<pkd->num_denom_sigs; i++) - { - const struct TALER_EXCHANGE_DenominationKeySignature *dks - = &pkd->denom_sigs[i]; - - GNUNET_assert (0 == - json_array_append_new ( - denom_sigs, - GNUNET_JSON_PACK ( - GNUNET_JSON_pack_data_auto ("h_denom_pub", - &dks->h_denom_pub), - GNUNET_JSON_pack_data_auto ("master_sig", - &dks->master_sig)))); - } - signkey_sigs = json_array (); - GNUNET_assert (NULL != signkey_sigs); - for (unsigned int i = 0; i<pkd->num_sign_sigs; i++) - { - const struct TALER_EXCHANGE_SigningKeySignature *sks - = &pkd->sign_sigs[i]; - - GNUNET_assert (0 == - json_array_append_new ( - signkey_sigs, - GNUNET_JSON_PACK ( - GNUNET_JSON_pack_data_auto ("exchange_pub", - &sks->exchange_pub), - GNUNET_JSON_pack_data_auto ("master_sig", - &sks->master_sig)))); - } - body = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_array_steal ("denom_sigs", - denom_sigs), - GNUNET_JSON_pack_array_steal ("signkey_sigs", - signkey_sigs)); - eh = TALER_EXCHANGE_curl_easy_get_ (ph->url); - if ( (NULL == eh) || - (GNUNET_OK != - TALER_curl_easy_post (&ph->post_ctx, - eh, - body)) ) - { - GNUNET_break (0); - if (NULL != eh) - curl_easy_cleanup (eh); - json_decref (body); - GNUNET_free (ph->url); - return NULL; - } - json_decref (body); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Requesting URL '%s'\n", - ph->url); - ph->job = GNUNET_CURL_job_add2 (ctx, - eh, - ph->post_ctx.headers, - &handle_post_keys_finished, - ph); - if (NULL == ph->job) - { - TALER_EXCHANGE_post_management_keys_cancel (ph); - return NULL; - } - return ph; -} - - -void -TALER_EXCHANGE_post_management_keys_cancel ( - struct TALER_EXCHANGE_ManagementPostKeysHandle *ph) -{ - if (NULL != ph->job) - { - GNUNET_CURL_job_cancel (ph->job); - ph->job = NULL; - } - TALER_curl_easy_post_finished (&ph->post_ctx); - GNUNET_free (ph->url); - GNUNET_free (ph); -} diff --git a/src/lib/exchange_api_management_revoke_denomination_key.c b/src/lib/exchange_api_management_revoke_denomination_key.c @@ -1,223 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2015-2024 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_management_revoke_denomination_key.c - * @brief functions to revoke an exchange denomination key - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include "taler/taler_json_lib.h" -#include <gnunet/gnunet_curl_lib.h> -#include <microhttpd.h> -#include "taler/taler_exchange_service.h" -#include "exchange_api_curl_defaults.h" -#include "taler/taler_signatures.h" -#include "taler/taler_curl_lib.h" -#include "taler/taler_json_lib.h" - - -/** - * @brief Handle for a POST /management/denominations/$H_DENOM_PUB/revoke request. - */ -struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Minor context that holds body and headers. - */ - struct TALER_CURL_PostContext post_ctx; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_ManagementRevokeDenominationKeyCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Reference to the execution context. - */ - struct GNUNET_CURL_Context *ctx; -}; - - -/** - * Function called when we're done processing the - * HTTP /management/denominations/$H_DENOM_PUB/revoke request. - * - * @param cls the `struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle *` - * @param response_code HTTP response code, 0 on error - * @param response response body, NULL if not in JSON - */ -static void -handle_revoke_denomination_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle *rh = cls; - const json_t *json = response; - struct TALER_EXCHANGE_ManagementRevokeDenominationResponse rdr = { - .hr.http_status = (unsigned int) response_code, - .hr.reply = json - }; - - rh->job = NULL; - switch (response_code) - { - case 0: - /* no reply */ - rdr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - rdr.hr.hint = "server offline?"; - break; - case MHD_HTTP_NO_CONTENT: - break; - case MHD_HTTP_FORBIDDEN: - rdr.hr.ec = TALER_JSON_get_error_code (json); - rdr.hr.hint = TALER_JSON_get_error_hint (json); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - rdr.hr.ec = TALER_JSON_get_error_code (json); - rdr.hr.hint = TALER_JSON_get_error_hint (json); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange management revoke denomination\n", - (unsigned int) response_code, - (int) rdr.hr.ec); - break; - } - if (NULL != rh->cb) - { - rh->cb (rh->cb_cls, - &rdr); - rh->cb = NULL; - } - TALER_EXCHANGE_management_revoke_denomination_key_cancel (rh); -} - - -struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle * -TALER_EXCHANGE_management_revoke_denomination_key ( - struct GNUNET_CURL_Context *ctx, - const char *url, - const struct TALER_DenominationHashP *h_denom_pub, - const struct TALER_MasterSignatureP *master_sig, - TALER_EXCHANGE_ManagementRevokeDenominationKeyCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle *rh; - CURL *eh; - json_t *body; - - rh = GNUNET_new (struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle); - rh->cb = cb; - rh->cb_cls = cb_cls; - rh->ctx = ctx; - { - char epub_str[sizeof (*h_denom_pub) * 2]; - char arg_str[sizeof (epub_str) + 64]; - char *end; - - end = GNUNET_STRINGS_data_to_string (h_denom_pub, - sizeof (*h_denom_pub), - epub_str, - sizeof (epub_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "management/denominations/%s/revoke", - epub_str); - rh->url = TALER_url_join (url, - arg_str, - NULL); - } - if (NULL == rh->url) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Could not construct request URL.\n"); - GNUNET_free (rh); - return NULL; - } - body = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_data_auto ("master_sig", - master_sig)); - if (NULL == body) - { - GNUNET_break (0); - GNUNET_free (rh->url); - GNUNET_free (rh); - return NULL; - } - eh = TALER_EXCHANGE_curl_easy_get_ (rh->url); - if ( (NULL == eh) || - (GNUNET_OK != - TALER_curl_easy_post (&rh->post_ctx, - eh, - body)) ) - { - GNUNET_break (0); - if (NULL != eh) - curl_easy_cleanup (eh); - json_decref (body); - GNUNET_free (rh->url); - GNUNET_free (rh); - return NULL; - } - json_decref (body); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Requesting URL '%s'\n", - rh->url); - rh->job = GNUNET_CURL_job_add2 (ctx, - eh, - rh->post_ctx.headers, - &handle_revoke_denomination_finished, - rh); - if (NULL == rh->job) - { - TALER_EXCHANGE_management_revoke_denomination_key_cancel (rh); - return NULL; - } - return rh; -} - - -void -TALER_EXCHANGE_management_revoke_denomination_key_cancel ( - struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle *rh) -{ - if (NULL != rh->job) - { - GNUNET_CURL_job_cancel (rh->job); - rh->job = NULL; - } - TALER_curl_easy_post_finished (&rh->post_ctx); - GNUNET_free (rh->url); - GNUNET_free (rh); -} diff --git a/src/lib/exchange_api_management_revoke_signing_key.c b/src/lib/exchange_api_management_revoke_signing_key.c @@ -1,213 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2015-2021 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_management_revoke_signing_key.c - * @brief functions to revoke an exchange online signing key - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include "taler/taler_json_lib.h" -#include <gnunet/gnunet_curl_lib.h> -#include <microhttpd.h> -#include "taler/taler_exchange_service.h" -#include "exchange_api_curl_defaults.h" -#include "taler/taler_signatures.h" -#include "taler/taler_curl_lib.h" -#include "taler/taler_json_lib.h" - - -struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Minor context that holds body and headers. - */ - struct TALER_CURL_PostContext post_ctx; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_ManagementRevokeSigningKeyCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Reference to the execution context. - */ - struct GNUNET_CURL_Context *ctx; -}; - - -/** - * Function called when we're done processing the - * HTTP /management/signkeys/%s/revoke request. - * - * @param cls the `struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle *` - * @param response_code HTTP response code, 0 on error - * @param response response body, NULL if not in JSON - */ -static void -handle_revoke_signing_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle *rh = cls; - const json_t *json = response; - struct TALER_EXCHANGE_ManagementRevokeSigningKeyResponse rsr = { - .hr.http_status = (unsigned int) response_code, - .hr.reply = json - }; - - rh->job = NULL; - switch (response_code) - { - case 0: - /* no reply */ - rsr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - rsr.hr.hint = "server offline?"; - break; - case MHD_HTTP_NO_CONTENT: - break; - case MHD_HTTP_FORBIDDEN: - rsr.hr.ec = TALER_JSON_get_error_code (json); - rsr.hr.hint = TALER_JSON_get_error_hint (json); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - rsr.hr.ec = TALER_JSON_get_error_code (json); - rsr.hr.hint = TALER_JSON_get_error_hint (json); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange management revoke signkey\n", - (unsigned int) response_code, - (int) rsr.hr.ec); - break; - } - if (NULL != rh->cb) - { - rh->cb (rh->cb_cls, - &rsr); - rh->cb = NULL; - } - TALER_EXCHANGE_management_revoke_signing_key_cancel (rh); -} - - -struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle * -TALER_EXCHANGE_management_revoke_signing_key ( - struct GNUNET_CURL_Context *ctx, - const char *url, - const struct TALER_ExchangePublicKeyP *exchange_pub, - const struct TALER_MasterSignatureP *master_sig, - TALER_EXCHANGE_ManagementRevokeSigningKeyCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle *rh; - CURL *eh; - json_t *body; - - rh = GNUNET_new (struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle); - rh->cb = cb; - rh->cb_cls = cb_cls; - rh->ctx = ctx; - { - char epub_str[sizeof (*exchange_pub) * 2]; - char arg_str[sizeof (epub_str) + 64]; - char *end; - - end = GNUNET_STRINGS_data_to_string (exchange_pub, - sizeof (*exchange_pub), - epub_str, - sizeof (epub_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "management/signkeys/%s/revoke", - epub_str); - rh->url = TALER_url_join (url, - arg_str, - NULL); - } - if (NULL == rh->url) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Could not construct request URL.\n"); - GNUNET_free (rh); - return NULL; - } - body = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_data_auto ("master_sig", - master_sig)); - eh = TALER_EXCHANGE_curl_easy_get_ (rh->url); - if ( (NULL == eh) || - (GNUNET_OK != - TALER_curl_easy_post (&rh->post_ctx, - eh, - body)) ) - { - GNUNET_break (0); - if (NULL != eh) - curl_easy_cleanup (eh); - json_decref (body); - GNUNET_free (rh->url); - GNUNET_free (rh); - return NULL; - } - json_decref (body); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Requesting URL '%s'\n", - rh->url); - rh->job = GNUNET_CURL_job_add2 (ctx, - eh, - rh->post_ctx.headers, - &handle_revoke_signing_finished, - rh); - if (NULL == rh->job) - { - TALER_EXCHANGE_management_revoke_signing_key_cancel (rh); - return NULL; - } - return rh; -} - - -void -TALER_EXCHANGE_management_revoke_signing_key_cancel ( - struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle *rh) -{ - if (NULL != rh->job) - { - GNUNET_CURL_job_cancel (rh->job); - rh->job = NULL; - } - TALER_curl_easy_post_finished (&rh->post_ctx); - GNUNET_free (rh->url); - GNUNET_free (rh); -} diff --git a/src/lib/exchange_api_management_set_global_fee.c b/src/lib/exchange_api_management_set_global_fee.c @@ -1,237 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2020-2022 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_management_set_global_fee.c - * @brief functions to set global fees at an exchange - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include "taler/taler_json_lib.h" -#include <gnunet/gnunet_curl_lib.h> -#include <microhttpd.h> -#include "exchange_api_curl_defaults.h" -#include "taler/taler_exchange_service.h" -#include "taler/taler_signatures.h" -#include "taler/taler_curl_lib.h" -#include "taler/taler_json_lib.h" - - -struct TALER_EXCHANGE_ManagementSetGlobalFeeHandle -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Minor context that holds body and headers. - */ - struct TALER_CURL_PostContext post_ctx; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_ManagementSetGlobalFeeCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Reference to the execution context. - */ - struct GNUNET_CURL_Context *ctx; -}; - - -/** - * Function called when we're done processing the - * HTTP /management/global request. - * - * @param cls the `struct TALER_EXCHANGE_ManagementAuditorEnableHandle *` - * @param response_code HTTP response code, 0 on error - * @param response response body, NULL if not in JSON - */ -static void -handle_set_global_fee_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_ManagementSetGlobalFeeHandle *sgfh = cls; - const json_t *json = response; - struct TALER_EXCHANGE_ManagementSetGlobalFeeResponse sfr = { - .hr.http_status = (unsigned int) response_code, - .hr.reply = json - }; - - sgfh->job = NULL; - switch (response_code) - { - case MHD_HTTP_NO_CONTENT: - break; - case MHD_HTTP_FORBIDDEN: - sfr.hr.ec = TALER_JSON_get_error_code (json); - sfr.hr.hint = TALER_JSON_get_error_hint (json); - break; - case MHD_HTTP_NOT_FOUND: - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Server did not find handler at `%s'. Did you configure the correct exchange base URL?\n", - sgfh->url); - if (NULL != json) - { - sfr.hr.ec = TALER_JSON_get_error_code (json); - sfr.hr.hint = TALER_JSON_get_error_hint (json); - } - else - { - sfr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - sfr.hr.hint = TALER_ErrorCode_get_hint (sfr.hr.ec); - } - break; - case MHD_HTTP_CONFLICT: - sfr.hr.ec = TALER_JSON_get_error_code (json); - sfr.hr.hint = TALER_JSON_get_error_hint (json); - break; - case MHD_HTTP_PRECONDITION_FAILED: - sfr.hr.ec = TALER_JSON_get_error_code (json); - sfr.hr.hint = TALER_JSON_get_error_hint (json); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - sfr.hr.ec = TALER_JSON_get_error_code (json); - sfr.hr.hint = TALER_JSON_get_error_hint (json); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange management set global fee\n", - (unsigned int) response_code, - (int) sfr.hr.ec); - break; - } - if (NULL != sgfh->cb) - { - sgfh->cb (sgfh->cb_cls, - &sfr); - sgfh->cb = NULL; - } - TALER_EXCHANGE_management_set_global_fees_cancel (sgfh); -} - - -struct TALER_EXCHANGE_ManagementSetGlobalFeeHandle * -TALER_EXCHANGE_management_set_global_fees ( - struct GNUNET_CURL_Context *ctx, - const char *exchange_base_url, - struct GNUNET_TIME_Timestamp validity_start, - struct GNUNET_TIME_Timestamp validity_end, - const struct TALER_GlobalFeeSet *fees, - struct GNUNET_TIME_Relative purse_timeout, - struct GNUNET_TIME_Relative history_expiration, - uint32_t purse_account_limit, - const struct TALER_MasterSignatureP *master_sig, - TALER_EXCHANGE_ManagementSetGlobalFeeCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_ManagementSetGlobalFeeHandle *sgfh; - CURL *eh; - json_t *body; - - sgfh = GNUNET_new (struct TALER_EXCHANGE_ManagementSetGlobalFeeHandle); - sgfh->cb = cb; - sgfh->cb_cls = cb_cls; - sgfh->ctx = ctx; - sgfh->url = TALER_url_join (exchange_base_url, - "management/global-fee", - NULL); - if (NULL == sgfh->url) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Could not construct request URL.\n"); - GNUNET_free (sgfh); - return NULL; - } - body = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_data_auto ("master_sig", - master_sig), - GNUNET_JSON_pack_timestamp ("fee_start", - validity_start), - GNUNET_JSON_pack_timestamp ("fee_end", - validity_end), - TALER_JSON_pack_amount ("history_fee", - &fees->history), - TALER_JSON_pack_amount ("account_fee", - &fees->account), - TALER_JSON_pack_amount ("purse_fee", - &fees->purse), - GNUNET_JSON_pack_time_rel ("purse_timeout", - purse_timeout), - GNUNET_JSON_pack_time_rel ("history_expiration", - history_expiration), - GNUNET_JSON_pack_uint64 ("purse_account_limit", - purse_account_limit)); - eh = TALER_EXCHANGE_curl_easy_get_ (sgfh->url); - if ( (NULL == eh) || - (GNUNET_OK != - TALER_curl_easy_post (&sgfh->post_ctx, - eh, - body)) ) - { - GNUNET_break (0); - if (NULL != eh) - curl_easy_cleanup (eh); - json_decref (body); - GNUNET_free (sgfh->url); - GNUNET_free (sgfh); - return NULL; - } - json_decref (body); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Requesting URL '%s'\n", - sgfh->url); - sgfh->job = GNUNET_CURL_job_add2 (ctx, - eh, - sgfh->post_ctx.headers, - &handle_set_global_fee_finished, - sgfh); - if (NULL == sgfh->job) - { - TALER_EXCHANGE_management_set_global_fees_cancel (sgfh); - return NULL; - } - return sgfh; -} - - -void -TALER_EXCHANGE_management_set_global_fees_cancel ( - struct TALER_EXCHANGE_ManagementSetGlobalFeeHandle *sgfh) -{ - if (NULL != sgfh->job) - { - GNUNET_CURL_job_cancel (sgfh->job); - sgfh->job = NULL; - } - TALER_curl_easy_post_finished (&sgfh->post_ctx); - GNUNET_free (sgfh->url); - GNUNET_free (sgfh); -} diff --git a/src/lib/exchange_api_management_set_wire_fee.c b/src/lib/exchange_api_management_set_wire_fee.c @@ -1,229 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2020-2022 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_management_set_wire_fee.c - * @brief functions to set wire fees at an exchange - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include "taler/taler_json_lib.h" -#include <gnunet/gnunet_curl_lib.h> -#include <microhttpd.h> -#include "exchange_api_curl_defaults.h" -#include "taler/taler_exchange_service.h" -#include "taler/taler_signatures.h" -#include "taler/taler_curl_lib.h" -#include "taler/taler_json_lib.h" - - -struct TALER_EXCHANGE_ManagementSetWireFeeHandle -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Minor context that holds body and headers. - */ - struct TALER_CURL_PostContext post_ctx; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_ManagementSetWireFeeCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Reference to the execution context. - */ - struct GNUNET_CURL_Context *ctx; -}; - - -/** - * Function called when we're done processing the - * HTTP /management/wire request. - * - * @param cls the `struct TALER_EXCHANGE_ManagementAuditorEnableHandle *` - * @param response_code HTTP response code, 0 on error - * @param response response body, NULL if not in JSON - */ -static void -handle_set_wire_fee_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_ManagementSetWireFeeHandle *swfh = cls; - const json_t *json = response; - struct TALER_EXCHANGE_ManagementSetWireFeeResponse swr = { - .hr.http_status = (unsigned int) response_code, - .hr.reply = json - }; - - swfh->job = NULL; - switch (response_code) - { - case MHD_HTTP_NO_CONTENT: - break; - case MHD_HTTP_FORBIDDEN: - swr.hr.ec = TALER_JSON_get_error_code (json); - swr.hr.hint = TALER_JSON_get_error_hint (json); - break; - case MHD_HTTP_NOT_FOUND: - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Server did not find handler at `%s'. Did you configure the correct exchange base URL?\n", - swfh->url); - if (NULL != json) - { - swr.hr.ec = TALER_JSON_get_error_code (json); - swr.hr.hint = TALER_JSON_get_error_hint (json); - } - else - { - swr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - swr.hr.hint = TALER_ErrorCode_get_hint (swr.hr.ec); - } - break; - case MHD_HTTP_CONFLICT: - swr.hr.ec = TALER_JSON_get_error_code (json); - swr.hr.hint = TALER_JSON_get_error_hint (json); - break; - case MHD_HTTP_PRECONDITION_FAILED: - swr.hr.ec = TALER_JSON_get_error_code (json); - swr.hr.hint = TALER_JSON_get_error_hint (json); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - swr.hr.ec = TALER_JSON_get_error_code (json); - swr.hr.hint = TALER_JSON_get_error_hint (json); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange management set wire fee\n", - (unsigned int) response_code, - (int) swr.hr.ec); - break; - } - if (NULL != swfh->cb) - { - swfh->cb (swfh->cb_cls, - &swr); - swfh->cb = NULL; - } - TALER_EXCHANGE_management_set_wire_fees_cancel (swfh); -} - - -struct TALER_EXCHANGE_ManagementSetWireFeeHandle * -TALER_EXCHANGE_management_set_wire_fees ( - struct GNUNET_CURL_Context *ctx, - const char *exchange_base_url, - const char *wire_method, - struct GNUNET_TIME_Timestamp validity_start, - struct GNUNET_TIME_Timestamp validity_end, - const struct TALER_WireFeeSet *fees, - const struct TALER_MasterSignatureP *master_sig, - TALER_EXCHANGE_ManagementSetWireFeeCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_ManagementSetWireFeeHandle *swfh; - CURL *eh; - json_t *body; - - swfh = GNUNET_new (struct TALER_EXCHANGE_ManagementSetWireFeeHandle); - swfh->cb = cb; - swfh->cb_cls = cb_cls; - swfh->ctx = ctx; - swfh->url = TALER_url_join (exchange_base_url, - "management/wire-fee", - NULL); - if (NULL == swfh->url) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Could not construct request URL.\n"); - GNUNET_free (swfh); - return NULL; - } - body = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("wire_method", - wire_method), - GNUNET_JSON_pack_data_auto ("master_sig", - master_sig), - GNUNET_JSON_pack_timestamp ("fee_start", - validity_start), - GNUNET_JSON_pack_timestamp ("fee_end", - validity_end), - TALER_JSON_pack_amount ("closing_fee", - &fees->closing), - TALER_JSON_pack_amount ("wire_fee", - &fees->wire)); - eh = TALER_EXCHANGE_curl_easy_get_ (swfh->url); - if ( (NULL == eh) || - (GNUNET_OK != - TALER_curl_easy_post (&swfh->post_ctx, - eh, - body)) ) - { - GNUNET_break (0); - if (NULL != eh) - curl_easy_cleanup (eh); - json_decref (body); - GNUNET_free (swfh->url); - GNUNET_free (swfh); - return NULL; - } - json_decref (body); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Requesting URL '%s'\n", - swfh->url); - swfh->job = GNUNET_CURL_job_add2 (ctx, - eh, - swfh->post_ctx.headers, - &handle_set_wire_fee_finished, - swfh); - if (NULL == swfh->job) - { - TALER_EXCHANGE_management_set_wire_fees_cancel (swfh); - return NULL; - } - return swfh; -} - - -void -TALER_EXCHANGE_management_set_wire_fees_cancel ( - struct TALER_EXCHANGE_ManagementSetWireFeeHandle *swfh) -{ - if (NULL != swfh->job) - { - GNUNET_CURL_job_cancel (swfh->job); - swfh->job = NULL; - } - TALER_curl_easy_post_finished (&swfh->post_ctx); - GNUNET_free (swfh->url); - GNUNET_free (swfh); -} diff --git a/src/lib/exchange_api_management_wire_disable.c b/src/lib/exchange_api_management_wire_disable.c @@ -1,222 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2015-2023 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_management_wire_disable.c - * @brief functions to disable an exchange wire method / bank account - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include "taler/taler_json_lib.h" -#include <gnunet/gnunet_curl_lib.h> -#include <microhttpd.h> -#include "taler/taler_exchange_service.h" -#include "exchange_api_curl_defaults.h" -#include "taler/taler_signatures.h" -#include "taler/taler_curl_lib.h" -#include "taler/taler_json_lib.h" - - -struct TALER_EXCHANGE_ManagementWireDisableHandle -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Minor context that holds body and headers. - */ - struct TALER_CURL_PostContext post_ctx; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_ManagementWireDisableCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Reference to the execution context. - */ - struct GNUNET_CURL_Context *ctx; -}; - - -/** - * Function called when we're done processing the - * HTTP /management/wire/disable request. - * - * @param cls the `struct TALER_EXCHANGE_ManagementAuditorDisableHandle *` - * @param response_code HTTP response code, 0 on error - * @param response response body, NULL if not in JSON - */ -static void -handle_auditor_disable_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_ManagementWireDisableHandle *wh = cls; - const json_t *json = response; - struct TALER_EXCHANGE_ManagementWireDisableResponse wdr = { - .hr.http_status = (unsigned int) response_code, - .hr.reply = json - }; - - wh->job = NULL; - switch (response_code) - { - case 0: - /* no reply */ - wdr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - wdr.hr.hint = "server offline?"; - break; - case MHD_HTTP_NO_CONTENT: - break; - case MHD_HTTP_FORBIDDEN: - wdr.hr.ec = TALER_JSON_get_error_code (json); - wdr.hr.hint = TALER_JSON_get_error_hint (json); - break; - case MHD_HTTP_NOT_FOUND: - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Server did not find handler at `%s'. Did you configure the correct exchange base URL?\n", - wh->url); - if (NULL != json) - { - wdr.hr.ec = TALER_JSON_get_error_code (json); - wdr.hr.hint = TALER_JSON_get_error_hint (json); - } - else - { - wdr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - wdr.hr.hint = TALER_ErrorCode_get_hint (wdr.hr.ec); - } - break; - case MHD_HTTP_CONFLICT: - wdr.hr.ec = TALER_JSON_get_error_code (json); - wdr.hr.hint = TALER_JSON_get_error_hint (json); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - wdr.hr.ec = TALER_JSON_get_error_code (json); - wdr.hr.hint = TALER_JSON_get_error_hint (json); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d exchange management disable wire\n", - (unsigned int) response_code, - (int) wdr.hr.ec); - break; - } - if (NULL != wh->cb) - { - wh->cb (wh->cb_cls, - &wdr); - wh->cb = NULL; - } - TALER_EXCHANGE_management_disable_wire_cancel (wh); -} - - -struct TALER_EXCHANGE_ManagementWireDisableHandle * -TALER_EXCHANGE_management_disable_wire ( - struct GNUNET_CURL_Context *ctx, - const char *url, - const struct TALER_FullPayto payto_uri, - struct GNUNET_TIME_Timestamp validity_end, - const struct TALER_MasterSignatureP *master_sig, - TALER_EXCHANGE_ManagementWireDisableCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_ManagementWireDisableHandle *wh; - CURL *eh; - json_t *body; - - wh = GNUNET_new (struct TALER_EXCHANGE_ManagementWireDisableHandle); - wh->cb = cb; - wh->cb_cls = cb_cls; - wh->ctx = ctx; - wh->url = TALER_url_join (url, - "management/wire/disable", - NULL); - if (NULL == wh->url) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Could not construct request URL.\n"); - GNUNET_free (wh); - return NULL; - } - body = GNUNET_JSON_PACK ( - TALER_JSON_pack_full_payto ("payto_uri", - payto_uri), - GNUNET_JSON_pack_data_auto ("master_sig_del", - master_sig), - GNUNET_JSON_pack_timestamp ("validity_end", - validity_end)); - eh = TALER_EXCHANGE_curl_easy_get_ (wh->url); - if ( (NULL == eh) || - (GNUNET_OK != - TALER_curl_easy_post (&wh->post_ctx, - eh, - body)) ) - { - GNUNET_break (0); - if (NULL != eh) - curl_easy_cleanup (eh); - json_decref (body); - GNUNET_free (wh->url); - GNUNET_free (wh); - return NULL; - } - json_decref (body); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Requesting URL '%s'\n", - wh->url); - wh->job = GNUNET_CURL_job_add2 (ctx, - eh, - wh->post_ctx.headers, - &handle_auditor_disable_finished, - wh); - if (NULL == wh->job) - { - TALER_EXCHANGE_management_disable_wire_cancel (wh); - return NULL; - } - return wh; -} - - -void -TALER_EXCHANGE_management_disable_wire_cancel ( - struct TALER_EXCHANGE_ManagementWireDisableHandle *wh) -{ - if (NULL != wh->job) - { - GNUNET_CURL_job_cancel (wh->job); - wh->job = NULL; - } - TALER_curl_easy_post_finished (&wh->post_ctx); - GNUNET_free (wh->url); - GNUNET_free (wh); -} diff --git a/src/lib/exchange_api_management_wire_enable.c b/src/lib/exchange_api_management_wire_enable.c @@ -1,254 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2015-2023 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_management_wire_enable.c - * @brief functions to enable an exchange wire method / bank account - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include "taler/taler_json_lib.h" -#include <gnunet/gnunet_curl_lib.h> -#include <microhttpd.h> -#include "taler/taler_exchange_service.h" -#include "exchange_api_curl_defaults.h" -#include "taler/taler_signatures.h" -#include "taler/taler_curl_lib.h" -#include "taler/taler_json_lib.h" - - -struct TALER_EXCHANGE_ManagementWireEnableHandle -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Minor context that holds body and headers. - */ - struct TALER_CURL_PostContext post_ctx; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_ManagementWireEnableCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Reference to the execution context. - */ - struct GNUNET_CURL_Context *ctx; -}; - - -/** - * Function called when we're done processing the - * HTTP /management/wire request. - * - * @param cls the `struct TALER_EXCHANGE_ManagementAuditorEnableHandle *` - * @param response_code HTTP response code, 0 on error - * @param response response body, NULL if not in JSON - */ -static void -handle_auditor_enable_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_ManagementWireEnableHandle *wh = cls; - const json_t *json = response; - struct TALER_EXCHANGE_ManagementWireEnableResponse wer = { - .hr.http_status = (unsigned int) response_code, - .hr.reply = json - }; - - wh->job = NULL; - switch (response_code) - { - case 0: - /* no reply */ - wer.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - wer.hr.hint = "server offline?"; - break; - case MHD_HTTP_NO_CONTENT: - break; - case MHD_HTTP_FORBIDDEN: - wer.hr.ec = TALER_JSON_get_error_code (json); - wer.hr.hint = TALER_JSON_get_error_hint (json); - break; - case MHD_HTTP_NOT_FOUND: - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Server did not find handler at `%s'. Did you configure the correct exchange base URL?\n", - wh->url); - if (NULL != json) - { - wer.hr.ec = TALER_JSON_get_error_code (json); - wer.hr.hint = TALER_JSON_get_error_hint (json); - } - else - { - wer.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - wer.hr.hint = TALER_ErrorCode_get_hint (wer.hr.ec); - } - break; - case MHD_HTTP_CONFLICT: - wer.hr.ec = TALER_JSON_get_error_code (json); - wer.hr.hint = TALER_JSON_get_error_hint (json); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - wer.hr.ec = TALER_JSON_get_error_code (json); - wer.hr.hint = TALER_JSON_get_error_hint (json); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange management enable wire\n", - (unsigned int) response_code, - (int) wer.hr.ec); - break; - } - if (NULL != wh->cb) - { - wh->cb (wh->cb_cls, - &wer); - wh->cb = NULL; - } - TALER_EXCHANGE_management_enable_wire_cancel (wh); -} - - -struct TALER_EXCHANGE_ManagementWireEnableHandle * -TALER_EXCHANGE_management_enable_wire ( - struct GNUNET_CURL_Context *ctx, - const char *url, - const struct TALER_FullPayto payto_uri, - const char *conversion_url, - const json_t *debit_restrictions, - const json_t *credit_restrictions, - struct GNUNET_TIME_Timestamp validity_start, - const struct TALER_MasterSignatureP *master_sig1, - const struct TALER_MasterSignatureP *master_sig2, - const char *bank_label, - int64_t priority, - TALER_EXCHANGE_ManagementWireEnableCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_ManagementWireEnableHandle *wh; - CURL *eh; - json_t *body; - - { - char *msg = TALER_payto_validate (payto_uri); - - if (NULL != msg) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "payto URI is malformed: %s\n", - msg); - GNUNET_free (msg); - return NULL; - } - } - wh = GNUNET_new (struct TALER_EXCHANGE_ManagementWireEnableHandle); - wh->cb = cb; - wh->cb_cls = cb_cls; - wh->ctx = ctx; - wh->url = TALER_url_join (url, - "management/wire", - NULL); - if (NULL == wh->url) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Could not construct request URL.\n"); - GNUNET_free (wh); - return NULL; - } - body = GNUNET_JSON_PACK ( - TALER_JSON_pack_full_payto ("payto_uri", - payto_uri), - GNUNET_JSON_pack_array_incref ("debit_restrictions", - (json_t *) debit_restrictions), - GNUNET_JSON_pack_array_incref ("credit_restrictions", - (json_t *) credit_restrictions), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("conversion_url", - conversion_url)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("bank_label", - bank_label)), - GNUNET_JSON_pack_int64 ("priority", - priority), - GNUNET_JSON_pack_data_auto ("master_sig_add", - master_sig1), - GNUNET_JSON_pack_data_auto ("master_sig_wire", - master_sig2), - GNUNET_JSON_pack_timestamp ("validity_start", - validity_start)); - eh = TALER_EXCHANGE_curl_easy_get_ (wh->url); - if ( (NULL == eh) || - (GNUNET_OK != - TALER_curl_easy_post (&wh->post_ctx, - eh, - body)) ) - { - GNUNET_break (0); - if (NULL != eh) - curl_easy_cleanup (eh); - json_decref (body); - GNUNET_free (wh->url); - GNUNET_free (wh); - return NULL; - } - json_decref (body); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Requesting URL '%s'\n", - wh->url); - wh->job = GNUNET_CURL_job_add2 (ctx, - eh, - wh->post_ctx.headers, - &handle_auditor_enable_finished, - wh); - if (NULL == wh->job) - { - TALER_EXCHANGE_management_enable_wire_cancel (wh); - return NULL; - } - return wh; -} - - -void -TALER_EXCHANGE_management_enable_wire_cancel ( - struct TALER_EXCHANGE_ManagementWireEnableHandle *wh) -{ - if (NULL != wh->job) - { - GNUNET_CURL_job_cancel (wh->job); - wh->job = NULL; - } - TALER_curl_easy_post_finished (&wh->post_ctx); - GNUNET_free (wh->url); - GNUNET_free (wh); -} diff --git a/src/lib/exchange_api_melt.c b/src/lib/exchange_api_melt.c @@ -1,645 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2025 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_melt.c - * @brief Implementation of the /melt request - * @author Özgür Kesim - */ -#include "taler/platform.h" -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_json_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_json_lib.h" -#include "taler/taler_exchange_service.h" -#include "exchange_api_common.h" -#include "exchange_api_handle.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" -#include "exchange_api_refresh_common.h" - - -/** - * @brief A /melt Handle - */ -struct TALER_EXCHANGE_MeltHandle -{ - - /** - * The keys of the this request handle will use - */ - struct TALER_EXCHANGE_Keys *keys; - - /** - * The url for this request. - */ - char *url; - - /** - * The exchange base url. - */ - char *exchange_url; - - /** - * Curl context. - */ - struct GNUNET_CURL_Context *cctx; - - /** - * Context for #TEH_curl_easy_post(). Keeps the data that must - * persist for Curl to make the upload. - */ - struct TALER_CURL_PostContext ctx; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with refresh melt failure results. - */ - TALER_EXCHANGE_MeltCallback melt_cb; - - /** - * Closure for @e result_cb and @e melt_failure_cb. - */ - void *melt_cb_cls; - - /** - * Actual information about the melt operation. - */ - struct MeltData md; - - /** - * The seed for the melt operation. - */ - struct TALER_PublicRefreshMasterSeedP rms; - - /** - * Details about the characteristics of the requested melt operation. - */ - const struct TALER_EXCHANGE_MeltInput *rd; - - /** - * True, if no blinding_seed is needed (no CS denominations involved) - */ - bool no_blinding_seed; - - /** - * If @e no_blinding_seed is false, the blinding seed for the intermediate - * call to /blinding-prepare, in order to retrieve the R-values from the - * exchange for the blind Clause-Schnorr signature. - */ - struct TALER_BlindingMasterSeedP blinding_seed; - - /** - * Array of `num_fresh_denom_pubs` per-coin values - * returned from melt operation. - */ - struct TALER_ExchangeBlindingValues *melt_blinding_values; - - /** - * Handle for the preflight request, or NULL. - */ - struct TALER_EXCHANGE_BlindingPrepareHandle *bpr; - - /** - * Public key of the coin being melted. - */ - struct TALER_CoinSpendPublicKeyP coin_pub; - - /** - * Signature affirming the melt. - */ - struct TALER_CoinSpendSignatureP coin_sig; - - /** - * @brief Public information about the coin's denomination key - */ - const struct TALER_EXCHANGE_DenomPublicKey *dki; - - /** - * Gamma value chosen by the exchange during melt. - */ - uint32_t noreveal_index; - -}; - - -/** - * Verify that the signature on the "200 OK" response - * from the exchange is valid. - * - * @param[in,out] mh melt handle - * @param json json reply with the signature - * @param[out] exchange_pub public key of the exchange used for the signature - * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not - */ -static enum GNUNET_GenericReturnValue -verify_melt_signature_ok (struct TALER_EXCHANGE_MeltHandle *mh, - const json_t *json, - struct TALER_ExchangePublicKeyP *exchange_pub) -{ - struct TALER_ExchangeSignatureP exchange_sig; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("exchange_sig", - &exchange_sig), - GNUNET_JSON_spec_fixed_auto ("exchange_pub", - exchange_pub), - GNUNET_JSON_spec_uint32 ("noreveal_index", - &mh->noreveal_index), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - /* check that exchange signing key is permitted */ - if (GNUNET_OK != - TALER_EXCHANGE_test_signing_key (mh->keys, - exchange_pub)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - /* check that noreveal index is in permitted range */ - if (TALER_CNC_KAPPA <= mh->noreveal_index) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - if (GNUNET_OK != - TALER_exchange_online_melt_confirmation_verify ( - &mh->md.rc, - mh->noreveal_index, - exchange_pub, - &exchange_sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /melt request. - * - * @param cls the `struct TALER_EXCHANGE_MeltHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_melt_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_MeltHandle *mh = cls; - const json_t *j = response; - struct TALER_EXCHANGE_MeltResponse mr = { - .hr.reply = j, - .hr.http_status = (unsigned int) response_code - }; - - mh->job = NULL; - switch (response_code) - { - case 0: - mr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - if (GNUNET_OK != - verify_melt_signature_ok (mh, - j, - &mr.details.ok.sign_key)) - { - GNUNET_break_op (0); - mr.hr.http_status = 0; - mr.hr.ec = TALER_EC_EXCHANGE_MELT_INVALID_SIGNATURE_BY_EXCHANGE; - break; - } - mr.details.ok.noreveal_index = mh->noreveal_index; - mr.details.ok.num_melt_blinding_values = mh->rd->num_fresh_denom_pubs; - mr.details.ok.melt_blinding_values = mh->melt_blinding_values; - mr.details.ok.blinding_seed = mh->no_blinding_seed - ? NULL - : &mh->blinding_seed; - mh->melt_cb (mh->melt_cb_cls, - &mr); - mh->melt_cb = NULL; - break; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - mr.hr.ec = TALER_JSON_get_error_code (j); - mr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_CONFLICT: - mr.hr.ec = TALER_JSON_get_error_code (j); - mr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_FORBIDDEN: - /* Nothing really to verify, exchange says one of the signatures is - invalid; assuming we checked them, this should never happen, we - should pass the JSON reply to the application */ - mr.hr.ec = TALER_JSON_get_error_code (j); - mr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - mr.hr.ec = TALER_JSON_get_error_code (j); - mr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - mr.hr.ec = TALER_JSON_get_error_code (j); - mr.hr.hint = TALER_JSON_get_error_hint (j); - break; - default: - /* unexpected response code */ - mr.hr.ec = TALER_JSON_get_error_code (j); - mr.hr.hint = TALER_JSON_get_error_hint (j); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange melt\n", - (unsigned int) response_code, - mr.hr.ec); - GNUNET_break_op (0); - break; - } - if (NULL != mh->melt_cb) - mh->melt_cb (mh->melt_cb_cls, - &mr); - TALER_EXCHANGE_melt_cancel (mh); -} - - -/** - * Start the actual melt operation, now that we have - * the exchange's input values. - * - * @param[in,out] mh melt operation to run - * @return #GNUNET_OK if we could start the operation - */ -static enum GNUNET_GenericReturnValue -start_melt (struct TALER_EXCHANGE_MeltHandle *mh) -{ - json_t *j_request_body; - json_t *j_transfer_pubs; - json_t *j_coin_evs; - CURL *eh; - struct TALER_DenominationHashP h_denom_pub; - - if (GNUNET_OK != - TALER_EXCHANGE_get_melt_data (&mh->rms, - mh->rd, - mh->no_blinding_seed - ? NULL - : &mh->blinding_seed, - mh->melt_blinding_values, - &mh->md)) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - TALER_denom_pub_hash ( - &mh->md.melted_coin.pub_key, - &h_denom_pub); - TALER_wallet_melt_sign ( - &mh->md.melted_coin.melt_amount_with_fee, - &mh->md.melted_coin.fee_melt, - &mh->md.rc, - &h_denom_pub, - mh->md.melted_coin.h_age_commitment, - &mh->md.melted_coin.coin_priv, - &mh->coin_sig); - GNUNET_CRYPTO_eddsa_key_get_public ( - &mh->md.melted_coin.coin_priv.eddsa_priv, - &mh->coin_pub.eddsa_pub); - mh->dki = TALER_EXCHANGE_get_denomination_key ( - mh->keys, - &mh->md.melted_coin.pub_key); - j_request_body = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_data_auto ("old_coin_pub", - &mh->coin_pub), - GNUNET_JSON_pack_data_auto ("old_denom_pub_h", - &h_denom_pub), - TALER_JSON_pack_denom_sig ("old_denom_sig", - &mh->md.melted_coin.sig), - GNUNET_JSON_pack_data_auto ("confirm_sig", - &mh->coin_sig), - TALER_JSON_pack_amount ("value_with_fee", - &mh->md.melted_coin.melt_amount_with_fee), - GNUNET_JSON_pack_allow_null ( - (NULL != mh->md.melted_coin.h_age_commitment) - ? GNUNET_JSON_pack_data_auto ("old_age_commitment_h", - mh->md.melted_coin.h_age_commitment) - : GNUNET_JSON_pack_string ("old_age_commitment_h", - NULL)), - GNUNET_JSON_pack_data_auto ("refresh_seed", - &mh->md.refresh_seed), - GNUNET_JSON_pack_allow_null ( - (mh->md.no_blinding_seed) - ? GNUNET_JSON_pack_string ("blinding_seed", - NULL) - : GNUNET_JSON_pack_data_auto ("blinding_seed", - &mh->md.blinding_seed)), - TALER_JSON_pack_array_of_data_auto ("denoms_h", - mh->md.num_fresh_coins, - mh->md.denoms_h) - ); - GNUNET_assert (NULL != j_request_body); - GNUNET_assert (NULL != - (j_transfer_pubs = json_array ())); - GNUNET_assert (NULL != - (j_coin_evs = json_array ())); - /** - * Fill the kappa array of coin envelopes and - * the array of transfer pubs. - */ - for (uint8_t k=0; k<TALER_CNC_KAPPA; k++) - { - json_t *j_envs; - json_t *j_tbs = GNUNET_JSON_PACK ( - TALER_JSON_pack_array_of_data_auto (NULL, - mh->md.num_fresh_coins, - mh->md.kappa_transfer_pubs[k]) - ); - - GNUNET_assert (NULL != (j_envs = json_array ())); - GNUNET_assert (NULL !=j_tbs); - - for (size_t i = 0; i < mh->md.num_fresh_coins; i++) - { - json_t *j_coin = GNUNET_JSON_PACK ( - TALER_JSON_pack_blinded_planchet (NULL, - &mh->md.kappa_blinded_planchets[k][i])); - GNUNET_assert (NULL != j_coin); - GNUNET_assert (0 == - json_array_append_new (j_envs, j_coin)); - } - GNUNET_assert (0 == - json_array_append_new (j_coin_evs, j_envs)); - GNUNET_assert (0 == - json_array_append_new (j_transfer_pubs, j_tbs)); - } - GNUNET_assert (0 == - json_object_set_new (j_request_body, - "coin_evs", - j_coin_evs)); - GNUNET_assert (0 == - json_object_set_new (j_request_body, - "transfer_pubs", - j_transfer_pubs)); - /* and now we can at last begin the actual request handling */ - mh->url = TALER_url_join (mh->exchange_url, - "melt", - NULL); - if (NULL == mh->url) - { - json_decref (j_request_body); - return GNUNET_SYSERR; - } - eh = TALER_EXCHANGE_curl_easy_get_ (mh->url); - if ( (NULL == eh) || - (GNUNET_OK != - TALER_curl_easy_post (&mh->ctx, - eh, - j_request_body)) ) - { - GNUNET_break (0); - if (NULL != eh) - curl_easy_cleanup (eh); - json_decref (j_request_body); - return GNUNET_SYSERR; - } - json_decref (j_request_body); - mh->job = GNUNET_CURL_job_add2 (mh->cctx, - eh, - mh->ctx.headers, - &handle_melt_finished, - mh); - return GNUNET_OK; -} - - -/** - * The melt request @a mh failed, return an error to - * the application and cancel the operation. - * - * @param[in] mh melt request that failed - * @param ec error code to fail with - */ -static void -fail_mh (struct TALER_EXCHANGE_MeltHandle *mh, - enum TALER_ErrorCode ec) -{ - struct TALER_EXCHANGE_MeltResponse mr = { - .hr.ec = ec - }; - - mh->melt_cb (mh->melt_cb_cls, - &mr); - TALER_EXCHANGE_melt_cancel (mh); -} - - -/** - * Callbacks of this type are used to serve the result of submitting a - * /blinding-prepare request to a exchange. - * - * @param cls closure with our `struct TALER_EXCHANGE_MeltHandle *` - * @param bpr response details - */ -static void -blinding_prepare_cb (void *cls, - const struct TALER_EXCHANGE_BlindingPrepareResponse *bpr) -{ - struct TALER_EXCHANGE_MeltHandle *mh = cls; - unsigned int nks_off = 0; - - mh->bpr = NULL; - if (MHD_HTTP_OK != bpr->hr.http_status) - { - struct TALER_EXCHANGE_MeltResponse mr = { - .hr = bpr->hr - }; - - mr.hr.hint = "/blinding-prepare failed"; - mh->melt_cb (mh->melt_cb_cls, - &mr); - TALER_EXCHANGE_melt_cancel (mh); - return; - } - for (unsigned int i = 0; i<mh->rd->num_fresh_denom_pubs; i++) - { - const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk = - &mh->rd->fresh_denom_pubs[i]; - struct TALER_ExchangeBlindingValues *wv = &mh->melt_blinding_values[i]; - - switch (fresh_pk->key.bsign_pub_key->cipher) - { - case GNUNET_CRYPTO_BSA_INVALID: - GNUNET_break (0); - fail_mh (mh, - TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR); - return; - case GNUNET_CRYPTO_BSA_RSA: - break; - case GNUNET_CRYPTO_BSA_CS: - TALER_denom_ewv_copy (wv, - &bpr->details.ok.blinding_values[nks_off]); - nks_off++; - break; - } - } - if (GNUNET_OK != - start_melt (mh)) - { - GNUNET_break (0); - fail_mh (mh, - TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR); - return; - } -} - - -struct TALER_EXCHANGE_MeltHandle * -TALER_EXCHANGE_melt ( - struct GNUNET_CURL_Context *ctx, - const char *url, - struct TALER_EXCHANGE_Keys *keys, - const struct TALER_PublicRefreshMasterSeedP *rms, - const struct TALER_EXCHANGE_MeltInput *rd, - TALER_EXCHANGE_MeltCallback melt_cb, - void *melt_cb_cls) -{ - struct TALER_EXCHANGE_NonceKey nks[GNUNET_NZL (rd->num_fresh_denom_pubs)]; - unsigned int nks_off = 0; - struct TALER_EXCHANGE_MeltHandle *mh; - - if (0 == rd->num_fresh_denom_pubs) - { - GNUNET_break (0); - return NULL; - } - mh = GNUNET_new (struct TALER_EXCHANGE_MeltHandle); - mh->noreveal_index = TALER_CNC_KAPPA; /* invalid value */ - mh->cctx = ctx; - mh->exchange_url = GNUNET_strdup (url); - mh->rd = rd; - mh->rms = *rms; - mh->melt_cb = melt_cb; - mh->melt_cb_cls = melt_cb_cls; - mh->no_blinding_seed = true; - mh->melt_blinding_values = - GNUNET_new_array (rd->num_fresh_denom_pubs, - struct TALER_ExchangeBlindingValues); - for (unsigned int i = 0; i<rd->num_fresh_denom_pubs; i++) - { - const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk = - &rd->fresh_denom_pubs[i]; - - switch (fresh_pk->key.bsign_pub_key->cipher) - { - case GNUNET_CRYPTO_BSA_INVALID: - GNUNET_break (0); - GNUNET_free (mh->melt_blinding_values); - GNUNET_free (mh); - return NULL; - case GNUNET_CRYPTO_BSA_RSA: - TALER_denom_ewv_copy (&mh->melt_blinding_values[i], - TALER_denom_ewv_rsa_singleton ()); - break; - case GNUNET_CRYPTO_BSA_CS: - nks[nks_off].pk = fresh_pk; - nks[nks_off].cnc_num = i; - nks_off++; - break; - } - } - mh->keys = TALER_EXCHANGE_keys_incref (keys); - if (0 != nks_off) - { - mh->no_blinding_seed = false; - TALER_cs_refresh_seed_to_blinding_seed ( - rms, - &mh->md.melted_coin.coin_priv, - &mh->blinding_seed); - mh->bpr = TALER_EXCHANGE_blinding_prepare_for_melt (ctx, - url, - &mh->blinding_seed, - nks_off, - nks, - &blinding_prepare_cb, - mh); - if (NULL == mh->bpr) - { - GNUNET_break (0); - TALER_EXCHANGE_melt_cancel (mh); - return NULL; - } - return mh; - } - if (GNUNET_OK != - start_melt (mh)) - { - GNUNET_break (0); - TALER_EXCHANGE_melt_cancel (mh); - return NULL; - } - return mh; -} - - -void -TALER_EXCHANGE_melt_cancel (struct TALER_EXCHANGE_MeltHandle *mh) -{ - for (unsigned int i = 0; i<mh->rd->num_fresh_denom_pubs; i++) - TALER_denom_ewv_free (&mh->melt_blinding_values[i]); - if (NULL != mh->job) - { - GNUNET_CURL_job_cancel (mh->job); - mh->job = NULL; - } - if (NULL != mh->bpr) - { - TALER_EXCHANGE_blinding_prepare_cancel (mh->bpr); - mh->bpr = NULL; - } - TALER_EXCHANGE_free_melt_data (&mh->md); /* does not free 'md' itself */ - GNUNET_free (mh->melt_blinding_values); - GNUNET_free (mh->url); - GNUNET_free (mh->exchange_url); - TALER_curl_easy_post_finished (&mh->ctx); - TALER_EXCHANGE_keys_decref (mh->keys); - GNUNET_free (mh); -} - - -/* end of exchange_api_melt.c */ diff --git a/src/lib/exchange_api_post-aml-OFFICER_PUB-decision.c b/src/lib/exchange_api_post-aml-OFFICER_PUB-decision.c @@ -0,0 +1,350 @@ +/* + This file is part of TALER + Copyright (C) 2023, 2024 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_post-aml-OFFICER_PUB-decision.c + * @brief functions to add an AML decision by an AML officer + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include "taler/taler_json_lib.h" +#include <microhttpd.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_exchange_service.h" +#include "exchange_api_curl_defaults.h" +#include "taler/taler_signatures.h" +#include "taler/taler_curl_lib.h" +#include "taler/taler_json_lib.h" + + +struct TALER_EXCHANGE_AddAmlDecision +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Minor context that holds body and headers. + */ + struct TALER_CURL_PostContext post_ctx; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_AddAmlDecisionCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; +}; + + +/** + * Function called when we're done processing the + * HTTP POST /aml/$OFFICER_PUB/decision request. + * + * @param cls the `struct TALER_EXCHANGE_AddAmlDecision *` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_add_aml_decision_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_AddAmlDecision *wh = cls; + const json_t *json = response; + struct TALER_EXCHANGE_AddAmlDecisionResponse adr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json + }; + + wh->job = NULL; + switch (response_code) + { + case 0: + /* no reply */ + adr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + adr.hr.hint = "server offline?"; + break; + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_FORBIDDEN: + adr.hr.ec = TALER_JSON_get_error_code (json); + adr.hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_CONFLICT: + adr.hr.ec = TALER_JSON_get_error_code (json); + adr.hr.hint = TALER_JSON_get_error_hint (json); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + adr.hr.ec = TALER_JSON_get_error_code (json); + adr.hr.hint = TALER_JSON_get_error_hint (json); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange AML decision\n", + (unsigned int) response_code, + (int) adr.hr.ec); + break; + } + if (NULL != wh->cb) + { + wh->cb (wh->cb_cls, + &adr); + wh->cb = NULL; + } + TALER_EXCHANGE_post_aml_decision_cancel (wh); +} + + +struct TALER_EXCHANGE_AddAmlDecision * +TALER_EXCHANGE_post_aml_decision ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const struct TALER_NormalizedPaytoHashP *h_payto, + const struct TALER_FullPayto payto_uri, + struct GNUNET_TIME_Timestamp decision_time, + const char *successor_measure, + const char *new_measures, + struct GNUNET_TIME_Timestamp expiration_time, + unsigned int num_rules, + const struct TALER_EXCHANGE_AccountRule *rules, + unsigned int num_measures, + const struct TALER_EXCHANGE_MeasureInformation *measures, + const json_t *properties, + bool keep_investigating, + const char *justification, + const struct TALER_AmlOfficerPrivateKeyP *officer_priv, + unsigned int num_events, + const char **events, + TALER_EXCHANGE_AddAmlDecisionCallback cb, + void *cb_cls) +{ + struct TALER_AmlOfficerPublicKeyP officer_pub; + struct TALER_AmlOfficerSignatureP officer_sig; + struct TALER_EXCHANGE_AddAmlDecision *wh; + CURL *eh; + json_t *body; + json_t *new_rules; + json_t *jrules; + json_t *jmeasures; + json_t *jevents = NULL; + + if (0 != num_events) + { + jevents = json_array (); + GNUNET_assert (NULL != jevents); + for (unsigned int i = 0; i<num_events; i++) + GNUNET_assert (0 == + json_array_append_new (jevents, + json_string (events[i]))); + } + jrules = json_array (); + GNUNET_assert (NULL != jrules); + for (unsigned int i = 0; i<num_rules; i++) + { + const struct TALER_EXCHANGE_AccountRule *al = &rules[i]; + json_t *rule; + json_t *ameasures; + + ameasures = json_array (); + GNUNET_assert (NULL != ameasures); + for (unsigned int j = 0; j<al->num_measures; j++) + GNUNET_assert (0 == + json_array_append_new (ameasures, + json_string (al->measures[j]))); + rule = GNUNET_JSON_PACK ( + TALER_JSON_pack_kycte ("operation_type", + al->operation_type), + TALER_JSON_pack_amount ("threshold", + &al->threshold), + GNUNET_JSON_pack_time_rel ("timeframe", + al->timeframe), + GNUNET_JSON_pack_array_steal ("measures", + ameasures), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_array_steal ("events", + jevents)), + GNUNET_JSON_pack_bool ("exposed", + al->exposed), + GNUNET_JSON_pack_bool ("is_and_combinator", + al->is_and_combinator), + GNUNET_JSON_pack_uint64 ("display_priority", + al->display_priority) + ); + GNUNET_break (0 == + json_array_append_new (jrules, + rule)); + } + + jmeasures = json_object (); + GNUNET_assert (NULL != jmeasures); + for (unsigned int i = 0; i<num_measures; i++) + { + const struct TALER_EXCHANGE_MeasureInformation *mi = &measures[i]; + json_t *measure; + + measure = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("check_name", + mi->check_name), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("prog_name", + mi->prog_name)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_incref ("context", + (json_t *) mi->context)) + ); + GNUNET_break (0 == + json_object_set_new (jmeasures, + mi->measure_name, + measure)); + } + + new_rules = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_timestamp ("expiration_time", + expiration_time), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("successor_measure", + successor_measure)), + GNUNET_JSON_pack_array_steal ("rules", + jrules), + GNUNET_JSON_pack_object_steal ("custom_measures", + jmeasures) + ); + + GNUNET_CRYPTO_eddsa_key_get_public ( + &officer_priv->eddsa_priv, + &officer_pub.eddsa_pub); + TALER_officer_aml_decision_sign (justification, + decision_time, + h_payto, + new_rules, + properties, + new_measures, + keep_investigating, + officer_priv, + &officer_sig); + wh = GNUNET_new (struct TALER_EXCHANGE_AddAmlDecision); + wh->cb = cb; + wh->cb_cls = cb_cls; + wh->ctx = ctx; + { + char *path; + char opus[sizeof (officer_pub) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string ( + &officer_pub, + sizeof (officer_pub), + opus, + sizeof (opus)); + *end = '\0'; + GNUNET_asprintf (&path, + "aml/%s/decision", + opus); + wh->url = TALER_url_join (url, + path, + NULL); + GNUNET_free (path); + } + if (NULL == wh->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (wh); + json_decref (new_rules); + return NULL; + } + body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("justification", + justification), + GNUNET_JSON_pack_data_auto ("h_payto", + h_payto), + GNUNET_JSON_pack_allow_null ( + TALER_JSON_pack_full_payto ("payto_uri", + payto_uri)), + GNUNET_JSON_pack_object_steal ("new_rules", + new_rules), + GNUNET_JSON_pack_object_incref ("properties", + (json_t *) properties), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("new_measures", + new_measures)), + GNUNET_JSON_pack_bool ("keep_investigating", + keep_investigating), + GNUNET_JSON_pack_data_auto ("officer_sig", + &officer_sig), + GNUNET_JSON_pack_timestamp ("decision_time", + decision_time)); + eh = TALER_EXCHANGE_curl_easy_get_ (wh->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&wh->post_ctx, + eh, + body)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (body); + GNUNET_free (wh->url); + return NULL; + } + json_decref (body); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", + wh->url); + wh->job = GNUNET_CURL_job_add2 (ctx, + eh, + wh->post_ctx.headers, + &handle_add_aml_decision_finished, + wh); + if (NULL == wh->job) + { + TALER_EXCHANGE_post_aml_decision_cancel (wh); + return NULL; + } + return wh; +} + + +void +TALER_EXCHANGE_post_aml_decision_cancel ( + struct TALER_EXCHANGE_AddAmlDecision *wh) +{ + if (NULL != wh->job) + { + GNUNET_CURL_job_cancel (wh->job); + wh->job = NULL; + } + TALER_curl_easy_post_finished (&wh->post_ctx); + GNUNET_free (wh->url); + GNUNET_free (wh); +} diff --git a/src/lib/exchange_api_post-auditors-AUDITOR_PUB-H_DENOM_PUB.c b/src/lib/exchange_api_post-auditors-AUDITOR_PUB-H_DENOM_PUB.c @@ -0,0 +1,239 @@ +/* + This file is part of TALER + Copyright (C) 2015-2021 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_post-auditors-AUDITOR_PUB-H_DENOM_PUB.c + * @brief functions for the auditor to add its signature for denomination at the exchange + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include "taler/taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include <microhttpd.h> +#include "taler/taler_exchange_service.h" +#include "auditor_api_curl_defaults.h" +#include "taler/taler_signatures.h" +#include "taler/taler_curl_lib.h" +#include "taler/taler_json_lib.h" + + +struct TALER_EXCHANGE_AuditorAddDenominationHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Minor context that holds body and headers. + */ + struct TALER_CURL_PostContext post_ctx; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_AuditorAddDenominationCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; +}; + + +/** + * Function called when we're done processing the + * HTTP POST /auditor/$AUDITOR_PUB/$H_DENOM_PUB request. + * + * @param cls the `struct TALER_EXCHANGE_AuditorAddDenominationHandle *` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_auditor_add_denomination_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_AuditorAddDenominationHandle *ah = cls; + const json_t *json = response; + struct TALER_EXCHANGE_AuditorAddDenominationResponse adr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json + }; + + ah->job = NULL; + switch (response_code) + { + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_FORBIDDEN: + adr.hr.ec = TALER_JSON_get_error_code (json); + adr.hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_NOT_FOUND: + adr.hr.ec = TALER_JSON_get_error_code (json); + adr.hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_GONE: + adr.hr.ec = TALER_JSON_get_error_code (json); + adr.hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_PRECONDITION_FAILED: + adr.hr.ec = TALER_JSON_get_error_code (json); + adr.hr.hint = TALER_JSON_get_error_hint (json); + break; + default: + /* unexpected response code */ + if (NULL != json) + { + adr.hr.ec = TALER_JSON_get_error_code (json); + adr.hr.hint = TALER_JSON_get_error_hint (json); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange auditor-add-denomination at URL `%s'\n", + (unsigned int) response_code, + (int) adr.hr.ec, + ah->url); + } + else + { + adr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + adr.hr.hint = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected HTTP response code %u (no JSON returned) at URL `%s'\n", + (unsigned int) response_code, + ah->url); + } + break; + } + if (NULL != ah->cb) + { + ah->cb (ah->cb_cls, + &adr); + ah->cb = NULL; + } + TALER_EXCHANGE_add_auditor_denomination_cancel (ah); +} + + +struct TALER_EXCHANGE_AuditorAddDenominationHandle * +TALER_EXCHANGE_add_auditor_denomination ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const struct TALER_DenominationHashP *h_denom_pub, + const struct TALER_AuditorPublicKeyP *auditor_pub, + const struct TALER_AuditorSignatureP *auditor_sig, + TALER_EXCHANGE_AuditorAddDenominationCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_AuditorAddDenominationHandle *ah; + CURL *eh; + json_t *body; + + ah = GNUNET_new (struct TALER_EXCHANGE_AuditorAddDenominationHandle); + ah->cb = cb; + ah->cb_cls = cb_cls; + ah->ctx = ctx; + { + char apub_str[sizeof (*auditor_pub) * 2]; + char denom_str[sizeof (*h_denom_pub) * 2]; + char arg_str[sizeof (apub_str) + sizeof (denom_str) + 32]; + char *end; + + end = GNUNET_STRINGS_data_to_string (auditor_pub, + sizeof (*auditor_pub), + apub_str, + sizeof (apub_str)); + *end = '\0'; + end = GNUNET_STRINGS_data_to_string (h_denom_pub, + sizeof (*h_denom_pub), + denom_str, + sizeof (denom_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "auditors/%s/%s", + apub_str, + denom_str); + ah->url = TALER_url_join (url, + arg_str, + NULL); + } + if (NULL == ah->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (ah); + return NULL; + } + body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("auditor_sig", + auditor_sig)); + eh = TALER_AUDITOR_curl_easy_get_ (ah->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&ah->post_ctx, + eh, + body)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (body); + GNUNET_free (ah->url); + return NULL; + } + json_decref (body); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", + ah->url); + ah->job = GNUNET_CURL_job_add2 (ctx, + eh, + ah->post_ctx.headers, + &handle_auditor_add_denomination_finished, + ah); + if (NULL == ah->job) + { + TALER_EXCHANGE_add_auditor_denomination_cancel (ah); + return NULL; + } + return ah; +} + + +void +TALER_EXCHANGE_add_auditor_denomination_cancel ( + struct TALER_EXCHANGE_AuditorAddDenominationHandle *ah) +{ + if (NULL != ah->job) + { + GNUNET_CURL_job_cancel (ah->job); + ah->job = NULL; + } + TALER_curl_easy_post_finished (&ah->post_ctx); + GNUNET_free (ah->url); + GNUNET_free (ah); +} diff --git a/src/lib/exchange_api_post-batch-deposit.c b/src/lib/exchange_api_post-batch-deposit.c @@ -0,0 +1,820 @@ +/* + This file is part of TALER + Copyright (C) 2014-2024 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> + */ +/** + * @file lib/exchange_api_post-batch-deposit.c + * @brief Implementation of the /batch-deposit request of the exchange's HTTP API + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_json_lib.h" +#include "taler/taler_auditor_service.h" +#include "taler/taler_exchange_service.h" +#include "exchange_api_common.h" +#include "exchange_api_handle.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * 1:#AUDITOR_CHANCE is the probability that we report deposits + * to the auditor. + * + * 20==5% of going to auditor. This is possibly still too high, but set + * deliberately this high for testing + */ +#define AUDITOR_CHANCE 20 + + +/** + * Entry in list of ongoing interactions with an auditor. + */ +struct TEAH_AuditorInteractionEntry +{ + /** + * DLL entry. + */ + struct TEAH_AuditorInteractionEntry *next; + + /** + * DLL entry. + */ + struct TEAH_AuditorInteractionEntry *prev; + + /** + * URL of our auditor. For logging. + */ + const char *auditor_url; + + /** + * Interaction state. + */ + struct TALER_AUDITOR_DepositConfirmationHandle *dch; + + /** + * Batch deposit this is for. + */ + struct TALER_EXCHANGE_BatchDepositHandle *dh; +}; + + +/** + * @brief A Deposit Handle + */ +struct TALER_EXCHANGE_BatchDepositHandle +{ + + /** + * The keys of the exchange. + */ + struct TALER_EXCHANGE_Keys *keys; + + /** + * Context for our curl request(s). + */ + struct GNUNET_CURL_Context *ctx; + + /** + * The url for this request. + */ + char *url; + + /** + * Context for #TEH_curl_easy_post(). Keeps the data that must + * persist for Curl to make the upload. + */ + struct TALER_CURL_PostContext post_ctx; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_BatchDepositResultCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Details about the contract. + */ + struct TALER_EXCHANGE_DepositContractDetail dcd; + + /** + * Array with details about the coins. + */ + struct TALER_EXCHANGE_CoinDepositDetail *cdds; + + /** + * Hash of the merchant's wire details. + */ + struct TALER_MerchantWireHashP h_wire; + + /** + * Hash over the extensions, or all zero. + */ + struct TALER_ExtensionPolicyHashP h_policy; + + /** + * Time when this confirmation was generated / when the exchange received + * the deposit request. + */ + struct GNUNET_TIME_Timestamp exchange_timestamp; + + /** + * Exchange signature, set for #auditor_cb. + */ + struct TALER_ExchangeSignatureP exchange_sig; + + /** + * Head of DLL of interactions with this auditor. + */ + struct TEAH_AuditorInteractionEntry *ai_head; + + /** + * Tail of DLL of interactions with this auditor. + */ + struct TEAH_AuditorInteractionEntry *ai_tail; + + /** + * Result to return to the application once @e ai_head is empty. + */ + struct TALER_EXCHANGE_BatchDepositResult dr; + + /** + * Exchange signing public key, set for #auditor_cb. + */ + struct TALER_ExchangePublicKeyP exchange_pub; + + /** + * Total amount deposited without fees as calculated by us. + */ + struct TALER_Amount total_without_fee; + + /** + * Response object to free at the end. + */ + json_t *response; + + /** + * Chance that we will inform the auditor about the deposit + * is 1:n, where the value of this field is "n". + */ + unsigned int auditor_chance; + + /** + * Length of the @e cdds array. + */ + unsigned int num_cdds; + +}; + + +/** + * Finish batch deposit operation by calling the callback. + * + * @param[in] dh handle to finished batch deposit operation + */ +static void +finish_dh (struct TALER_EXCHANGE_BatchDepositHandle *dh) +{ + dh->cb (dh->cb_cls, + &dh->dr); + TALER_EXCHANGE_batch_deposit_cancel (dh); +} + + +/** + * Function called with the result from our call to the + * auditor's /deposit-confirmation handler. + * + * @param cls closure of type `struct TEAH_AuditorInteractionEntry *` + * @param dcr response + */ +static void +acc_confirmation_cb ( + void *cls, + const struct TALER_AUDITOR_DepositConfirmationResponse *dcr) +{ + struct TEAH_AuditorInteractionEntry *aie = cls; + struct TALER_EXCHANGE_BatchDepositHandle *dh = aie->dh; + + if (MHD_HTTP_OK != dcr->hr.http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to submit deposit confirmation to auditor `%s' with HTTP status %d (EC: %d). This is acceptable if it does not happen often.\n", + aie->auditor_url, + dcr->hr.http_status, + dcr->hr.ec); + } + GNUNET_CONTAINER_DLL_remove (dh->ai_head, + dh->ai_tail, + aie); + GNUNET_free (aie); + if (NULL == dh->ai_head) + finish_dh (dh); +} + + +/** + * Function called for each auditor to give us a chance to possibly + * launch a deposit confirmation interaction. + * + * @param cls closure + * @param auditor_url base URL of the auditor + * @param auditor_pub public key of the auditor + */ +static void +auditor_cb (void *cls, + const char *auditor_url, + const struct TALER_AuditorPublicKeyP *auditor_pub) +{ + struct TALER_EXCHANGE_BatchDepositHandle *dh = cls; + const struct TALER_EXCHANGE_SigningPublicKey *spk; + struct TEAH_AuditorInteractionEntry *aie; + const struct TALER_CoinSpendSignatureP *csigs[GNUNET_NZL ( + dh->num_cdds)]; + const struct TALER_CoinSpendPublicKeyP *cpubs[GNUNET_NZL ( + dh->num_cdds)]; + + for (unsigned int i = 0; i<dh->num_cdds; i++) + { + const struct TALER_EXCHANGE_CoinDepositDetail *cdd = &dh->cdds[i]; + + csigs[i] = &cdd->coin_sig; + cpubs[i] = &cdd->coin_pub; + } + + if (0 != + GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, + dh->auditor_chance)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Not providing deposit confirmation to auditor\n"); + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Will provide deposit confirmation to auditor `%s'\n", + TALER_B2S (auditor_pub)); + spk = TALER_EXCHANGE_get_signing_key_info (dh->keys, + &dh->exchange_pub); + if (NULL == spk) + { + GNUNET_break_op (0); + return; + } + aie = GNUNET_new (struct TEAH_AuditorInteractionEntry); + aie->dh = dh; + aie->auditor_url = auditor_url; + aie->dch = TALER_AUDITOR_deposit_confirmation ( + dh->ctx, + auditor_url, + &dh->h_wire, + &dh->h_policy, + &dh->dcd.h_contract_terms, + dh->exchange_timestamp, + dh->dcd.wire_deadline, + dh->dcd.refund_deadline, + &dh->total_without_fee, + dh->num_cdds, + cpubs, + csigs, + &dh->dcd.merchant_pub, + &dh->exchange_pub, + &dh->exchange_sig, + &dh->keys->master_pub, + spk->valid_from, + spk->valid_until, + spk->valid_legal, + &spk->master_sig, + &acc_confirmation_cb, + aie); + GNUNET_CONTAINER_DLL_insert (dh->ai_head, + dh->ai_tail, + aie); +} + + +/** + * Function called when we're done processing the + * HTTP /deposit request. + * + * @param cls the `struct TALER_EXCHANGE_BatchDepositHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_deposit_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_BatchDepositHandle *dh = cls; + const json_t *j = response; + struct TALER_EXCHANGE_BatchDepositResult *dr = &dh->dr; + + dh->job = NULL; + dh->response = json_incref ((json_t*) j); + dr->hr.reply = dh->response; + dr->hr.http_status = (unsigned int) response_code; + switch (response_code) + { + case 0: + dr->hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &dh->exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &dh->exchange_pub), + GNUNET_JSON_spec_mark_optional ( + TALER_JSON_spec_web_url ("transaction_base_url", + &dr->details.ok.transaction_base_url), + NULL), + GNUNET_JSON_spec_timestamp ("exchange_timestamp", + &dh->exchange_timestamp), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + dr->hr.http_status = 0; + dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (dh->keys, + &dh->exchange_pub)) + { + GNUNET_break_op (0); + dr->hr.http_status = 0; + dr->hr.ec = TALER_EC_EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE; + break; + } + { + const struct TALER_CoinSpendSignatureP *csigs[ + GNUNET_NZL (dh->num_cdds)]; + + for (unsigned int i = 0; i<dh->num_cdds; i++) + csigs[i] = &dh->cdds[i].coin_sig; + if (GNUNET_OK != + TALER_exchange_online_deposit_confirmation_verify ( + &dh->dcd.h_contract_terms, + &dh->h_wire, + &dh->h_policy, + dh->exchange_timestamp, + dh->dcd.wire_deadline, + dh->dcd.refund_deadline, + &dh->total_without_fee, + dh->num_cdds, + csigs, + &dh->dcd.merchant_pub, + &dh->exchange_pub, + &dh->exchange_sig)) + { + GNUNET_break_op (0); + dr->hr.http_status = 0; + dr->hr.ec = TALER_EC_EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE; + break; + } + } + TEAH_get_auditors_for_dc (dh->keys, + &auditor_cb, + dh); + } + dr->details.ok.exchange_sig = &dh->exchange_sig; + dr->details.ok.exchange_pub = &dh->exchange_pub; + dr->details.ok.deposit_timestamp = dh->exchange_timestamp; + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + dr->hr.ec = TALER_JSON_get_error_code (j); + dr->hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_FORBIDDEN: + dr->hr.ec = TALER_JSON_get_error_code (j); + dr->hr.hint = TALER_JSON_get_error_hint (j); + /* Nothing really to verify, exchange says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + dr->hr.ec = TALER_JSON_get_error_code (j); + dr->hr.hint = TALER_JSON_get_error_hint (j); + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + break; + case MHD_HTTP_CONFLICT: + { + dr->hr.ec = TALER_JSON_get_error_code (j); + dr->hr.hint = TALER_JSON_get_error_hint (j); + switch (dr->hr.ec) + { + case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS: + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ( + "coin_pub", + &dr->details.conflict.details + .insufficient_funds.coin_pub), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + dr->hr.http_status = 0; + dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + } + break; + case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_AGE_HASH: + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ( + "coin_pub", + &dr->details.conflict.details + .coin_conflicting_age_hash.coin_pub), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + dr->hr.http_status = 0; + dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + } + break; + case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY: + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ( + "coin_pub", + &dr->details.conflict.details + .coin_conflicting_denomination_key.coin_pub), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + dr->hr.http_status = 0; + dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + } + break; + case TALER_EC_EXCHANGE_DEPOSIT_CONFLICTING_CONTRACT: + break; + default: + GNUNET_break_op (0); + break; + } + } + break; + case MHD_HTTP_GONE: + /* could happen if denomination was revoked */ + /* Note: one might want to check /keys for revocation + signature here, alas tricky in case our /keys + is outdated => left to clients */ + dr->hr.ec = TALER_JSON_get_error_code (j); + dr->hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ( + "h_payto", + &dr->details.unavailable_for_legal_reasons.h_payto), + GNUNET_JSON_spec_uint64 ( + "requirement_row", + &dr->details.unavailable_for_legal_reasons.requirement_row), + GNUNET_JSON_spec_bool ( + "bad_kyc_auth", + &dr->details.unavailable_for_legal_reasons.bad_kyc_auth), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + dr->hr.http_status = 0; + dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + } + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + dr->hr.ec = TALER_JSON_get_error_code (j); + dr->hr.hint = TALER_JSON_get_error_hint (j); + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + dr->hr.ec = TALER_JSON_get_error_code (j); + dr->hr.hint = TALER_JSON_get_error_hint (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange deposit\n", + (unsigned int) response_code, + dr->hr.ec); + GNUNET_break_op (0); + break; + } + if (NULL != dh->ai_head) + return; + finish_dh (dh); +} + + +struct TALER_EXCHANGE_BatchDepositHandle * +TALER_EXCHANGE_batch_deposit ( + struct GNUNET_CURL_Context *ctx, + const char *url, + struct TALER_EXCHANGE_Keys *keys, + const struct TALER_EXCHANGE_DepositContractDetail *dcd, + unsigned int num_cdds, + const struct TALER_EXCHANGE_CoinDepositDetail cdds[static num_cdds], + TALER_EXCHANGE_BatchDepositResultCallback cb, + void *cb_cls, + enum TALER_ErrorCode *ec) +{ + struct TALER_EXCHANGE_BatchDepositHandle *dh; + json_t *deposit_obj; + json_t *deposits; + CURL *eh; + const struct GNUNET_HashCode *wallet_data_hashp; + + if (0 == num_cdds) + { + GNUNET_break (0); + return NULL; + } + if (GNUNET_TIME_timestamp_cmp (dcd->refund_deadline, + >, + dcd->wire_deadline)) + { + GNUNET_break_op (0); + *ec = TALER_EC_EXCHANGE_DEPOSIT_REFUND_DEADLINE_AFTER_WIRE_DEADLINE; + return NULL; + } + dh = GNUNET_new (struct TALER_EXCHANGE_BatchDepositHandle); + dh->auditor_chance = AUDITOR_CHANCE; + dh->cb = cb; + dh->cb_cls = cb_cls; + dh->cdds = GNUNET_memdup (cdds, + num_cdds * sizeof (*cdds)); + dh->num_cdds = num_cdds; + dh->dcd = *dcd; + if (NULL != dcd->policy_details) + TALER_deposit_policy_hash (dcd->policy_details, + &dh->h_policy); + TALER_merchant_wire_signature_hash (dcd->merchant_payto_uri, + &dcd->wire_salt, + &dh->h_wire); + deposits = json_array (); + GNUNET_assert (NULL != deposits); + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (cdds[0].amount.currency, + &dh->total_without_fee)); + for (unsigned int i = 0; i<num_cdds; i++) + { + const struct TALER_EXCHANGE_CoinDepositDetail *cdd = &cdds[i]; + const struct TALER_EXCHANGE_DenomPublicKey *dki; + const struct TALER_AgeCommitmentHashP *h_age_commitmentp; + struct TALER_Amount amount_without_fee; + + dki = TALER_EXCHANGE_get_denomination_key_by_hash (keys, + &cdd->h_denom_pub); + if (NULL == dki) + { + *ec = TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN; + GNUNET_break_op (0); + json_decref (deposits); + return NULL; + } + if (0 > + TALER_amount_subtract (&amount_without_fee, + &cdd->amount, + &dki->fees.deposit)) + { + *ec = TALER_EC_EXCHANGE_DEPOSIT_FEE_ABOVE_AMOUNT; + GNUNET_break_op (0); + GNUNET_free (dh->cdds); + GNUNET_free (dh); + json_decref (deposits); + return NULL; + } + GNUNET_assert (0 <= + TALER_amount_add (&dh->total_without_fee, + &dh->total_without_fee, + &amount_without_fee)); + if (GNUNET_OK != + TALER_EXCHANGE_verify_deposit_signature_ (dcd, + &dh->h_policy, + &dh->h_wire, + cdd, + dki)) + { + *ec = TALER_EC_EXCHANGE_DEPOSIT_COIN_SIGNATURE_INVALID; + GNUNET_break_op (0); + GNUNET_free (dh->cdds); + GNUNET_free (dh); + json_decref (deposits); + return NULL; + } + if (! GNUNET_is_zero (&dcd->merchant_sig)) + { + /* FIXME #9185: check merchant_sig!? */ + } + if (GNUNET_is_zero (&cdd->h_age_commitment)) + h_age_commitmentp = NULL; + else + h_age_commitmentp = &cdd->h_age_commitment; + GNUNET_assert ( + 0 == + json_array_append_new ( + deposits, + GNUNET_JSON_PACK ( + TALER_JSON_pack_amount ("contribution", + &cdd->amount), + GNUNET_JSON_pack_data_auto ("denom_pub_hash", + &cdd->h_denom_pub), + TALER_JSON_pack_denom_sig ("ub_sig", + &cdd->denom_sig), + GNUNET_JSON_pack_data_auto ("coin_pub", + &cdd->coin_pub), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_data_auto ("h_age_commitment", + h_age_commitmentp)), + GNUNET_JSON_pack_data_auto ("coin_sig", + &cdd->coin_sig) + ))); + } + dh->url = TALER_url_join (url, + "batch-deposit", + NULL); + if (NULL == dh->url) + { + GNUNET_break (0); + *ec = TALER_EC_GENERIC_ALLOCATION_FAILURE; + GNUNET_free (dh->url); + GNUNET_free (dh->cdds); + GNUNET_free (dh); + json_decref (deposits); + return NULL; + } + + if (GNUNET_is_zero (&dcd->wallet_data_hash)) + wallet_data_hashp = NULL; + else + wallet_data_hashp = &dcd->wallet_data_hash; + + deposit_obj = GNUNET_JSON_PACK ( + TALER_JSON_pack_full_payto ("merchant_payto_uri", + dcd->merchant_payto_uri), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_data_auto ("extra_wire_subject_metadata", + dcd->extra_wire_subject_metadata)), + GNUNET_JSON_pack_data_auto ("wire_salt", + &dcd->wire_salt), + GNUNET_JSON_pack_data_auto ("h_contract_terms", + &dcd->h_contract_terms), + GNUNET_JSON_pack_array_steal ("coins", + deposits), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_data_auto ("wallet_data_hash", + wallet_data_hashp)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_steal ("policy_details", + (json_t *) dcd->policy_details)), + GNUNET_JSON_pack_timestamp ("timestamp", + dcd->wallet_timestamp), + GNUNET_JSON_pack_data_auto ("merchant_pub", + &dcd->merchant_pub), + GNUNET_JSON_pack_data_auto ("merchant_sig", + &dcd->merchant_sig), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_timestamp ("refund_deadline", + dcd->refund_deadline)), + GNUNET_JSON_pack_timestamp ("wire_transfer_deadline", + dcd->wire_deadline)); + GNUNET_assert (NULL != deposit_obj); + eh = TALER_EXCHANGE_curl_easy_get_ (dh->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&dh->post_ctx, + eh, + deposit_obj)) ) + { + *ec = TALER_EC_GENERIC_CURL_ALLOCATION_FAILURE; + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (deposit_obj); + GNUNET_free (dh->cdds); + GNUNET_free (dh->url); + GNUNET_free (dh); + return NULL; + } + json_decref (deposit_obj); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "URL for deposit: `%s'\n", + dh->url); + dh->ctx = ctx; + dh->keys = TALER_EXCHANGE_keys_incref (keys); + dh->job = GNUNET_CURL_job_add2 (ctx, + eh, + dh->post_ctx.headers, + &handle_deposit_finished, + dh); + return dh; +} + + +void +TALER_EXCHANGE_batch_deposit_force_dc ( + struct TALER_EXCHANGE_BatchDepositHandle *deposit) +{ + deposit->auditor_chance = 1; +} + + +void +TALER_EXCHANGE_batch_deposit_cancel ( + struct TALER_EXCHANGE_BatchDepositHandle *deposit) +{ + struct TEAH_AuditorInteractionEntry *aie; + + while (NULL != (aie = deposit->ai_head)) + { + GNUNET_assert (aie->dh == deposit); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Not sending deposit confirmation to auditor `%s' due to cancellation\n", + aie->auditor_url); + TALER_AUDITOR_deposit_confirmation_cancel (aie->dch); + GNUNET_CONTAINER_DLL_remove (deposit->ai_head, + deposit->ai_tail, + aie); + GNUNET_free (aie); + } + if (NULL != deposit->job) + { + GNUNET_CURL_job_cancel (deposit->job); + deposit->job = NULL; + } + TALER_EXCHANGE_keys_decref (deposit->keys); + GNUNET_free (deposit->url); + GNUNET_free (deposit->cdds); + TALER_curl_easy_post_finished (&deposit->post_ctx); + json_decref (deposit->response); + GNUNET_free (deposit); +} + + +/* end of exchange_api_batch_deposit.c */ diff --git a/src/lib/exchange_api_post-blinding-prepare.c b/src/lib/exchange_api_post-blinding-prepare.c @@ -0,0 +1,422 @@ +/* + This file is part of TALER + Copyright (C) 2025 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_post-blinding-prepare.c + * @brief Implementation of /blinding-prepare requests + * @author Özgür Kesim + */ + +#include "taler/platform.h" +#include <gnunet/gnunet_common.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include <sys/wait.h> +#include "taler/taler_curl_lib.h" +#include "taler/taler_error_codes.h" +#include "taler/taler_json_lib.h" +#include "taler/taler_exchange_service.h" +#include "exchange_api_common.h" +#include "exchange_api_handle.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" +#include "taler/taler_util.h" + +/** + * A /blinding-prepare request-handle + */ +struct TALER_EXCHANGE_BlindingPrepareHandle +{ + /** + * number of elements to prepare + */ + size_t num; + + /** + * True, if this operation is for melting (or withdraw otherwise). + */ + bool for_melt; + + /** + * The seed for the batch of nonces. + */ + const struct TALER_BlindingMasterSeedP *seed; + + /** + * The url for this request. + */ + char *url; + + /** + * Context for curl. + */ + struct GNUNET_CURL_Context *curl_ctx; + + /** + * CURL handle for the request job. + */ + struct GNUNET_CURL_Job *job; + + /** + * Post Context + */ + struct TALER_CURL_PostContext post_ctx; + + /** + * Function to call with withdraw response results. + */ + TALER_EXCHANGE_BlindingPrepareCallback callback; + + /** + * Closure for @e callback + */ + void *callback_cls; + +}; + + +/** + * We got a 200 OK response for the /blinding-prepare operation. + * Extract the r_pub values and return them to the caller via the callback + * + * @param handle operation handle + * @param response response details + * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors + */ +static enum GNUNET_GenericReturnValue +blinding_prepare_ok (struct TALER_EXCHANGE_BlindingPrepareHandle *handle, + struct TALER_EXCHANGE_BlindingPrepareResponse *response) +{ + const json_t *j_r_pubs; + const char *cipher; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("cipher", + &cipher), + GNUNET_JSON_spec_array_const ("r_pubs", + &j_r_pubs), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (response->hr.reply, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + if (strcmp ("CS", cipher)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + if (json_array_size (j_r_pubs) + != handle->num) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + { + size_t num = handle->num; + const json_t *j_pair; + size_t idx; + struct TALER_ExchangeBlindingValues blinding_values[GNUNET_NZL (num)]; + + memset (blinding_values, + 0, + sizeof(blinding_values)); + + json_array_foreach (j_r_pubs, idx, j_pair) { + struct GNUNET_CRYPTO_BlindingInputValues *bi = + GNUNET_new (struct GNUNET_CRYPTO_BlindingInputValues); + struct GNUNET_CRYPTO_CSPublicRPairP *csv = &bi->details.cs_values; + struct GNUNET_JSON_Specification tuple[] = { + GNUNET_JSON_spec_fixed (NULL, + &csv->r_pub[0], + sizeof(csv->r_pub[0])), + GNUNET_JSON_spec_fixed (NULL, + &csv->r_pub[1], + sizeof(csv->r_pub[1])), + GNUNET_JSON_spec_end () + }; + struct GNUNET_JSON_Specification jspec[] = { + TALER_JSON_spec_tuple_of (NULL, tuple), + GNUNET_JSON_spec_end () + }; + const char *err_msg; + unsigned int err_line; + + if (GNUNET_OK != + GNUNET_JSON_parse (j_pair, + jspec, + &err_msg, + &err_line)) + { + GNUNET_break_op (0); + GNUNET_free (bi); + for (size_t i=0; i < idx; i++) + TALER_denom_ewv_free (&blinding_values[i]); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Error while parsing response: in line %d: %s", + err_line, + err_msg); + return GNUNET_SYSERR; + } + + bi->cipher = GNUNET_CRYPTO_BSA_CS; + bi->rc = 1; + blinding_values[idx].blinding_inputs = bi; + } + + response->details.ok.blinding_values = blinding_values; + response->details.ok.num_blinding_values = num; + + handle->callback ( + handle->callback_cls, + response); + + for (size_t i = 0; i < num; i++) + TALER_denom_ewv_free (&blinding_values[i]); + } + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the HTTP /blinding-prepare request. + * + * @param cls the `struct TALER_EXCHANGE_BlindingPrepareHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_blinding_prepare_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_BlindingPrepareHandle *handle = cls; + const json_t *j_response = response; + struct TALER_EXCHANGE_BlindingPrepareResponse bpr = { + .hr = { + .reply = j_response, + .http_status = (unsigned int) response_code + }, + }; + + handle->job = NULL; + + switch (response_code) + { + case 0: + bpr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + + case MHD_HTTP_OK: + { + if (GNUNET_OK != + blinding_prepare_ok (handle, + &bpr)) + { + GNUNET_break_op (0); + bpr.hr.http_status = 0; + bpr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + } + TALER_EXCHANGE_blinding_prepare_cancel (handle); + return; + + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + bpr.hr.ec = TALER_JSON_get_error_code (j_response); + bpr.hr.hint = TALER_JSON_get_error_hint (j_response); + break; + + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, the exchange basically just says + that it doesn't know the /csr endpoint or denomination. + Can happen if the exchange doesn't support Clause Schnorr. + We should simply pass the JSON reply to the application. */ + bpr.hr.ec = TALER_JSON_get_error_code (j_response); + bpr.hr.hint = TALER_JSON_get_error_hint (j_response); + break; + + case MHD_HTTP_GONE: + /* could happen if denomination was revoked */ + /* Note: one might want to check /keys for revocation + signature here, alas tricky in case our /keys + is outdated => left to clients */ + bpr.hr.ec = TALER_JSON_get_error_code (j_response); + bpr.hr.hint = TALER_JSON_get_error_hint (j_response); + break; + + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + bpr.hr.ec = TALER_JSON_get_error_code (j_response); + bpr.hr.hint = TALER_JSON_get_error_hint (j_response); + break; + + default: + /* unexpected response code */ + GNUNET_break_op (0); + bpr.hr.ec = TALER_JSON_get_error_code (j_response); + bpr.hr.hint = TALER_JSON_get_error_hint (j_response); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for the blinding-prepare request\n", + (unsigned int) response_code, + (int) bpr.hr.ec); + break; + + } + + handle->callback (handle->callback_cls, + &bpr); + handle->callback = NULL; + TALER_EXCHANGE_blinding_prepare_cancel (handle); +} + + +struct TALER_EXCHANGE_BlindingPrepareHandle * +TALER_EXCHANGE_blinding_prepare ( + struct GNUNET_CURL_Context *curl_ctx, + const char *exchange_url, + const struct TALER_BlindingMasterSeedP *seed, + bool for_melt, + size_t num, + const struct TALER_EXCHANGE_NonceKey nonce_keys[static num], + TALER_EXCHANGE_BlindingPrepareCallback callback, + void *callback_cls) +{ + struct TALER_EXCHANGE_BlindingPrepareHandle *bph; + + if (0 == num) + { + GNUNET_break (0); + return NULL; + } + for (unsigned int i = 0; i<num; i++) + if (GNUNET_CRYPTO_BSA_CS != + nonce_keys[i].pk->key.bsign_pub_key->cipher) + { + GNUNET_break (0); + return NULL; + } + bph = GNUNET_new (struct TALER_EXCHANGE_BlindingPrepareHandle); + bph->num = num; + bph->callback = callback; + bph->for_melt = for_melt; + bph->callback_cls = callback_cls; + bph->url = TALER_url_join (exchange_url, + "blinding-prepare", + NULL); + if (NULL == bph->url) + { + GNUNET_break (0); + GNUNET_free (bph); + return NULL; + } + { + CURL *eh; + json_t *j_nks; + json_t *j_request = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("cipher", + "CS"), + GNUNET_JSON_pack_string ("operation", + for_melt ? "melt" : "withdraw"), + GNUNET_JSON_pack_data_auto ("seed", + seed)); + GNUNET_assert (NULL != j_request); + + j_nks = json_array (); + GNUNET_assert (NULL != j_nks); + + for (size_t i = 0; i<num; i++) + { + const struct TALER_EXCHANGE_NonceKey *nk = &nonce_keys[i]; + json_t *j_entry = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_uint64 ("coin_offset", + nk->cnc_num), + GNUNET_JSON_pack_data_auto ("denom_pub_hash", + &nk->pk->h_key)); + + GNUNET_assert (NULL != j_entry); + GNUNET_assert (0 == + json_array_append_new (j_nks, + j_entry)); + } + GNUNET_assert (0 == + json_object_set_new (j_request, + "nks", + j_nks)); + eh = TALER_EXCHANGE_curl_easy_get_ (bph->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&bph->post_ctx, + eh, + j_request))) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (j_request); + GNUNET_free (bph->url); + GNUNET_free (bph); + return NULL; + } + + json_decref (j_request); + bph->job = GNUNET_CURL_job_add2 (curl_ctx, + eh, + bph->post_ctx.headers, + &handle_blinding_prepare_finished, + bph); + if (NULL == bph->job) + { + GNUNET_break (0); + TALER_EXCHANGE_blinding_prepare_cancel (bph); + return NULL; + } + } + return bph; +} + + +void +TALER_EXCHANGE_blinding_prepare_cancel ( + struct TALER_EXCHANGE_BlindingPrepareHandle *bph) +{ + if (NULL == bph) + return; + if (NULL != bph->job) + { + GNUNET_CURL_job_cancel (bph->job); + bph->job = NULL; + } + GNUNET_free (bph->url); + TALER_curl_easy_post_finished (&bph->post_ctx); + GNUNET_free (bph); +} + + +/* end of lib/exchange_api_blinding_prepare.c */ diff --git a/src/lib/exchange_api_post-coins-COIN_PUB-refund.c b/src/lib/exchange_api_post-coins-COIN_PUB-refund.c @@ -0,0 +1,484 @@ +/* + This file is part of TALER + 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 General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_post-coins-COIN_PUB-refund.c + * @brief Implementation of the /refund request of the exchange's HTTP API + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_json_lib.h" +#include "taler/taler_exchange_service.h" +#include "exchange_api_handle.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A Refund Handle + */ +struct TALER_EXCHANGE_RefundHandle +{ + + /** + * The keys of the exchange this request handle will use + */ + struct TALER_EXCHANGE_Keys *keys; + + /** + * The url for this request. + */ + char *url; + + /** + * Context for #TEH_curl_easy_post(). Keeps the data that must + * persist for Curl to make the upload. + */ + struct TALER_CURL_PostContext ctx; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_RefundCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Hash over the proposal data to identify the contract + * which is being refunded. + */ + struct TALER_PrivateContractHashP h_contract_terms; + + /** + * The coin's public key. This is the value that must have been + * signed (blindly) by the Exchange. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + + /** + * The Merchant's public key. Allows the merchant to later refund + * the transaction or to inquire about the wire transfer identifier. + */ + struct TALER_MerchantPublicKeyP merchant; + + /** + * Merchant-generated transaction ID for the refund. + */ + uint64_t rtransaction_id; + + /** + * Amount to be refunded, including refund fee charged by the + * exchange to the customer. + */ + struct TALER_Amount refund_amount; + +}; + + +/** + * Verify that the signature on the "200 OK" response + * from the exchange is valid. + * + * @param[in,out] rh refund handle (refund fee added) + * @param json json reply with the signature + * @param[out] exchange_pub set to the exchange's public key + * @param[out] exchange_sig set to the exchange's signature + * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not + */ +static enum GNUNET_GenericReturnValue +verify_refund_signature_ok (struct TALER_EXCHANGE_RefundHandle *rh, + const json_t *json, + struct TALER_ExchangePublicKeyP *exchange_pub, + struct TALER_ExchangeSignatureP *exchange_sig) +{ + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + exchange_pub), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (rh->keys, + exchange_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_exchange_online_refund_confirmation_verify ( + &rh->h_contract_terms, + &rh->coin_pub, + &rh->merchant, + rh->rtransaction_id, + &rh->refund_amount, + exchange_pub, + exchange_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Verify that the information on the "412 Dependency Failed" response + * from the exchange is valid and indeed shows that there is a refund + * transaction ID reuse going on. + * + * @param[in,out] rh refund handle (refund fee added) + * @param json json reply with the signature + * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not + */ +static enum GNUNET_GenericReturnValue +verify_failed_dependency_ok (struct TALER_EXCHANGE_RefundHandle *rh, + const json_t *json) +{ + const json_t *h; + json_t *e; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_array_const ("history", + &h), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (1 != json_array_size (h)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + e = json_array_get (h, 0); + { + struct TALER_Amount amount; + const char *type; + struct TALER_MerchantSignatureP sig; + struct TALER_Amount refund_fee; + struct TALER_PrivateContractHashP h_contract_terms; + uint64_t rtransaction_id; + struct TALER_MerchantPublicKeyP merchant_pub; + struct GNUNET_JSON_Specification ispec[] = { + TALER_JSON_spec_amount_any ("amount", + &amount), + GNUNET_JSON_spec_string ("type", + &type), + TALER_JSON_spec_amount_any ("refund_fee", + &refund_fee), + GNUNET_JSON_spec_fixed_auto ("merchant_sig", + &sig), + GNUNET_JSON_spec_fixed_auto ("h_contract_terms", + &h_contract_terms), + GNUNET_JSON_spec_fixed_auto ("merchant_pub", + &merchant_pub), + GNUNET_JSON_spec_uint64 ("rtransaction_id", + &rtransaction_id), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (e, + ispec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_merchant_refund_verify (&rh->coin_pub, + &h_contract_terms, + rtransaction_id, + &amount, + &merchant_pub, + &sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if ( (rtransaction_id != rh->rtransaction_id) || + (0 != GNUNET_memcmp (&rh->h_contract_terms, + &h_contract_terms)) || + (0 != GNUNET_memcmp (&rh->merchant, + &merchant_pub)) || + (0 == TALER_amount_cmp (&rh->refund_amount, + &amount)) ) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /refund request. + * + * @param cls the `struct TALER_EXCHANGE_RefundHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_refund_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_RefundHandle *rh = cls; + const json_t *j = response; + struct TALER_EXCHANGE_RefundResponse rr = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code + }; + + rh->job = NULL; + switch (response_code) + { + case 0: + rr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + verify_refund_signature_ok (rh, + j, + &rr.details.ok.exchange_pub, + &rr.details.ok.exchange_sig)) + { + GNUNET_break_op (0); + rr.hr.http_status = 0; + rr.hr.ec = TALER_EC_EXCHANGE_REFUND_INVALID_SIGNATURE_BY_EXCHANGE; + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); also can happen if the currency + differs (which we should obviously never support). + Just pass JSON reply to the application */ + rr.hr.ec = TALER_JSON_get_error_code (j); + rr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_FORBIDDEN: + /* Nothing really to verify, exchange says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + rr.hr.ec = TALER_JSON_get_error_code (j); + rr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + rr.hr.ec = TALER_JSON_get_error_code (j); + rr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_CONFLICT: + /* Requested total refunds exceed deposited amount */ + rr.hr.ec = TALER_JSON_get_error_code (j); + rr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_GONE: + /* Kind of normal: the money was already sent to the merchant + (it was too late for the refund). */ + rr.hr.ec = TALER_JSON_get_error_code (j); + rr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_FAILED_DEPENDENCY: + rr.hr.ec = TALER_JSON_get_error_code (j); + rr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_PRECONDITION_FAILED: + if (GNUNET_OK != + verify_failed_dependency_ok (rh, + j)) + { + GNUNET_break (0); + rr.hr.http_status = 0; + rr.hr.ec = TALER_EC_EXCHANGE_REFUND_INVALID_FAILURE_PROOF_BY_EXCHANGE; + rr.hr.hint = "failed precondition proof returned by exchange is invalid"; + break; + } + /* Two different refund requests were made about the same deposit, but + carrying identical refund transaction ids. */ + rr.hr.ec = TALER_JSON_get_error_code (j); + rr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + rr.hr.ec = TALER_JSON_get_error_code (j); + rr.hr.hint = TALER_JSON_get_error_hint (j); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + rr.hr.ec = TALER_JSON_get_error_code (j); + rr.hr.hint = TALER_JSON_get_error_hint (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange refund\n", + (unsigned int) response_code, + rr.hr.ec); + break; + } + rh->cb (rh->cb_cls, + &rr); + TALER_EXCHANGE_refund_cancel (rh); +} + + +struct TALER_EXCHANGE_RefundHandle * +TALER_EXCHANGE_refund ( + struct GNUNET_CURL_Context *ctx, + const char *url, + struct TALER_EXCHANGE_Keys *keys, + const struct TALER_Amount *amount, + const struct TALER_PrivateContractHashP *h_contract_terms, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + uint64_t rtransaction_id, + const struct TALER_MerchantPrivateKeyP *merchant_priv, + TALER_EXCHANGE_RefundCallback cb, + void *cb_cls) +{ + struct TALER_MerchantPublicKeyP merchant_pub; + struct TALER_MerchantSignatureP merchant_sig; + struct TALER_EXCHANGE_RefundHandle *rh; + json_t *refund_obj; + CURL *eh; + char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32]; + + GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv, + &merchant_pub.eddsa_pub); + TALER_merchant_refund_sign (coin_pub, + h_contract_terms, + rtransaction_id, + amount, + merchant_priv, + &merchant_sig); + { + char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string ( + coin_pub, + sizeof (struct TALER_CoinSpendPublicKeyP), + pub_str, + sizeof (pub_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "coins/%s/refund", + pub_str); + } + refund_obj = GNUNET_JSON_PACK ( + TALER_JSON_pack_amount ("refund_amount", + amount), + GNUNET_JSON_pack_data_auto ("h_contract_terms", + h_contract_terms), + GNUNET_JSON_pack_uint64 ("rtransaction_id", + rtransaction_id), + GNUNET_JSON_pack_data_auto ("merchant_pub", + &merchant_pub), + GNUNET_JSON_pack_data_auto ("merchant_sig", + &merchant_sig)); + rh = GNUNET_new (struct TALER_EXCHANGE_RefundHandle); + rh->cb = cb; + rh->cb_cls = cb_cls; + rh->url = TALER_url_join (url, + arg_str, + NULL); + if (NULL == rh->url) + { + json_decref (refund_obj); + GNUNET_free (rh); + return NULL; + } + rh->h_contract_terms = *h_contract_terms; + rh->coin_pub = *coin_pub; + rh->merchant = merchant_pub; + rh->rtransaction_id = rtransaction_id; + rh->refund_amount = *amount; + eh = TALER_EXCHANGE_curl_easy_get_ (rh->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&rh->ctx, + eh, + refund_obj)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (refund_obj); + GNUNET_free (rh->url); + GNUNET_free (rh); + return NULL; + } + json_decref (refund_obj); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "URL for refund: `%s'\n", + rh->url); + rh->keys = TALER_EXCHANGE_keys_incref (keys); + rh->job = GNUNET_CURL_job_add2 (ctx, + eh, + rh->ctx.headers, + &handle_refund_finished, + rh); + return rh; +} + + +void +TALER_EXCHANGE_refund_cancel (struct TALER_EXCHANGE_RefundHandle *refund) +{ + if (NULL != refund->job) + { + GNUNET_CURL_job_cancel (refund->job); + refund->job = NULL; + } + GNUNET_free (refund->url); + TALER_curl_easy_post_finished (&refund->ctx); + TALER_EXCHANGE_keys_decref (refund->keys); + GNUNET_free (refund); +} + + +/* end of exchange_api_refund.c */ diff --git a/src/lib/exchange_api_post-kyc-start-ID.c b/src/lib/exchange_api_post-kyc-start-ID.c @@ -0,0 +1,220 @@ +/* + This file is part of TALER + Copyright (C) 2024 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_post-kyc-start-ID.c + * @brief functions to start a KYC process + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include "taler/taler_json_lib.h" +#include <microhttpd.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_exchange_service.h" +#include "exchange_api_curl_defaults.h" +#include "taler/taler_signatures.h" +#include "taler/taler_curl_lib.h" +#include "taler/taler_json_lib.h" + + +struct TALER_EXCHANGE_KycStartHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Minor context that holds body and headers. + */ + struct TALER_CURL_PostContext post_ctx; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_KycStartCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; +}; + + +/** + * Function called when we're done processing the + * HTTP POST /kyc-start/$ID request. + * + * @param cls the `struct TALER_EXCHANGE_KycStartHandle *` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_kyc_start_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_KycStartHandle *wh = cls; + const json_t *json = response; + struct TALER_EXCHANGE_KycStartResponse adr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json + }; + + wh->job = NULL; + switch (response_code) + { + case 0: + /* no reply */ + adr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + adr.hr.hint = "server offline?"; + break; + case MHD_HTTP_OK: + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ( + "redirect_url", + &adr.details.ok.redirect_url), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + adr.hr.http_status = 0; + adr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + } + break; + case MHD_HTTP_NOT_FOUND: + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + adr.hr.ec = TALER_JSON_get_error_code (json); + adr.hr.hint = TALER_JSON_get_error_hint (json); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange AML decision\n", + (unsigned int) response_code, + (int) adr.hr.ec); + break; + } + if (NULL != wh->cb) + { + wh->cb (wh->cb_cls, + &adr); + wh->cb = NULL; + } + TALER_EXCHANGE_kyc_start_cancel (wh); +} + + +struct TALER_EXCHANGE_KycStartHandle * +TALER_EXCHANGE_kyc_start ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const char *id, + TALER_EXCHANGE_KycStartCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_KycStartHandle *wh; + CURL *eh; + json_t *body; + + wh = GNUNET_new (struct TALER_EXCHANGE_KycStartHandle); + wh->cb = cb; + wh->cb_cls = cb_cls; + wh->ctx = ctx; + { + char *path; + + GNUNET_asprintf (&path, + "kyc-start/%s", + id); + wh->url = TALER_url_join (url, + path, + NULL); + GNUNET_free (path); + } + if (NULL == wh->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (wh); + return NULL; + } + body = json_object (); /* as per spec: empty! */ + GNUNET_assert (NULL != body); + eh = TALER_EXCHANGE_curl_easy_get_ (wh->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&wh->post_ctx, + eh, + body)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (body); + GNUNET_free (wh->url); + return NULL; + } + json_decref (body); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", + wh->url); + wh->job = GNUNET_CURL_job_add2 (ctx, + eh, + wh->post_ctx.headers, + &handle_kyc_start_finished, + wh); + if (NULL == wh->job) + { + TALER_EXCHANGE_kyc_start_cancel (wh); + return NULL; + } + return wh; +} + + +void +TALER_EXCHANGE_kyc_start_cancel ( + struct TALER_EXCHANGE_KycStartHandle *wh) +{ + if (NULL != wh->job) + { + GNUNET_CURL_job_cancel (wh->job); + wh->job = NULL; + } + TALER_curl_easy_post_finished (&wh->post_ctx); + GNUNET_free (wh->url); + GNUNET_free (wh); +} diff --git a/src/lib/exchange_api_post-kyc-wallet.c b/src/lib/exchange_api_post-kyc-wallet.c @@ -0,0 +1,257 @@ +/* + This file is part of TALER + Copyright (C) 2021 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_post-kyc-wallet.c + * @brief Implementation of the /kyc-wallet request + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <microhttpd.h> /* just for HTTP wallet codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_exchange_service.h" +#include "taler/taler_json_lib.h" +#include "exchange_api_handle.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A ``/kyc-wallet`` handle + */ +struct TALER_EXCHANGE_KycWalletHandle +{ + + /** + * Context for #TEH_curl_easy_post(). Keeps the data that must + * persist for Curl to make the upload. + */ + struct TALER_CURL_PostContext ctx; + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_KycWalletCallback cb; + + /** + * Closure for @e cb. + */ + void *cb_cls; + +}; + + +/** + * Function called when we're done processing the + * HTTP /kyc-wallet request. + * + * @param cls the `struct TALER_EXCHANGE_KycWalletHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_kyc_wallet_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_KycWalletHandle *kwh = cls; + const json_t *j = response; + struct TALER_EXCHANGE_WalletKycResponse ks = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code + }; + + kwh->job = NULL; + switch (response_code) + { + case 0: + ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_mark_optional ( + TALER_JSON_spec_amount_any ( + "next_threshold", + &ks.details.ok.next_threshold), + NULL), + GNUNET_JSON_spec_timestamp ( + "expiration_time", + &ks.details.ok.expiration_time), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + ks.hr.http_status = 0; + ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + break; + } + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_BAD_REQUEST: + ks.hr.ec = TALER_JSON_get_error_code (j); + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_FORBIDDEN: + ks.hr.ec = TALER_JSON_get_error_code (j); + break; + case MHD_HTTP_NOT_FOUND: + ks.hr.ec = TALER_JSON_get_error_code (j); + break; + case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ( + "h_payto", + &ks.details.unavailable_for_legal_reasons.h_payto), + GNUNET_JSON_spec_uint64 ( + "requirement_row", + &ks.details.unavailable_for_legal_reasons.requirement_row), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + ks.hr.http_status = 0; + ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + break; + } + case MHD_HTTP_INTERNAL_SERVER_ERROR: + ks.hr.ec = TALER_JSON_get_error_code (j); + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + ks.hr.ec = TALER_JSON_get_error_code (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange /kyc-wallet\n", + (unsigned int) response_code, + (int) ks.hr.ec); + break; + } + kwh->cb (kwh->cb_cls, + &ks); + TALER_EXCHANGE_kyc_wallet_cancel (kwh); +} + + +struct TALER_EXCHANGE_KycWalletHandle * +TALER_EXCHANGE_kyc_wallet ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const struct TALER_ReservePrivateKeyP *reserve_priv, + const struct TALER_Amount *balance, + TALER_EXCHANGE_KycWalletCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_KycWalletHandle *kwh; + CURL *eh; + json_t *req; + struct TALER_ReservePublicKeyP reserve_pub; + struct TALER_ReserveSignatureP reserve_sig; + + GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, + &reserve_pub.eddsa_pub); + TALER_wallet_account_setup_sign (reserve_priv, + balance, + &reserve_sig); + req = GNUNET_JSON_PACK ( + TALER_JSON_pack_amount ("balance", + balance), + GNUNET_JSON_pack_data_auto ("reserve_pub", + &reserve_pub), + GNUNET_JSON_pack_data_auto ("reserve_sig", + &reserve_sig)); + GNUNET_assert (NULL != req); + kwh = GNUNET_new (struct TALER_EXCHANGE_KycWalletHandle); + kwh->cb = cb; + kwh->cb_cls = cb_cls; + kwh->url = TALER_url_join (url, + "kyc-wallet", + NULL); + if (NULL == kwh->url) + { + json_decref (req); + GNUNET_free (kwh); + return NULL; + } + eh = TALER_EXCHANGE_curl_easy_get_ (kwh->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&kwh->ctx, + eh, + req)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (req); + GNUNET_free (kwh->url); + GNUNET_free (kwh); + return NULL; + } + json_decref (req); + kwh->job = GNUNET_CURL_job_add2 (ctx, + eh, + kwh->ctx.headers, + &handle_kyc_wallet_finished, + kwh); + return kwh; +} + + +void +TALER_EXCHANGE_kyc_wallet_cancel (struct TALER_EXCHANGE_KycWalletHandle *kwh) +{ + if (NULL != kwh->job) + { + GNUNET_CURL_job_cancel (kwh->job); + kwh->job = NULL; + } + GNUNET_free (kwh->url); + TALER_curl_easy_post_finished (&kwh->ctx); + GNUNET_free (kwh); +} + + +/* end of exchange_api_kyc_wallet.c */ diff --git a/src/lib/exchange_api_post-management-auditors-AUDITOR_PUB-disable.c b/src/lib/exchange_api_post-management-auditors-AUDITOR_PUB-disable.c @@ -0,0 +1,220 @@ +/* + This file is part of TALER + Copyright (C) 2015-2021 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_post-management-auditors-AUDITOR_PUB-disable.c + * @brief functions to disable an auditor + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include "taler/taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include <microhttpd.h> +#include "taler/taler_exchange_service.h" +#include "exchange_api_curl_defaults.h" +#include "taler/taler_signatures.h" +#include "taler/taler_curl_lib.h" +#include "taler/taler_json_lib.h" + +/** + * @brief Handle for a POST /management/auditors/$AUDITOR_PUB/disable request. + */ +struct TALER_EXCHANGE_ManagementAuditorDisableHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Minor context that holds body and headers. + */ + struct TALER_CURL_PostContext post_ctx; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_ManagementAuditorDisableCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; +}; + + +/** + * Function called when we're done processing the + * HTTP /management/auditors/%s/disable request. + * + * @param cls the `struct TALER_EXCHANGE_ManagementAuditorDisableHandle *` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_auditor_disable_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_ManagementAuditorDisableHandle *ah = cls; + const json_t *json = response; + struct TALER_EXCHANGE_ManagementAuditorDisableResponse adr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json + }; + + ah->job = NULL; + switch (response_code) + { + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_FORBIDDEN: + adr.hr.ec = TALER_JSON_get_error_code (json); + adr.hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_NOT_FOUND: + adr.hr.ec = TALER_JSON_get_error_code (json); + adr.hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_CONFLICT: + adr.hr.ec = TALER_JSON_get_error_code (json); + adr.hr.hint = TALER_JSON_get_error_hint (json); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + adr.hr.ec = TALER_JSON_get_error_code (json); + adr.hr.hint = TALER_JSON_get_error_hint (json); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange management auditor disable\n", + (unsigned int) response_code, + (int) adr.hr.ec); + break; + } + if (NULL != ah->cb) + { + ah->cb (ah->cb_cls, + &adr); + ah->cb = NULL; + } + TALER_EXCHANGE_management_disable_auditor_cancel (ah); +} + + +struct TALER_EXCHANGE_ManagementAuditorDisableHandle * +TALER_EXCHANGE_management_disable_auditor ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const struct TALER_AuditorPublicKeyP *auditor_pub, + struct GNUNET_TIME_Timestamp validity_end, + const struct TALER_MasterSignatureP *master_sig, + TALER_EXCHANGE_ManagementAuditorDisableCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_ManagementAuditorDisableHandle *ah; + CURL *eh; + json_t *body; + + ah = GNUNET_new (struct TALER_EXCHANGE_ManagementAuditorDisableHandle); + ah->cb = cb; + ah->cb_cls = cb_cls; + ah->ctx = ctx; + { + char epub_str[sizeof (*auditor_pub) * 2]; + char arg_str[sizeof (epub_str) + 64]; + char *end; + + end = GNUNET_STRINGS_data_to_string (auditor_pub, + sizeof (*auditor_pub), + epub_str, + sizeof (epub_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "management/auditors/%s/disable", + epub_str); + ah->url = TALER_url_join (url, + arg_str, + NULL); + } + if (NULL == ah->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (ah); + return NULL; + } + body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("master_sig", + master_sig), + GNUNET_JSON_pack_timestamp ("validity_end", + validity_end)); + eh = TALER_EXCHANGE_curl_easy_get_ (ah->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&ah->post_ctx, + eh, + body))) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (body); + GNUNET_free (ah->url); + return NULL; + } + json_decref (body); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", + ah->url); + ah->job = GNUNET_CURL_job_add2 (ctx, + eh, + ah->post_ctx.headers, + &handle_auditor_disable_finished, + ah); + if (NULL == ah->job) + { + TALER_EXCHANGE_management_disable_auditor_cancel (ah); + return NULL; + } + return ah; +} + + +void +TALER_EXCHANGE_management_disable_auditor_cancel ( + struct TALER_EXCHANGE_ManagementAuditorDisableHandle *ah) +{ + if (NULL != ah->job) + { + GNUNET_CURL_job_cancel (ah->job); + ah->job = NULL; + } + TALER_curl_easy_post_finished (&ah->post_ctx); + GNUNET_free (ah->url); + GNUNET_free (ah); +} diff --git a/src/lib/exchange_api_post-management-auditors.c b/src/lib/exchange_api_post-management-auditors.c @@ -0,0 +1,227 @@ +/* + This file is part of TALER + Copyright (C) 2015-2021 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_post-management-auditors.c + * @brief functions to enable an auditor + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include "taler/taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include <microhttpd.h> +#include "taler/taler_exchange_service.h" +#include "exchange_api_curl_defaults.h" +#include "taler/taler_signatures.h" +#include "taler/taler_curl_lib.h" +#include "taler/taler_json_lib.h" + + +/** + * @brief Handle for a POST /management/auditors request. + */ +struct TALER_EXCHANGE_ManagementAuditorEnableHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Minor context that holds body and headers. + */ + struct TALER_CURL_PostContext post_ctx; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_ManagementAuditorEnableCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; +}; + + +/** + * Function called when we're done processing the + * HTTP POST /management/auditors request. + * + * @param cls the `struct TALER_EXCHANGE_ManagementAuditorEnableHandle *` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_auditor_enable_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_ManagementAuditorEnableHandle *ah = cls; + const json_t *json = response; + struct TALER_EXCHANGE_ManagementAuditorEnableResponse auditor_enable_response + = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json + }; + + ah->job = NULL; + switch (response_code) + { + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_FORBIDDEN: + auditor_enable_response.hr.ec = TALER_JSON_get_error_code (json); + auditor_enable_response.hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_NOT_FOUND: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Server did not find handler at `%s'. Did you configure the correct exchange base URL?\n", + ah->url); + if (NULL != json) + { + auditor_enable_response.hr.ec = TALER_JSON_get_error_code (json); + auditor_enable_response.hr.hint = TALER_JSON_get_error_hint (json); + } + else + { + auditor_enable_response.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + auditor_enable_response.hr.hint = TALER_ErrorCode_get_hint ( + auditor_enable_response.hr.ec); + } + break; + case MHD_HTTP_CONFLICT: + auditor_enable_response.hr.ec = TALER_JSON_get_error_code (json); + auditor_enable_response.hr.hint = TALER_JSON_get_error_hint (json); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + auditor_enable_response.hr.ec = TALER_JSON_get_error_code (json); + auditor_enable_response.hr.hint = TALER_JSON_get_error_hint (json); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange management auditor enable\n", + (unsigned int) response_code, + (int) auditor_enable_response.hr.ec); + break; + } + if (NULL != ah->cb) + { + ah->cb (ah->cb_cls, + &auditor_enable_response); + ah->cb = NULL; + } + TALER_EXCHANGE_management_enable_auditor_cancel (ah); +} + + +struct TALER_EXCHANGE_ManagementAuditorEnableHandle * +TALER_EXCHANGE_management_enable_auditor ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const struct TALER_AuditorPublicKeyP *auditor_pub, + const char *auditor_url, + const char *auditor_name, + struct GNUNET_TIME_Timestamp validity_start, + const struct TALER_MasterSignatureP *master_sig, + TALER_EXCHANGE_ManagementAuditorEnableCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_ManagementAuditorEnableHandle *ah; + CURL *eh; + json_t *body; + + ah = GNUNET_new (struct TALER_EXCHANGE_ManagementAuditorEnableHandle); + ah->cb = cb; + ah->cb_cls = cb_cls; + ah->ctx = ctx; + ah->url = TALER_url_join (url, + "management/auditors", + NULL); + if (NULL == ah->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (ah); + return NULL; + } + body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("auditor_url", + auditor_url), + GNUNET_JSON_pack_string ("auditor_name", + auditor_name), + GNUNET_JSON_pack_data_auto ("auditor_pub", + auditor_pub), + GNUNET_JSON_pack_data_auto ("master_sig", + master_sig), + GNUNET_JSON_pack_timestamp ("validity_start", + validity_start)); + eh = TALER_EXCHANGE_curl_easy_get_ (ah->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&ah->post_ctx, + eh, + body)) ) + { + GNUNET_break (0); + json_decref (body); + if (NULL != eh) + curl_easy_cleanup (eh); + GNUNET_free (ah->url); + return NULL; + } + json_decref (body); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", + ah->url); + ah->job = GNUNET_CURL_job_add2 (ctx, + eh, + ah->post_ctx.headers, + &handle_auditor_enable_finished, + ah); + if (NULL == ah->job) + { + TALER_EXCHANGE_management_enable_auditor_cancel (ah); + return NULL; + } + return ah; +} + + +void +TALER_EXCHANGE_management_enable_auditor_cancel ( + struct TALER_EXCHANGE_ManagementAuditorEnableHandle *ah) +{ + if (NULL != ah->job) + { + GNUNET_CURL_job_cancel (ah->job); + ah->job = NULL; + } + TALER_curl_easy_post_finished (&ah->post_ctx); + GNUNET_free (ah->url); + GNUNET_free (ah); +} diff --git a/src/lib/exchange_api_post-management-denominations-H_DENOM_PUB-revoke.c b/src/lib/exchange_api_post-management-denominations-H_DENOM_PUB-revoke.c @@ -0,0 +1,223 @@ +/* + This file is part of TALER + Copyright (C) 2015-2024 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_post-management-denominations-H_DENOM_PUB-revoke.c + * @brief functions to revoke an exchange denomination key + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include "taler/taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include <microhttpd.h> +#include "taler/taler_exchange_service.h" +#include "exchange_api_curl_defaults.h" +#include "taler/taler_signatures.h" +#include "taler/taler_curl_lib.h" +#include "taler/taler_json_lib.h" + + +/** + * @brief Handle for a POST /management/denominations/$H_DENOM_PUB/revoke request. + */ +struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Minor context that holds body and headers. + */ + struct TALER_CURL_PostContext post_ctx; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_ManagementRevokeDenominationKeyCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; +}; + + +/** + * Function called when we're done processing the + * HTTP /management/denominations/$H_DENOM_PUB/revoke request. + * + * @param cls the `struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle *` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_revoke_denomination_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle *rh = cls; + const json_t *json = response; + struct TALER_EXCHANGE_ManagementRevokeDenominationResponse rdr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json + }; + + rh->job = NULL; + switch (response_code) + { + case 0: + /* no reply */ + rdr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + rdr.hr.hint = "server offline?"; + break; + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_FORBIDDEN: + rdr.hr.ec = TALER_JSON_get_error_code (json); + rdr.hr.hint = TALER_JSON_get_error_hint (json); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + rdr.hr.ec = TALER_JSON_get_error_code (json); + rdr.hr.hint = TALER_JSON_get_error_hint (json); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange management revoke denomination\n", + (unsigned int) response_code, + (int) rdr.hr.ec); + break; + } + if (NULL != rh->cb) + { + rh->cb (rh->cb_cls, + &rdr); + rh->cb = NULL; + } + TALER_EXCHANGE_management_revoke_denomination_key_cancel (rh); +} + + +struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle * +TALER_EXCHANGE_management_revoke_denomination_key ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const struct TALER_DenominationHashP *h_denom_pub, + const struct TALER_MasterSignatureP *master_sig, + TALER_EXCHANGE_ManagementRevokeDenominationKeyCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle *rh; + CURL *eh; + json_t *body; + + rh = GNUNET_new (struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle); + rh->cb = cb; + rh->cb_cls = cb_cls; + rh->ctx = ctx; + { + char epub_str[sizeof (*h_denom_pub) * 2]; + char arg_str[sizeof (epub_str) + 64]; + char *end; + + end = GNUNET_STRINGS_data_to_string (h_denom_pub, + sizeof (*h_denom_pub), + epub_str, + sizeof (epub_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "management/denominations/%s/revoke", + epub_str); + rh->url = TALER_url_join (url, + arg_str, + NULL); + } + if (NULL == rh->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (rh); + return NULL; + } + body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("master_sig", + master_sig)); + if (NULL == body) + { + GNUNET_break (0); + GNUNET_free (rh->url); + GNUNET_free (rh); + return NULL; + } + eh = TALER_EXCHANGE_curl_easy_get_ (rh->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&rh->post_ctx, + eh, + body)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (body); + GNUNET_free (rh->url); + GNUNET_free (rh); + return NULL; + } + json_decref (body); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", + rh->url); + rh->job = GNUNET_CURL_job_add2 (ctx, + eh, + rh->post_ctx.headers, + &handle_revoke_denomination_finished, + rh); + if (NULL == rh->job) + { + TALER_EXCHANGE_management_revoke_denomination_key_cancel (rh); + return NULL; + } + return rh; +} + + +void +TALER_EXCHANGE_management_revoke_denomination_key_cancel ( + struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle *rh) +{ + if (NULL != rh->job) + { + GNUNET_CURL_job_cancel (rh->job); + rh->job = NULL; + } + TALER_curl_easy_post_finished (&rh->post_ctx); + GNUNET_free (rh->url); + GNUNET_free (rh); +} diff --git a/src/lib/exchange_api_post-management-drain.c b/src/lib/exchange_api_post-management-drain.c @@ -0,0 +1,214 @@ +/* + This file is part of TALER + Copyright (C) 2020-2023 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_post-management-drain.c + * @brief functions to set wire fees at an exchange + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include "taler/taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include <microhttpd.h> +#include "exchange_api_curl_defaults.h" +#include "taler/taler_exchange_service.h" +#include "taler/taler_signatures.h" +#include "taler/taler_curl_lib.h" +#include "taler/taler_json_lib.h" + + +struct TALER_EXCHANGE_ManagementDrainProfitsHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Minor context that holds body and headers. + */ + struct TALER_CURL_PostContext post_ctx; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_ManagementDrainProfitsCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; +}; + + +/** + * Function called when we're done processing the + * HTTP /management/drain request. + * + * @param cls the `struct TALER_EXCHANGE_ManagementDrainProfitsHandle *` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_drain_profits_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_ManagementDrainProfitsHandle *dp = cls; + const json_t *json = response; + struct TALER_EXCHANGE_ManagementDrainResponse dr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json + }; + + dp->job = NULL; + switch (response_code) + { + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_FORBIDDEN: + dr.hr.ec = TALER_JSON_get_error_code (json); + dr.hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_CONFLICT: + dr.hr.ec = TALER_JSON_get_error_code (json); + dr.hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_PRECONDITION_FAILED: + dr.hr.ec = TALER_JSON_get_error_code (json); + dr.hr.hint = TALER_JSON_get_error_hint (json); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + dr.hr.ec = TALER_JSON_get_error_code (json); + dr.hr.hint = TALER_JSON_get_error_hint (json); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange management drain profits\n", + (unsigned int) response_code, + (int) dr.hr.ec); + break; + } + if (NULL != dp->cb) + { + dp->cb (dp->cb_cls, + &dr); + dp->cb = NULL; + } + TALER_EXCHANGE_management_drain_profits_cancel (dp); +} + + +struct TALER_EXCHANGE_ManagementDrainProfitsHandle * +TALER_EXCHANGE_management_drain_profits ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const struct TALER_WireTransferIdentifierRawP *wtid, + const struct TALER_Amount *amount, + struct GNUNET_TIME_Timestamp date, + const char *account_section, + const struct TALER_FullPayto payto_uri, + const struct TALER_MasterSignatureP *master_sig, + TALER_EXCHANGE_ManagementDrainProfitsCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_ManagementDrainProfitsHandle *dp; + CURL *eh; + json_t *body; + + dp = GNUNET_new (struct TALER_EXCHANGE_ManagementDrainProfitsHandle); + dp->cb = cb; + dp->cb_cls = cb_cls; + dp->ctx = ctx; + dp->url = TALER_url_join (url, + "management/drain", + NULL); + if (NULL == dp->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (dp); + return NULL; + } + body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("debit_account_section", + account_section), + TALER_JSON_pack_full_payto ("credit_payto_uri", + payto_uri), + GNUNET_JSON_pack_data_auto ("wtid", + wtid), + GNUNET_JSON_pack_data_auto ("master_sig", + master_sig), + GNUNET_JSON_pack_timestamp ("date", + date), + TALER_JSON_pack_amount ("amount", + amount)); + eh = TALER_EXCHANGE_curl_easy_get_ (dp->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&dp->post_ctx, + eh, + body)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (body); + GNUNET_free (dp->url); + return NULL; + } + json_decref (body); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", + dp->url); + dp->job = GNUNET_CURL_job_add2 (ctx, + eh, + dp->post_ctx.headers, + &handle_drain_profits_finished, + dp); + if (NULL == dp->job) + { + TALER_EXCHANGE_management_drain_profits_cancel (dp); + return NULL; + } + return dp; +} + + +void +TALER_EXCHANGE_management_drain_profits_cancel ( + struct TALER_EXCHANGE_ManagementDrainProfitsHandle *dp) +{ + if (NULL != dp->job) + { + GNUNET_CURL_job_cancel (dp->job); + dp->job = NULL; + } + TALER_curl_easy_post_finished (&dp->post_ctx); + GNUNET_free (dp->url); + GNUNET_free (dp); +} diff --git a/src/lib/exchange_api_post-management-extensions.c b/src/lib/exchange_api_post-management-extensions.c @@ -0,0 +1,213 @@ +/* + This file is part of TALER + Copyright (C) 2015-2023 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> + */ +/** + * @file lib/exchange_api_post-management-extensions.c + * @brief functions to handle the settings for extensions (p2p and age restriction) + * @author Özgür Kesim + */ +#include "taler/platform.h" +#include "taler/taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_extensions.h" +#include "exchange_api_curl_defaults.h" +#include "taler/taler_exchange_service.h" +#include "taler/taler_signatures.h" +#include "taler/taler_curl_lib.h" +#include "taler/taler_json_lib.h" + + +/** + * @brief Handle for a POST /management/extensions request. + */ +struct TALER_EXCHANGE_ManagementPostExtensionsHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Minor context that holds body and headers. + */ + struct TALER_CURL_PostContext post_ctx; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_ManagementPostExtensionsCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; +}; + + +/** + * Function called when we're done processing the + * HTTP POST /management/extensions request. + * + * @param cls the `struct TALER_EXCHANGE_ManagementPostExtensionsHandle *` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_post_extensions_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_ManagementPostExtensionsHandle *ph = cls; + const json_t *json = response; + struct TALER_EXCHANGE_ManagementPostExtensionsResponse per = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json + }; + + ph->job = NULL; + switch (response_code) + { + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_FORBIDDEN: + per.hr.ec = TALER_JSON_get_error_code (json); + per.hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_NOT_FOUND: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Server did not find handler at `%s'. Did you configure the correct exchange base URL?\n", + ph->url); + if (NULL != json) + { + per.hr.ec = TALER_JSON_get_error_code (json); + per.hr.hint = TALER_JSON_get_error_hint (json); + } + else + { + per.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + per.hr.hint = TALER_ErrorCode_get_hint (per.hr.ec); + } + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + per.hr.ec = TALER_JSON_get_error_code (json); + per.hr.hint = TALER_JSON_get_error_hint (json); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange management post extensions\n", + (unsigned int) response_code, + (int) per.hr.ec); + break; + } + if (NULL != ph->cb) + { + ph->cb (ph->cb_cls, + &per); + ph->cb = NULL; + } + TALER_EXCHANGE_management_post_extensions_cancel (ph); +} + + +struct TALER_EXCHANGE_ManagementPostExtensionsHandle * +TALER_EXCHANGE_management_post_extensions ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const struct TALER_EXCHANGE_ManagementPostExtensionsData *ped, + TALER_EXCHANGE_ManagementPostExtensionsCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_ManagementPostExtensionsHandle *ph; + CURL *eh = NULL; + json_t *body = NULL; + + ph = GNUNET_new (struct TALER_EXCHANGE_ManagementPostExtensionsHandle); + ph->cb = cb; + ph->cb_cls = cb_cls; + ph->ctx = ctx; + ph->url = TALER_url_join (url, + "management/extensions", + NULL); + if (NULL == ph->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (ph); + return NULL; + } + + body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_object_steal ("extensions", + (json_t *) ped->extensions), + GNUNET_JSON_pack_data_auto ("extensions_sig", + &ped->extensions_sig)); + + eh = TALER_EXCHANGE_curl_easy_get_ (ph->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&ph->post_ctx, + eh, + body)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (body); + GNUNET_free (ph->url); + return NULL; + } + json_decref (body); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Requesting URL '%s'\n", + ph->url); + ph->job = GNUNET_CURL_job_add2 (ctx, + eh, + ph->post_ctx.headers, + &handle_post_extensions_finished, + ph); + if (NULL == ph->job) + { + TALER_EXCHANGE_management_post_extensions_cancel (ph); + return NULL; + } + return ph; +} + + +void +TALER_EXCHANGE_management_post_extensions_cancel ( + struct TALER_EXCHANGE_ManagementPostExtensionsHandle *ph) +{ + if (NULL != ph->job) + { + GNUNET_CURL_job_cancel (ph->job); + ph->job = NULL; + } + TALER_curl_easy_post_finished (&ph->post_ctx); + GNUNET_free (ph->url); + GNUNET_free (ph); +} diff --git a/src/lib/exchange_api_post-management-global-fees.c b/src/lib/exchange_api_post-management-global-fees.c @@ -0,0 +1,237 @@ +/* + This file is part of TALER + Copyright (C) 2020-2022 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_post-management-global-fees.c + * @brief functions to set global fees at an exchange + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include "taler/taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include <microhttpd.h> +#include "exchange_api_curl_defaults.h" +#include "taler/taler_exchange_service.h" +#include "taler/taler_signatures.h" +#include "taler/taler_curl_lib.h" +#include "taler/taler_json_lib.h" + + +struct TALER_EXCHANGE_ManagementSetGlobalFeeHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Minor context that holds body and headers. + */ + struct TALER_CURL_PostContext post_ctx; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_ManagementSetGlobalFeeCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; +}; + + +/** + * Function called when we're done processing the + * HTTP /management/global request. + * + * @param cls the `struct TALER_EXCHANGE_ManagementAuditorEnableHandle *` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_set_global_fee_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_ManagementSetGlobalFeeHandle *sgfh = cls; + const json_t *json = response; + struct TALER_EXCHANGE_ManagementSetGlobalFeeResponse sfr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json + }; + + sgfh->job = NULL; + switch (response_code) + { + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_FORBIDDEN: + sfr.hr.ec = TALER_JSON_get_error_code (json); + sfr.hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_NOT_FOUND: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Server did not find handler at `%s'. Did you configure the correct exchange base URL?\n", + sgfh->url); + if (NULL != json) + { + sfr.hr.ec = TALER_JSON_get_error_code (json); + sfr.hr.hint = TALER_JSON_get_error_hint (json); + } + else + { + sfr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + sfr.hr.hint = TALER_ErrorCode_get_hint (sfr.hr.ec); + } + break; + case MHD_HTTP_CONFLICT: + sfr.hr.ec = TALER_JSON_get_error_code (json); + sfr.hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_PRECONDITION_FAILED: + sfr.hr.ec = TALER_JSON_get_error_code (json); + sfr.hr.hint = TALER_JSON_get_error_hint (json); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + sfr.hr.ec = TALER_JSON_get_error_code (json); + sfr.hr.hint = TALER_JSON_get_error_hint (json); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange management set global fee\n", + (unsigned int) response_code, + (int) sfr.hr.ec); + break; + } + if (NULL != sgfh->cb) + { + sgfh->cb (sgfh->cb_cls, + &sfr); + sgfh->cb = NULL; + } + TALER_EXCHANGE_management_set_global_fees_cancel (sgfh); +} + + +struct TALER_EXCHANGE_ManagementSetGlobalFeeHandle * +TALER_EXCHANGE_management_set_global_fees ( + struct GNUNET_CURL_Context *ctx, + const char *exchange_base_url, + struct GNUNET_TIME_Timestamp validity_start, + struct GNUNET_TIME_Timestamp validity_end, + const struct TALER_GlobalFeeSet *fees, + struct GNUNET_TIME_Relative purse_timeout, + struct GNUNET_TIME_Relative history_expiration, + uint32_t purse_account_limit, + const struct TALER_MasterSignatureP *master_sig, + TALER_EXCHANGE_ManagementSetGlobalFeeCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_ManagementSetGlobalFeeHandle *sgfh; + CURL *eh; + json_t *body; + + sgfh = GNUNET_new (struct TALER_EXCHANGE_ManagementSetGlobalFeeHandle); + sgfh->cb = cb; + sgfh->cb_cls = cb_cls; + sgfh->ctx = ctx; + sgfh->url = TALER_url_join (exchange_base_url, + "management/global-fee", + NULL); + if (NULL == sgfh->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (sgfh); + return NULL; + } + body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("master_sig", + master_sig), + GNUNET_JSON_pack_timestamp ("fee_start", + validity_start), + GNUNET_JSON_pack_timestamp ("fee_end", + validity_end), + TALER_JSON_pack_amount ("history_fee", + &fees->history), + TALER_JSON_pack_amount ("account_fee", + &fees->account), + TALER_JSON_pack_amount ("purse_fee", + &fees->purse), + GNUNET_JSON_pack_time_rel ("purse_timeout", + purse_timeout), + GNUNET_JSON_pack_time_rel ("history_expiration", + history_expiration), + GNUNET_JSON_pack_uint64 ("purse_account_limit", + purse_account_limit)); + eh = TALER_EXCHANGE_curl_easy_get_ (sgfh->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&sgfh->post_ctx, + eh, + body)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (body); + GNUNET_free (sgfh->url); + GNUNET_free (sgfh); + return NULL; + } + json_decref (body); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", + sgfh->url); + sgfh->job = GNUNET_CURL_job_add2 (ctx, + eh, + sgfh->post_ctx.headers, + &handle_set_global_fee_finished, + sgfh); + if (NULL == sgfh->job) + { + TALER_EXCHANGE_management_set_global_fees_cancel (sgfh); + return NULL; + } + return sgfh; +} + + +void +TALER_EXCHANGE_management_set_global_fees_cancel ( + struct TALER_EXCHANGE_ManagementSetGlobalFeeHandle *sgfh) +{ + if (NULL != sgfh->job) + { + GNUNET_CURL_job_cancel (sgfh->job); + sgfh->job = NULL; + } + TALER_curl_easy_post_finished (&sgfh->post_ctx); + GNUNET_free (sgfh->url); + GNUNET_free (sgfh); +} diff --git a/src/lib/exchange_api_post-management-keys.c b/src/lib/exchange_api_post-management-keys.c @@ -0,0 +1,238 @@ +/* + This file is part of TALER + Copyright (C) 2015-2021 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_post-management-keys.c + * @brief functions to affirm the validity of exchange keys using the master private key + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include "taler/taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include <microhttpd.h> +#include "taler/taler_exchange_service.h" +#include "exchange_api_curl_defaults.h" +#include "taler/taler_signatures.h" +#include "taler/taler_curl_lib.h" +#include "taler/taler_json_lib.h" + + +/** + * @brief Handle for a POST /management/keys request. + */ +struct TALER_EXCHANGE_ManagementPostKeysHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Minor context that holds body and headers. + */ + struct TALER_CURL_PostContext post_ctx; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_ManagementPostKeysCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; +}; + + +/** + * Function called when we're done processing the + * HTTP POST /management/keys request. + * + * @param cls the `struct TALER_EXCHANGE_ManagementPostKeysHandle *` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_post_keys_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_ManagementPostKeysHandle *ph = cls; + const json_t *json = response; + struct TALER_EXCHANGE_ManagementPostKeysResponse pkr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json + }; + + ph->job = NULL; + switch (response_code) + { + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_FORBIDDEN: + pkr.hr.ec = TALER_JSON_get_error_code (json); + pkr.hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_NOT_FOUND: + pkr.hr.ec = TALER_JSON_get_error_code (json); + pkr.hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_REQUEST_ENTITY_TOO_LARGE: + pkr.hr.ec = TALER_JSON_get_error_code (json); + pkr.hr.hint = TALER_JSON_get_error_hint (json); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + pkr.hr.ec = TALER_JSON_get_error_code (json); + pkr.hr.hint = TALER_JSON_get_error_hint (json); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange management post keys\n", + (unsigned int) response_code, + (int) pkr.hr.ec); + break; + } + if (NULL != ph->cb) + { + ph->cb (ph->cb_cls, + &pkr); + ph->cb = NULL; + } + TALER_EXCHANGE_post_management_keys_cancel (ph); +} + + +struct TALER_EXCHANGE_ManagementPostKeysHandle * +TALER_EXCHANGE_post_management_keys ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const struct TALER_EXCHANGE_ManagementPostKeysData *pkd, + TALER_EXCHANGE_ManagementPostKeysCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_ManagementPostKeysHandle *ph; + CURL *eh; + json_t *body; + json_t *denom_sigs; + json_t *signkey_sigs; + + ph = GNUNET_new (struct TALER_EXCHANGE_ManagementPostKeysHandle); + ph->cb = cb; + ph->cb_cls = cb_cls; + ph->ctx = ctx; + ph->url = TALER_url_join (url, + "management/keys", + NULL); + if (NULL == ph->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (ph); + return NULL; + } + denom_sigs = json_array (); + GNUNET_assert (NULL != denom_sigs); + for (unsigned int i = 0; i<pkd->num_denom_sigs; i++) + { + const struct TALER_EXCHANGE_DenominationKeySignature *dks + = &pkd->denom_sigs[i]; + + GNUNET_assert (0 == + json_array_append_new ( + denom_sigs, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("h_denom_pub", + &dks->h_denom_pub), + GNUNET_JSON_pack_data_auto ("master_sig", + &dks->master_sig)))); + } + signkey_sigs = json_array (); + GNUNET_assert (NULL != signkey_sigs); + for (unsigned int i = 0; i<pkd->num_sign_sigs; i++) + { + const struct TALER_EXCHANGE_SigningKeySignature *sks + = &pkd->sign_sigs[i]; + + GNUNET_assert (0 == + json_array_append_new ( + signkey_sigs, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("exchange_pub", + &sks->exchange_pub), + GNUNET_JSON_pack_data_auto ("master_sig", + &sks->master_sig)))); + } + body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_array_steal ("denom_sigs", + denom_sigs), + GNUNET_JSON_pack_array_steal ("signkey_sigs", + signkey_sigs)); + eh = TALER_EXCHANGE_curl_easy_get_ (ph->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&ph->post_ctx, + eh, + body)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (body); + GNUNET_free (ph->url); + return NULL; + } + json_decref (body); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", + ph->url); + ph->job = GNUNET_CURL_job_add2 (ctx, + eh, + ph->post_ctx.headers, + &handle_post_keys_finished, + ph); + if (NULL == ph->job) + { + TALER_EXCHANGE_post_management_keys_cancel (ph); + return NULL; + } + return ph; +} + + +void +TALER_EXCHANGE_post_management_keys_cancel ( + struct TALER_EXCHANGE_ManagementPostKeysHandle *ph) +{ + if (NULL != ph->job) + { + GNUNET_CURL_job_cancel (ph->job); + ph->job = NULL; + } + TALER_curl_easy_post_finished (&ph->post_ctx); + GNUNET_free (ph->url); + GNUNET_free (ph); +} diff --git a/src/lib/exchange_api_post-management-partners.c b/src/lib/exchange_api_post-management-partners.c @@ -0,0 +1,219 @@ +/* + This file is part of TALER + Copyright (C) 2023 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_post-management-partners.c + * @brief functions to add an partner by an AML officer + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include "taler/taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include <microhttpd.h> +#include "taler/taler_exchange_service.h" +#include "exchange_api_curl_defaults.h" +#include "taler/taler_signatures.h" +#include "taler/taler_curl_lib.h" +#include "taler/taler_json_lib.h" + + +struct TALER_EXCHANGE_ManagementAddPartner +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Minor context that holds body and headers. + */ + struct TALER_CURL_PostContext post_ctx; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_ManagementAddPartnerCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; +}; + + +/** + * Function called when we're done processing the + * HTTP POST /management/partners request. + * + * @param cls the `struct TALER_EXCHANGE_ManagementAddPartner *` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_add_partner_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_ManagementAddPartner *wh = cls; + const json_t *json = response; + struct TALER_EXCHANGE_ManagementAddPartnerResponse apr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json + }; + + wh->job = NULL; + switch (response_code) + { + case 0: + /* no reply */ + apr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + apr.hr.hint = "server offline?"; + break; + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_FORBIDDEN: + apr.hr.ec = TALER_JSON_get_error_code (json); + apr.hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_CONFLICT: + apr.hr.ec = TALER_JSON_get_error_code (json); + apr.hr.hint = TALER_JSON_get_error_hint (json); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + apr.hr.ec = TALER_JSON_get_error_code (json); + apr.hr.hint = TALER_JSON_get_error_hint (json); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for adding exchange partner\n", + (unsigned int) response_code, + (int) apr.hr.ec); + break; + } + if (NULL != wh->cb) + { + wh->cb (wh->cb_cls, + &apr); + wh->cb = NULL; + } + TALER_EXCHANGE_management_add_partner_cancel (wh); +} + + +struct TALER_EXCHANGE_ManagementAddPartner * +TALER_EXCHANGE_management_add_partner ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const struct TALER_MasterPublicKeyP *partner_pub, + struct GNUNET_TIME_Timestamp start_date, + struct GNUNET_TIME_Timestamp end_date, + struct GNUNET_TIME_Relative wad_frequency, + const struct TALER_Amount *wad_fee, + const char *partner_base_url, + const struct TALER_MasterSignatureP *master_sig, + TALER_EXCHANGE_ManagementAddPartnerCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_ManagementAddPartner *wh; + CURL *eh; + json_t *body; + + wh = GNUNET_new (struct TALER_EXCHANGE_ManagementAddPartner); + wh->cb = cb; + wh->cb_cls = cb_cls; + wh->ctx = ctx; + wh->url = TALER_url_join (url, + "management/partners", + NULL); + if (NULL == wh->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (wh); + return NULL; + } + body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("partner_base_url", + partner_base_url), + GNUNET_JSON_pack_timestamp ("start_date", + start_date), + GNUNET_JSON_pack_timestamp ("end_date", + end_date), + GNUNET_JSON_pack_time_rel ("wad_frequency", + wad_frequency), + GNUNET_JSON_pack_data_auto ("partner_pub", + &partner_pub), + GNUNET_JSON_pack_data_auto ("master_sig", + &master_sig), + TALER_JSON_pack_amount ("wad_fee", + wad_fee) + ); + eh = TALER_EXCHANGE_curl_easy_get_ (wh->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&wh->post_ctx, + eh, + body)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (body); + GNUNET_free (wh->url); + return NULL; + } + json_decref (body); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", + wh->url); + wh->job = GNUNET_CURL_job_add2 (ctx, + eh, + wh->post_ctx.headers, + &handle_add_partner_finished, + wh); + if (NULL == wh->job) + { + TALER_EXCHANGE_management_add_partner_cancel (wh); + return NULL; + } + return wh; +} + + +void +TALER_EXCHANGE_management_add_partner_cancel ( + struct TALER_EXCHANGE_ManagementAddPartner *wh) +{ + if (NULL != wh->job) + { + GNUNET_CURL_job_cancel (wh->job); + wh->job = NULL; + } + TALER_curl_easy_post_finished (&wh->post_ctx); + GNUNET_free (wh->url); + GNUNET_free (wh); +} diff --git a/src/lib/exchange_api_post-management-signkeys-EXCHANGE_PUB-revoke.c b/src/lib/exchange_api_post-management-signkeys-EXCHANGE_PUB-revoke.c @@ -0,0 +1,213 @@ +/* + This file is part of TALER + Copyright (C) 2015-2021 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_post-management-signkeys-EXCHANGE_PUB-revoke.c + * @brief functions to revoke an exchange online signing key + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include "taler/taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include <microhttpd.h> +#include "taler/taler_exchange_service.h" +#include "exchange_api_curl_defaults.h" +#include "taler/taler_signatures.h" +#include "taler/taler_curl_lib.h" +#include "taler/taler_json_lib.h" + + +struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Minor context that holds body and headers. + */ + struct TALER_CURL_PostContext post_ctx; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_ManagementRevokeSigningKeyCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; +}; + + +/** + * Function called when we're done processing the + * HTTP /management/signkeys/%s/revoke request. + * + * @param cls the `struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle *` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_revoke_signing_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle *rh = cls; + const json_t *json = response; + struct TALER_EXCHANGE_ManagementRevokeSigningKeyResponse rsr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json + }; + + rh->job = NULL; + switch (response_code) + { + case 0: + /* no reply */ + rsr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + rsr.hr.hint = "server offline?"; + break; + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_FORBIDDEN: + rsr.hr.ec = TALER_JSON_get_error_code (json); + rsr.hr.hint = TALER_JSON_get_error_hint (json); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + rsr.hr.ec = TALER_JSON_get_error_code (json); + rsr.hr.hint = TALER_JSON_get_error_hint (json); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange management revoke signkey\n", + (unsigned int) response_code, + (int) rsr.hr.ec); + break; + } + if (NULL != rh->cb) + { + rh->cb (rh->cb_cls, + &rsr); + rh->cb = NULL; + } + TALER_EXCHANGE_management_revoke_signing_key_cancel (rh); +} + + +struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle * +TALER_EXCHANGE_management_revoke_signing_key ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const struct TALER_ExchangePublicKeyP *exchange_pub, + const struct TALER_MasterSignatureP *master_sig, + TALER_EXCHANGE_ManagementRevokeSigningKeyCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle *rh; + CURL *eh; + json_t *body; + + rh = GNUNET_new (struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle); + rh->cb = cb; + rh->cb_cls = cb_cls; + rh->ctx = ctx; + { + char epub_str[sizeof (*exchange_pub) * 2]; + char arg_str[sizeof (epub_str) + 64]; + char *end; + + end = GNUNET_STRINGS_data_to_string (exchange_pub, + sizeof (*exchange_pub), + epub_str, + sizeof (epub_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "management/signkeys/%s/revoke", + epub_str); + rh->url = TALER_url_join (url, + arg_str, + NULL); + } + if (NULL == rh->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (rh); + return NULL; + } + body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("master_sig", + master_sig)); + eh = TALER_EXCHANGE_curl_easy_get_ (rh->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&rh->post_ctx, + eh, + body)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (body); + GNUNET_free (rh->url); + GNUNET_free (rh); + return NULL; + } + json_decref (body); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", + rh->url); + rh->job = GNUNET_CURL_job_add2 (ctx, + eh, + rh->post_ctx.headers, + &handle_revoke_signing_finished, + rh); + if (NULL == rh->job) + { + TALER_EXCHANGE_management_revoke_signing_key_cancel (rh); + return NULL; + } + return rh; +} + + +void +TALER_EXCHANGE_management_revoke_signing_key_cancel ( + struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle *rh) +{ + if (NULL != rh->job) + { + GNUNET_CURL_job_cancel (rh->job); + rh->job = NULL; + } + TALER_curl_easy_post_finished (&rh->post_ctx); + GNUNET_free (rh->url); + GNUNET_free (rh); +} diff --git a/src/lib/exchange_api_post-management-wire-disable.c b/src/lib/exchange_api_post-management-wire-disable.c @@ -0,0 +1,222 @@ +/* + This file is part of TALER + Copyright (C) 2015-2023 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_post-management-wire-disable.c + * @brief functions to disable an exchange wire method / bank account + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include "taler/taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include <microhttpd.h> +#include "taler/taler_exchange_service.h" +#include "exchange_api_curl_defaults.h" +#include "taler/taler_signatures.h" +#include "taler/taler_curl_lib.h" +#include "taler/taler_json_lib.h" + + +struct TALER_EXCHANGE_ManagementWireDisableHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Minor context that holds body and headers. + */ + struct TALER_CURL_PostContext post_ctx; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_ManagementWireDisableCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; +}; + + +/** + * Function called when we're done processing the + * HTTP /management/wire/disable request. + * + * @param cls the `struct TALER_EXCHANGE_ManagementAuditorDisableHandle *` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_auditor_disable_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_ManagementWireDisableHandle *wh = cls; + const json_t *json = response; + struct TALER_EXCHANGE_ManagementWireDisableResponse wdr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json + }; + + wh->job = NULL; + switch (response_code) + { + case 0: + /* no reply */ + wdr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + wdr.hr.hint = "server offline?"; + break; + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_FORBIDDEN: + wdr.hr.ec = TALER_JSON_get_error_code (json); + wdr.hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_NOT_FOUND: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Server did not find handler at `%s'. Did you configure the correct exchange base URL?\n", + wh->url); + if (NULL != json) + { + wdr.hr.ec = TALER_JSON_get_error_code (json); + wdr.hr.hint = TALER_JSON_get_error_hint (json); + } + else + { + wdr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + wdr.hr.hint = TALER_ErrorCode_get_hint (wdr.hr.ec); + } + break; + case MHD_HTTP_CONFLICT: + wdr.hr.ec = TALER_JSON_get_error_code (json); + wdr.hr.hint = TALER_JSON_get_error_hint (json); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + wdr.hr.ec = TALER_JSON_get_error_code (json); + wdr.hr.hint = TALER_JSON_get_error_hint (json); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d exchange management disable wire\n", + (unsigned int) response_code, + (int) wdr.hr.ec); + break; + } + if (NULL != wh->cb) + { + wh->cb (wh->cb_cls, + &wdr); + wh->cb = NULL; + } + TALER_EXCHANGE_management_disable_wire_cancel (wh); +} + + +struct TALER_EXCHANGE_ManagementWireDisableHandle * +TALER_EXCHANGE_management_disable_wire ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const struct TALER_FullPayto payto_uri, + struct GNUNET_TIME_Timestamp validity_end, + const struct TALER_MasterSignatureP *master_sig, + TALER_EXCHANGE_ManagementWireDisableCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_ManagementWireDisableHandle *wh; + CURL *eh; + json_t *body; + + wh = GNUNET_new (struct TALER_EXCHANGE_ManagementWireDisableHandle); + wh->cb = cb; + wh->cb_cls = cb_cls; + wh->ctx = ctx; + wh->url = TALER_url_join (url, + "management/wire/disable", + NULL); + if (NULL == wh->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (wh); + return NULL; + } + body = GNUNET_JSON_PACK ( + TALER_JSON_pack_full_payto ("payto_uri", + payto_uri), + GNUNET_JSON_pack_data_auto ("master_sig_del", + master_sig), + GNUNET_JSON_pack_timestamp ("validity_end", + validity_end)); + eh = TALER_EXCHANGE_curl_easy_get_ (wh->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&wh->post_ctx, + eh, + body)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (body); + GNUNET_free (wh->url); + GNUNET_free (wh); + return NULL; + } + json_decref (body); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", + wh->url); + wh->job = GNUNET_CURL_job_add2 (ctx, + eh, + wh->post_ctx.headers, + &handle_auditor_disable_finished, + wh); + if (NULL == wh->job) + { + TALER_EXCHANGE_management_disable_wire_cancel (wh); + return NULL; + } + return wh; +} + + +void +TALER_EXCHANGE_management_disable_wire_cancel ( + struct TALER_EXCHANGE_ManagementWireDisableHandle *wh) +{ + if (NULL != wh->job) + { + GNUNET_CURL_job_cancel (wh->job); + wh->job = NULL; + } + TALER_curl_easy_post_finished (&wh->post_ctx); + GNUNET_free (wh->url); + GNUNET_free (wh); +} diff --git a/src/lib/exchange_api_post-management-wire-fee.c b/src/lib/exchange_api_post-management-wire-fee.c @@ -0,0 +1,229 @@ +/* + This file is part of TALER + Copyright (C) 2020-2022 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_post-management-wire-fee.c + * @brief functions to set wire fees at an exchange + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include "taler/taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include <microhttpd.h> +#include "exchange_api_curl_defaults.h" +#include "taler/taler_exchange_service.h" +#include "taler/taler_signatures.h" +#include "taler/taler_curl_lib.h" +#include "taler/taler_json_lib.h" + + +struct TALER_EXCHANGE_ManagementSetWireFeeHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Minor context that holds body and headers. + */ + struct TALER_CURL_PostContext post_ctx; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_ManagementSetWireFeeCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; +}; + + +/** + * Function called when we're done processing the + * HTTP /management/wire request. + * + * @param cls the `struct TALER_EXCHANGE_ManagementAuditorEnableHandle *` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_set_wire_fee_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_ManagementSetWireFeeHandle *swfh = cls; + const json_t *json = response; + struct TALER_EXCHANGE_ManagementSetWireFeeResponse swr = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json + }; + + swfh->job = NULL; + switch (response_code) + { + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_FORBIDDEN: + swr.hr.ec = TALER_JSON_get_error_code (json); + swr.hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_NOT_FOUND: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Server did not find handler at `%s'. Did you configure the correct exchange base URL?\n", + swfh->url); + if (NULL != json) + { + swr.hr.ec = TALER_JSON_get_error_code (json); + swr.hr.hint = TALER_JSON_get_error_hint (json); + } + else + { + swr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + swr.hr.hint = TALER_ErrorCode_get_hint (swr.hr.ec); + } + break; + case MHD_HTTP_CONFLICT: + swr.hr.ec = TALER_JSON_get_error_code (json); + swr.hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_PRECONDITION_FAILED: + swr.hr.ec = TALER_JSON_get_error_code (json); + swr.hr.hint = TALER_JSON_get_error_hint (json); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + swr.hr.ec = TALER_JSON_get_error_code (json); + swr.hr.hint = TALER_JSON_get_error_hint (json); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange management set wire fee\n", + (unsigned int) response_code, + (int) swr.hr.ec); + break; + } + if (NULL != swfh->cb) + { + swfh->cb (swfh->cb_cls, + &swr); + swfh->cb = NULL; + } + TALER_EXCHANGE_management_set_wire_fees_cancel (swfh); +} + + +struct TALER_EXCHANGE_ManagementSetWireFeeHandle * +TALER_EXCHANGE_management_set_wire_fees ( + struct GNUNET_CURL_Context *ctx, + const char *exchange_base_url, + const char *wire_method, + struct GNUNET_TIME_Timestamp validity_start, + struct GNUNET_TIME_Timestamp validity_end, + const struct TALER_WireFeeSet *fees, + const struct TALER_MasterSignatureP *master_sig, + TALER_EXCHANGE_ManagementSetWireFeeCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_ManagementSetWireFeeHandle *swfh; + CURL *eh; + json_t *body; + + swfh = GNUNET_new (struct TALER_EXCHANGE_ManagementSetWireFeeHandle); + swfh->cb = cb; + swfh->cb_cls = cb_cls; + swfh->ctx = ctx; + swfh->url = TALER_url_join (exchange_base_url, + "management/wire-fee", + NULL); + if (NULL == swfh->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (swfh); + return NULL; + } + body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("wire_method", + wire_method), + GNUNET_JSON_pack_data_auto ("master_sig", + master_sig), + GNUNET_JSON_pack_timestamp ("fee_start", + validity_start), + GNUNET_JSON_pack_timestamp ("fee_end", + validity_end), + TALER_JSON_pack_amount ("closing_fee", + &fees->closing), + TALER_JSON_pack_amount ("wire_fee", + &fees->wire)); + eh = TALER_EXCHANGE_curl_easy_get_ (swfh->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&swfh->post_ctx, + eh, + body)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (body); + GNUNET_free (swfh->url); + GNUNET_free (swfh); + return NULL; + } + json_decref (body); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", + swfh->url); + swfh->job = GNUNET_CURL_job_add2 (ctx, + eh, + swfh->post_ctx.headers, + &handle_set_wire_fee_finished, + swfh); + if (NULL == swfh->job) + { + TALER_EXCHANGE_management_set_wire_fees_cancel (swfh); + return NULL; + } + return swfh; +} + + +void +TALER_EXCHANGE_management_set_wire_fees_cancel ( + struct TALER_EXCHANGE_ManagementSetWireFeeHandle *swfh) +{ + if (NULL != swfh->job) + { + GNUNET_CURL_job_cancel (swfh->job); + swfh->job = NULL; + } + TALER_curl_easy_post_finished (&swfh->post_ctx); + GNUNET_free (swfh->url); + GNUNET_free (swfh); +} diff --git a/src/lib/exchange_api_post-management-wire.c b/src/lib/exchange_api_post-management-wire.c @@ -0,0 +1,254 @@ +/* + This file is part of TALER + Copyright (C) 2015-2023 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_post-management-wire.c + * @brief functions to enable an exchange wire method / bank account + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include "taler/taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include <microhttpd.h> +#include "taler/taler_exchange_service.h" +#include "exchange_api_curl_defaults.h" +#include "taler/taler_signatures.h" +#include "taler/taler_curl_lib.h" +#include "taler/taler_json_lib.h" + + +struct TALER_EXCHANGE_ManagementWireEnableHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Minor context that holds body and headers. + */ + struct TALER_CURL_PostContext post_ctx; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_ManagementWireEnableCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; +}; + + +/** + * Function called when we're done processing the + * HTTP /management/wire request. + * + * @param cls the `struct TALER_EXCHANGE_ManagementAuditorEnableHandle *` + * @param response_code HTTP response code, 0 on error + * @param response response body, NULL if not in JSON + */ +static void +handle_auditor_enable_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_ManagementWireEnableHandle *wh = cls; + const json_t *json = response; + struct TALER_EXCHANGE_ManagementWireEnableResponse wer = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json + }; + + wh->job = NULL; + switch (response_code) + { + case 0: + /* no reply */ + wer.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + wer.hr.hint = "server offline?"; + break; + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_FORBIDDEN: + wer.hr.ec = TALER_JSON_get_error_code (json); + wer.hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_NOT_FOUND: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Server did not find handler at `%s'. Did you configure the correct exchange base URL?\n", + wh->url); + if (NULL != json) + { + wer.hr.ec = TALER_JSON_get_error_code (json); + wer.hr.hint = TALER_JSON_get_error_hint (json); + } + else + { + wer.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + wer.hr.hint = TALER_ErrorCode_get_hint (wer.hr.ec); + } + break; + case MHD_HTTP_CONFLICT: + wer.hr.ec = TALER_JSON_get_error_code (json); + wer.hr.hint = TALER_JSON_get_error_hint (json); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + wer.hr.ec = TALER_JSON_get_error_code (json); + wer.hr.hint = TALER_JSON_get_error_hint (json); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange management enable wire\n", + (unsigned int) response_code, + (int) wer.hr.ec); + break; + } + if (NULL != wh->cb) + { + wh->cb (wh->cb_cls, + &wer); + wh->cb = NULL; + } + TALER_EXCHANGE_management_enable_wire_cancel (wh); +} + + +struct TALER_EXCHANGE_ManagementWireEnableHandle * +TALER_EXCHANGE_management_enable_wire ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const struct TALER_FullPayto payto_uri, + const char *conversion_url, + const json_t *debit_restrictions, + const json_t *credit_restrictions, + struct GNUNET_TIME_Timestamp validity_start, + const struct TALER_MasterSignatureP *master_sig1, + const struct TALER_MasterSignatureP *master_sig2, + const char *bank_label, + int64_t priority, + TALER_EXCHANGE_ManagementWireEnableCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_ManagementWireEnableHandle *wh; + CURL *eh; + json_t *body; + + { + char *msg = TALER_payto_validate (payto_uri); + + if (NULL != msg) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "payto URI is malformed: %s\n", + msg); + GNUNET_free (msg); + return NULL; + } + } + wh = GNUNET_new (struct TALER_EXCHANGE_ManagementWireEnableHandle); + wh->cb = cb; + wh->cb_cls = cb_cls; + wh->ctx = ctx; + wh->url = TALER_url_join (url, + "management/wire", + NULL); + if (NULL == wh->url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not construct request URL.\n"); + GNUNET_free (wh); + return NULL; + } + body = GNUNET_JSON_PACK ( + TALER_JSON_pack_full_payto ("payto_uri", + payto_uri), + GNUNET_JSON_pack_array_incref ("debit_restrictions", + (json_t *) debit_restrictions), + GNUNET_JSON_pack_array_incref ("credit_restrictions", + (json_t *) credit_restrictions), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("conversion_url", + conversion_url)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("bank_label", + bank_label)), + GNUNET_JSON_pack_int64 ("priority", + priority), + GNUNET_JSON_pack_data_auto ("master_sig_add", + master_sig1), + GNUNET_JSON_pack_data_auto ("master_sig_wire", + master_sig2), + GNUNET_JSON_pack_timestamp ("validity_start", + validity_start)); + eh = TALER_EXCHANGE_curl_easy_get_ (wh->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&wh->post_ctx, + eh, + body)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (body); + GNUNET_free (wh->url); + GNUNET_free (wh); + return NULL; + } + json_decref (body); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", + wh->url); + wh->job = GNUNET_CURL_job_add2 (ctx, + eh, + wh->post_ctx.headers, + &handle_auditor_enable_finished, + wh); + if (NULL == wh->job) + { + TALER_EXCHANGE_management_enable_wire_cancel (wh); + return NULL; + } + return wh; +} + + +void +TALER_EXCHANGE_management_enable_wire_cancel ( + struct TALER_EXCHANGE_ManagementWireEnableHandle *wh) +{ + if (NULL != wh->job) + { + GNUNET_CURL_job_cancel (wh->job); + wh->job = NULL; + } + TALER_curl_easy_post_finished (&wh->post_ctx); + GNUNET_free (wh->url); + GNUNET_free (wh); +} diff --git a/src/lib/exchange_api_post-melt.c b/src/lib/exchange_api_post-melt.c @@ -0,0 +1,645 @@ +/* + This file is part of TALER + Copyright (C) 2025 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_post-melt.c + * @brief Implementation of the /melt request + * @author Özgür Kesim + */ +#include "taler/platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_json_lib.h" +#include "taler/taler_exchange_service.h" +#include "exchange_api_common.h" +#include "exchange_api_handle.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" +#include "exchange_api_refresh_common.h" + + +/** + * @brief A /melt Handle + */ +struct TALER_EXCHANGE_MeltHandle +{ + + /** + * The keys of the this request handle will use + */ + struct TALER_EXCHANGE_Keys *keys; + + /** + * The url for this request. + */ + char *url; + + /** + * The exchange base url. + */ + char *exchange_url; + + /** + * Curl context. + */ + struct GNUNET_CURL_Context *cctx; + + /** + * Context for #TEH_curl_easy_post(). Keeps the data that must + * persist for Curl to make the upload. + */ + struct TALER_CURL_PostContext ctx; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with refresh melt failure results. + */ + TALER_EXCHANGE_MeltCallback melt_cb; + + /** + * Closure for @e result_cb and @e melt_failure_cb. + */ + void *melt_cb_cls; + + /** + * Actual information about the melt operation. + */ + struct MeltData md; + + /** + * The seed for the melt operation. + */ + struct TALER_PublicRefreshMasterSeedP rms; + + /** + * Details about the characteristics of the requested melt operation. + */ + const struct TALER_EXCHANGE_MeltInput *rd; + + /** + * True, if no blinding_seed is needed (no CS denominations involved) + */ + bool no_blinding_seed; + + /** + * If @e no_blinding_seed is false, the blinding seed for the intermediate + * call to /blinding-prepare, in order to retrieve the R-values from the + * exchange for the blind Clause-Schnorr signature. + */ + struct TALER_BlindingMasterSeedP blinding_seed; + + /** + * Array of `num_fresh_denom_pubs` per-coin values + * returned from melt operation. + */ + struct TALER_ExchangeBlindingValues *melt_blinding_values; + + /** + * Handle for the preflight request, or NULL. + */ + struct TALER_EXCHANGE_BlindingPrepareHandle *bpr; + + /** + * Public key of the coin being melted. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + + /** + * Signature affirming the melt. + */ + struct TALER_CoinSpendSignatureP coin_sig; + + /** + * @brief Public information about the coin's denomination key + */ + const struct TALER_EXCHANGE_DenomPublicKey *dki; + + /** + * Gamma value chosen by the exchange during melt. + */ + uint32_t noreveal_index; + +}; + + +/** + * Verify that the signature on the "200 OK" response + * from the exchange is valid. + * + * @param[in,out] mh melt handle + * @param json json reply with the signature + * @param[out] exchange_pub public key of the exchange used for the signature + * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not + */ +static enum GNUNET_GenericReturnValue +verify_melt_signature_ok (struct TALER_EXCHANGE_MeltHandle *mh, + const json_t *json, + struct TALER_ExchangePublicKeyP *exchange_pub) +{ + struct TALER_ExchangeSignatureP exchange_sig; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + exchange_pub), + GNUNET_JSON_spec_uint32 ("noreveal_index", + &mh->noreveal_index), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + /* check that exchange signing key is permitted */ + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (mh->keys, + exchange_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + /* check that noreveal index is in permitted range */ + if (TALER_CNC_KAPPA <= mh->noreveal_index) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != + TALER_exchange_online_melt_confirmation_verify ( + &mh->md.rc, + mh->noreveal_index, + exchange_pub, + &exchange_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /melt request. + * + * @param cls the `struct TALER_EXCHANGE_MeltHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_melt_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_MeltHandle *mh = cls; + const json_t *j = response; + struct TALER_EXCHANGE_MeltResponse mr = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code + }; + + mh->job = NULL; + switch (response_code) + { + case 0: + mr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + verify_melt_signature_ok (mh, + j, + &mr.details.ok.sign_key)) + { + GNUNET_break_op (0); + mr.hr.http_status = 0; + mr.hr.ec = TALER_EC_EXCHANGE_MELT_INVALID_SIGNATURE_BY_EXCHANGE; + break; + } + mr.details.ok.noreveal_index = mh->noreveal_index; + mr.details.ok.num_melt_blinding_values = mh->rd->num_fresh_denom_pubs; + mr.details.ok.melt_blinding_values = mh->melt_blinding_values; + mr.details.ok.blinding_seed = mh->no_blinding_seed + ? NULL + : &mh->blinding_seed; + mh->melt_cb (mh->melt_cb_cls, + &mr); + mh->melt_cb = NULL; + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + mr.hr.ec = TALER_JSON_get_error_code (j); + mr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_CONFLICT: + mr.hr.ec = TALER_JSON_get_error_code (j); + mr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_FORBIDDEN: + /* Nothing really to verify, exchange says one of the signatures is + invalid; assuming we checked them, this should never happen, we + should pass the JSON reply to the application */ + mr.hr.ec = TALER_JSON_get_error_code (j); + mr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + mr.hr.ec = TALER_JSON_get_error_code (j); + mr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + mr.hr.ec = TALER_JSON_get_error_code (j); + mr.hr.hint = TALER_JSON_get_error_hint (j); + break; + default: + /* unexpected response code */ + mr.hr.ec = TALER_JSON_get_error_code (j); + mr.hr.hint = TALER_JSON_get_error_hint (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange melt\n", + (unsigned int) response_code, + mr.hr.ec); + GNUNET_break_op (0); + break; + } + if (NULL != mh->melt_cb) + mh->melt_cb (mh->melt_cb_cls, + &mr); + TALER_EXCHANGE_melt_cancel (mh); +} + + +/** + * Start the actual melt operation, now that we have + * the exchange's input values. + * + * @param[in,out] mh melt operation to run + * @return #GNUNET_OK if we could start the operation + */ +static enum GNUNET_GenericReturnValue +start_melt (struct TALER_EXCHANGE_MeltHandle *mh) +{ + json_t *j_request_body; + json_t *j_transfer_pubs; + json_t *j_coin_evs; + CURL *eh; + struct TALER_DenominationHashP h_denom_pub; + + if (GNUNET_OK != + TALER_EXCHANGE_get_melt_data (&mh->rms, + mh->rd, + mh->no_blinding_seed + ? NULL + : &mh->blinding_seed, + mh->melt_blinding_values, + &mh->md)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + TALER_denom_pub_hash ( + &mh->md.melted_coin.pub_key, + &h_denom_pub); + TALER_wallet_melt_sign ( + &mh->md.melted_coin.melt_amount_with_fee, + &mh->md.melted_coin.fee_melt, + &mh->md.rc, + &h_denom_pub, + mh->md.melted_coin.h_age_commitment, + &mh->md.melted_coin.coin_priv, + &mh->coin_sig); + GNUNET_CRYPTO_eddsa_key_get_public ( + &mh->md.melted_coin.coin_priv.eddsa_priv, + &mh->coin_pub.eddsa_pub); + mh->dki = TALER_EXCHANGE_get_denomination_key ( + mh->keys, + &mh->md.melted_coin.pub_key); + j_request_body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("old_coin_pub", + &mh->coin_pub), + GNUNET_JSON_pack_data_auto ("old_denom_pub_h", + &h_denom_pub), + TALER_JSON_pack_denom_sig ("old_denom_sig", + &mh->md.melted_coin.sig), + GNUNET_JSON_pack_data_auto ("confirm_sig", + &mh->coin_sig), + TALER_JSON_pack_amount ("value_with_fee", + &mh->md.melted_coin.melt_amount_with_fee), + GNUNET_JSON_pack_allow_null ( + (NULL != mh->md.melted_coin.h_age_commitment) + ? GNUNET_JSON_pack_data_auto ("old_age_commitment_h", + mh->md.melted_coin.h_age_commitment) + : GNUNET_JSON_pack_string ("old_age_commitment_h", + NULL)), + GNUNET_JSON_pack_data_auto ("refresh_seed", + &mh->md.refresh_seed), + GNUNET_JSON_pack_allow_null ( + (mh->md.no_blinding_seed) + ? GNUNET_JSON_pack_string ("blinding_seed", + NULL) + : GNUNET_JSON_pack_data_auto ("blinding_seed", + &mh->md.blinding_seed)), + TALER_JSON_pack_array_of_data_auto ("denoms_h", + mh->md.num_fresh_coins, + mh->md.denoms_h) + ); + GNUNET_assert (NULL != j_request_body); + GNUNET_assert (NULL != + (j_transfer_pubs = json_array ())); + GNUNET_assert (NULL != + (j_coin_evs = json_array ())); + /** + * Fill the kappa array of coin envelopes and + * the array of transfer pubs. + */ + for (uint8_t k=0; k<TALER_CNC_KAPPA; k++) + { + json_t *j_envs; + json_t *j_tbs = GNUNET_JSON_PACK ( + TALER_JSON_pack_array_of_data_auto (NULL, + mh->md.num_fresh_coins, + mh->md.kappa_transfer_pubs[k]) + ); + + GNUNET_assert (NULL != (j_envs = json_array ())); + GNUNET_assert (NULL !=j_tbs); + + for (size_t i = 0; i < mh->md.num_fresh_coins; i++) + { + json_t *j_coin = GNUNET_JSON_PACK ( + TALER_JSON_pack_blinded_planchet (NULL, + &mh->md.kappa_blinded_planchets[k][i])); + GNUNET_assert (NULL != j_coin); + GNUNET_assert (0 == + json_array_append_new (j_envs, j_coin)); + } + GNUNET_assert (0 == + json_array_append_new (j_coin_evs, j_envs)); + GNUNET_assert (0 == + json_array_append_new (j_transfer_pubs, j_tbs)); + } + GNUNET_assert (0 == + json_object_set_new (j_request_body, + "coin_evs", + j_coin_evs)); + GNUNET_assert (0 == + json_object_set_new (j_request_body, + "transfer_pubs", + j_transfer_pubs)); + /* and now we can at last begin the actual request handling */ + mh->url = TALER_url_join (mh->exchange_url, + "melt", + NULL); + if (NULL == mh->url) + { + json_decref (j_request_body); + return GNUNET_SYSERR; + } + eh = TALER_EXCHANGE_curl_easy_get_ (mh->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&mh->ctx, + eh, + j_request_body)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (j_request_body); + return GNUNET_SYSERR; + } + json_decref (j_request_body); + mh->job = GNUNET_CURL_job_add2 (mh->cctx, + eh, + mh->ctx.headers, + &handle_melt_finished, + mh); + return GNUNET_OK; +} + + +/** + * The melt request @a mh failed, return an error to + * the application and cancel the operation. + * + * @param[in] mh melt request that failed + * @param ec error code to fail with + */ +static void +fail_mh (struct TALER_EXCHANGE_MeltHandle *mh, + enum TALER_ErrorCode ec) +{ + struct TALER_EXCHANGE_MeltResponse mr = { + .hr.ec = ec + }; + + mh->melt_cb (mh->melt_cb_cls, + &mr); + TALER_EXCHANGE_melt_cancel (mh); +} + + +/** + * Callbacks of this type are used to serve the result of submitting a + * /blinding-prepare request to a exchange. + * + * @param cls closure with our `struct TALER_EXCHANGE_MeltHandle *` + * @param bpr response details + */ +static void +blinding_prepare_cb (void *cls, + const struct TALER_EXCHANGE_BlindingPrepareResponse *bpr) +{ + struct TALER_EXCHANGE_MeltHandle *mh = cls; + unsigned int nks_off = 0; + + mh->bpr = NULL; + if (MHD_HTTP_OK != bpr->hr.http_status) + { + struct TALER_EXCHANGE_MeltResponse mr = { + .hr = bpr->hr + }; + + mr.hr.hint = "/blinding-prepare failed"; + mh->melt_cb (mh->melt_cb_cls, + &mr); + TALER_EXCHANGE_melt_cancel (mh); + return; + } + for (unsigned int i = 0; i<mh->rd->num_fresh_denom_pubs; i++) + { + const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk = + &mh->rd->fresh_denom_pubs[i]; + struct TALER_ExchangeBlindingValues *wv = &mh->melt_blinding_values[i]; + + switch (fresh_pk->key.bsign_pub_key->cipher) + { + case GNUNET_CRYPTO_BSA_INVALID: + GNUNET_break (0); + fail_mh (mh, + TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR); + return; + case GNUNET_CRYPTO_BSA_RSA: + break; + case GNUNET_CRYPTO_BSA_CS: + TALER_denom_ewv_copy (wv, + &bpr->details.ok.blinding_values[nks_off]); + nks_off++; + break; + } + } + if (GNUNET_OK != + start_melt (mh)) + { + GNUNET_break (0); + fail_mh (mh, + TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR); + return; + } +} + + +struct TALER_EXCHANGE_MeltHandle * +TALER_EXCHANGE_melt ( + struct GNUNET_CURL_Context *ctx, + const char *url, + struct TALER_EXCHANGE_Keys *keys, + const struct TALER_PublicRefreshMasterSeedP *rms, + const struct TALER_EXCHANGE_MeltInput *rd, + TALER_EXCHANGE_MeltCallback melt_cb, + void *melt_cb_cls) +{ + struct TALER_EXCHANGE_NonceKey nks[GNUNET_NZL (rd->num_fresh_denom_pubs)]; + unsigned int nks_off = 0; + struct TALER_EXCHANGE_MeltHandle *mh; + + if (0 == rd->num_fresh_denom_pubs) + { + GNUNET_break (0); + return NULL; + } + mh = GNUNET_new (struct TALER_EXCHANGE_MeltHandle); + mh->noreveal_index = TALER_CNC_KAPPA; /* invalid value */ + mh->cctx = ctx; + mh->exchange_url = GNUNET_strdup (url); + mh->rd = rd; + mh->rms = *rms; + mh->melt_cb = melt_cb; + mh->melt_cb_cls = melt_cb_cls; + mh->no_blinding_seed = true; + mh->melt_blinding_values = + GNUNET_new_array (rd->num_fresh_denom_pubs, + struct TALER_ExchangeBlindingValues); + for (unsigned int i = 0; i<rd->num_fresh_denom_pubs; i++) + { + const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk = + &rd->fresh_denom_pubs[i]; + + switch (fresh_pk->key.bsign_pub_key->cipher) + { + case GNUNET_CRYPTO_BSA_INVALID: + GNUNET_break (0); + GNUNET_free (mh->melt_blinding_values); + GNUNET_free (mh); + return NULL; + case GNUNET_CRYPTO_BSA_RSA: + TALER_denom_ewv_copy (&mh->melt_blinding_values[i], + TALER_denom_ewv_rsa_singleton ()); + break; + case GNUNET_CRYPTO_BSA_CS: + nks[nks_off].pk = fresh_pk; + nks[nks_off].cnc_num = i; + nks_off++; + break; + } + } + mh->keys = TALER_EXCHANGE_keys_incref (keys); + if (0 != nks_off) + { + mh->no_blinding_seed = false; + TALER_cs_refresh_seed_to_blinding_seed ( + rms, + &mh->md.melted_coin.coin_priv, + &mh->blinding_seed); + mh->bpr = TALER_EXCHANGE_blinding_prepare_for_melt (ctx, + url, + &mh->blinding_seed, + nks_off, + nks, + &blinding_prepare_cb, + mh); + if (NULL == mh->bpr) + { + GNUNET_break (0); + TALER_EXCHANGE_melt_cancel (mh); + return NULL; + } + return mh; + } + if (GNUNET_OK != + start_melt (mh)) + { + GNUNET_break (0); + TALER_EXCHANGE_melt_cancel (mh); + return NULL; + } + return mh; +} + + +void +TALER_EXCHANGE_melt_cancel (struct TALER_EXCHANGE_MeltHandle *mh) +{ + for (unsigned int i = 0; i<mh->rd->num_fresh_denom_pubs; i++) + TALER_denom_ewv_free (&mh->melt_blinding_values[i]); + if (NULL != mh->job) + { + GNUNET_CURL_job_cancel (mh->job); + mh->job = NULL; + } + if (NULL != mh->bpr) + { + TALER_EXCHANGE_blinding_prepare_cancel (mh->bpr); + mh->bpr = NULL; + } + TALER_EXCHANGE_free_melt_data (&mh->md); /* does not free 'md' itself */ + GNUNET_free (mh->melt_blinding_values); + GNUNET_free (mh->url); + GNUNET_free (mh->exchange_url); + TALER_curl_easy_post_finished (&mh->ctx); + TALER_EXCHANGE_keys_decref (mh->keys); + GNUNET_free (mh); +} + + +/* end of exchange_api_melt.c */ diff --git a/src/lib/exchange_api_post-purses-PURSE_PUB-create.c b/src/lib/exchange_api_post-purses-PURSE_PUB-create.c @@ -0,0 +1,656 @@ +/* + This file is part of TALER + Copyright (C) 2022-2023 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> + */ +/** + * @file lib/exchange_api_post-purses-PURSE_PUB-create.c + * @brief Implementation of the client to create a purse with + * an initial set of deposits (and a contract) + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_json_lib.h" +#include "taler/taler_exchange_service.h" +#include "exchange_api_handle.h" +#include "exchange_api_common.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * Information we track per deposited coin. + */ +struct Deposit +{ + /** + * Coin's public key. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + + /** + * Signature made with the coin. + */ + struct TALER_CoinSpendSignatureP coin_sig; + + /** + * Coin's denomination. + */ + struct TALER_DenominationHashP h_denom_pub; + + /** + * Age restriction hash for the coin. + */ + struct TALER_AgeCommitmentHashP ahac; + + /** + * How much did we say the coin contributed. + */ + struct TALER_Amount contribution; +}; + + +/** + * @brief A purse create with deposit handle + */ +struct TALER_EXCHANGE_PurseCreateDepositHandle +{ + + /** + * The keys of the exchange this request handle will use + */ + struct TALER_EXCHANGE_Keys *keys; + + /** + * The url for this request. + */ + char *url; + + /** + * The base URL of the exchange. + */ + char *exchange_url; + + /** + * Context for #TEH_curl_easy_post(). Keeps the data that must + * persist for Curl to make the upload. + */ + struct TALER_CURL_PostContext ctx; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_PurseCreateDepositCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Expected value in the purse after fees. + */ + struct TALER_Amount purse_value_after_fees; + + /** + * Our encrypted contract (if we had any). + */ + struct TALER_EncryptedContract econtract; + + /** + * Public key of the merge capability. + */ + struct TALER_PurseMergePublicKeyP merge_pub; + + /** + * Public key of the purse. + */ + struct TALER_PurseContractPublicKeyP purse_pub; + + /** + * Signature with the purse key on the request. + */ + struct TALER_PurseContractSignatureP purse_sig; + + /** + * Hash over the purse's contract terms. + */ + struct TALER_PrivateContractHashP h_contract_terms; + + /** + * When does the purse expire. + */ + struct GNUNET_TIME_Timestamp purse_expiration; + + /** + * Array of @e num_deposit deposits. + */ + struct Deposit *deposits; + + /** + * How many deposits did we make? + */ + unsigned int num_deposits; + +}; + + +/** + * Function called when we're done processing the + * HTTP /deposit request. + * + * @param cls the `struct TALER_EXCHANGE_DepositHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_purse_create_deposit_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_PurseCreateDepositHandle *pch = cls; + const json_t *j = response; + struct TALER_EXCHANGE_PurseCreateDepositResponse dr = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code + }; + const struct TALER_EXCHANGE_Keys *keys = pch->keys; + + pch->job = NULL; + switch (response_code) + { + case 0: + dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + { + struct GNUNET_TIME_Timestamp etime; + struct TALER_Amount total_deposited; + struct TALER_ExchangeSignatureP exchange_sig; + struct TALER_ExchangePublicKeyP exchange_pub; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &exchange_pub), + GNUNET_JSON_spec_timestamp ("exchange_timestamp", + &etime), + TALER_JSON_spec_amount ("total_deposited", + pch->purse_value_after_fees.currency, + &total_deposited), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (keys, + &exchange_pub)) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_EXCHANGE_PURSE_CREATE_EXCHANGE_SIGNATURE_INVALID; + break; + } + if (GNUNET_OK != + TALER_exchange_online_purse_created_verify ( + etime, + pch->purse_expiration, + &pch->purse_value_after_fees, + &total_deposited, + &pch->purse_pub, + &pch->h_contract_terms, + &exchange_pub, + &exchange_sig)) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_EXCHANGE_PURSE_CREATE_EXCHANGE_SIGNATURE_INVALID; + break; + } + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_FORBIDDEN: + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + /* Nothing really to verify, exchange says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + break; + case MHD_HTTP_CONFLICT: + { + dr.hr.ec = TALER_JSON_get_error_code (j); + switch (dr.hr.ec) + { + case TALER_EC_EXCHANGE_PURSE_CREATE_CONFLICTING_META_DATA: + if (GNUNET_OK != + TALER_EXCHANGE_check_purse_create_conflict_ ( + &pch->purse_sig, + &pch->purse_pub, + j)) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + break; + case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS: + /* Nothing to check anymore here, proof needs to be + checked in the GET /coins/$COIN_PUB handler */ + break; + case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY: + // FIXME #7267: write check (add to exchange_api_common! */ + break; + case TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA: + { + struct TALER_CoinSpendPublicKeyP coin_pub; + struct TALER_CoinSpendSignatureP coin_sig; + struct TALER_DenominationHashP h_denom_pub; + struct TALER_AgeCommitmentHashP phac; + bool found = false; + + if (GNUNET_OK != + TALER_EXCHANGE_check_purse_coin_conflict_ ( + &pch->purse_pub, + pch->exchange_url, + j, + &h_denom_pub, + &phac, + &coin_pub, + &coin_sig)) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + for (unsigned int i = 0; i<pch->num_deposits; i++) + { + struct Deposit *deposit = &pch->deposits[i]; + + if (0 != + GNUNET_memcmp (&coin_pub, + &deposit->coin_pub)) + continue; + if (0 != + GNUNET_memcmp (&deposit->h_denom_pub, + &h_denom_pub)) + { + found = true; + break; + } + if (0 != + GNUNET_memcmp (&deposit->ahac, + &phac)) + { + found = true; + break; + } + if (0 == + GNUNET_memcmp (&coin_sig, + &deposit->coin_sig)) + { + GNUNET_break_op (0); + continue; + } + found = true; + break; + } + if (! found) + { + /* conflict is for a different coin! */ + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + } + case TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA: + if (GNUNET_OK != + TALER_EXCHANGE_check_purse_econtract_conflict_ ( + &pch->econtract.econtract_sig, + &pch->purse_pub, + j)) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected error code %d for conflcting deposit\n", + dr.hr.ec); + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + } + } + break; + case MHD_HTTP_GONE: + /* could happen if denomination was revoked */ + /* Note: one might want to check /keys for revocation + signature here, alas tricky in case our /keys + is outdated => left to clients */ + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange deposit\n", + (unsigned int) response_code, + dr.hr.ec); + GNUNET_break_op (0); + break; + } + pch->cb (pch->cb_cls, + &dr); + TALER_EXCHANGE_purse_create_with_deposit_cancel (pch); +} + + +struct TALER_EXCHANGE_PurseCreateDepositHandle * +TALER_EXCHANGE_purse_create_with_deposit ( + struct GNUNET_CURL_Context *ctx, + const char *url, + struct TALER_EXCHANGE_Keys *keys, + const struct TALER_PurseContractPrivateKeyP *purse_priv, + const struct TALER_PurseMergePrivateKeyP *merge_priv, + const struct TALER_ContractDiffiePrivateP *contract_priv, + const json_t *contract_terms, + unsigned int num_deposits, + const struct TALER_EXCHANGE_PurseDeposit deposits[static num_deposits], + bool upload_contract, + TALER_EXCHANGE_PurseCreateDepositCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_PurseCreateDepositHandle *pch; + json_t *create_obj; + json_t *deposit_arr; + CURL *eh; + char arg_str[sizeof (pch->purse_pub) * 2 + 32]; + uint32_t min_age = 0; + + pch = GNUNET_new (struct TALER_EXCHANGE_PurseCreateDepositHandle); + pch->cb = cb; + pch->cb_cls = cb_cls; + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_timestamp ("pay_deadline", + &pch->purse_expiration), + TALER_JSON_spec_amount_any ("amount", + &pch->purse_value_after_fees), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("minimum_age", + &min_age), + NULL), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (contract_terms, + spec, + NULL, NULL)) + { + GNUNET_break (0); + return NULL; + } + } + if (GNUNET_OK != + TALER_JSON_contract_hash (contract_terms, + &pch->h_contract_terms)) + { + GNUNET_break (0); + return NULL; + } + GNUNET_CRYPTO_eddsa_key_get_public (&purse_priv->eddsa_priv, + &pch->purse_pub.eddsa_pub); + { + char pub_str[sizeof (pch->purse_pub) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string ( + &pch->purse_pub, + sizeof (pch->purse_pub), + pub_str, + sizeof (pub_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "purses/%s/create", + pub_str); + } + GNUNET_CRYPTO_eddsa_key_get_public (&merge_priv->eddsa_priv, + &pch->merge_pub.eddsa_pub); + pch->url = TALER_url_join (url, + arg_str, + NULL); + if (NULL == pch->url) + { + GNUNET_break (0); + GNUNET_free (pch); + return NULL; + } + pch->num_deposits = num_deposits; + pch->deposits = GNUNET_new_array (num_deposits, + struct Deposit); + deposit_arr = json_array (); + GNUNET_assert (NULL != deposit_arr); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Signing with URL `%s'\n", + url); + for (unsigned int i = 0; i<num_deposits; i++) + { + const struct TALER_EXCHANGE_PurseDeposit *deposit = &deposits[i]; + const struct TALER_AgeCommitmentProof *acp = deposit->age_commitment_proof; + struct Deposit *d = &pch->deposits[i]; + json_t *jdeposit; + struct TALER_AgeCommitmentHashP *aghp = NULL; + struct TALER_AgeAttestationP attest; + struct TALER_AgeAttestationP *attestp = NULL; + + if (NULL != acp) + { + TALER_age_commitment_hash (&acp->commitment, + &d->ahac); + aghp = &d->ahac; + if (GNUNET_OK != + TALER_age_commitment_attest (acp, + min_age, + &attest)) + { + GNUNET_break (0); + GNUNET_array_grow (pch->deposits, + pch->num_deposits, + 0); + GNUNET_free (pch->url); + json_decref (deposit_arr); + GNUNET_free (pch); + return NULL; + } + } + d->contribution = deposit->amount; + d->h_denom_pub = deposit->h_denom_pub; + GNUNET_CRYPTO_eddsa_key_get_public (&deposit->coin_priv.eddsa_priv, + &d->coin_pub.eddsa_pub); + TALER_wallet_purse_deposit_sign ( + url, + &pch->purse_pub, + &deposit->amount, + &d->h_denom_pub, + &d->ahac, + &deposit->coin_priv, + &d->coin_sig); + jdeposit = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_data_auto ("h_age_commitment", + aghp)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_data_auto ("age_attestation", + attestp)), + TALER_JSON_pack_amount ("amount", + &deposit->amount), + GNUNET_JSON_pack_data_auto ("denom_pub_hash", + &deposit->h_denom_pub), + TALER_JSON_pack_denom_sig ("ub_sig", + &deposit->denom_sig), + GNUNET_JSON_pack_data_auto ("coin_sig", + &d->coin_sig), + GNUNET_JSON_pack_data_auto ("coin_pub", + &d->coin_pub)); + GNUNET_assert (0 == + json_array_append_new (deposit_arr, + jdeposit)); + } + TALER_wallet_purse_create_sign (pch->purse_expiration, + &pch->h_contract_terms, + &pch->merge_pub, + min_age, + &pch->purse_value_after_fees, + purse_priv, + &pch->purse_sig); + if (upload_contract) + { + TALER_CRYPTO_contract_encrypt_for_merge (&pch->purse_pub, + contract_priv, + merge_priv, + contract_terms, + &pch->econtract.econtract, + &pch->econtract.econtract_size); + GNUNET_CRYPTO_ecdhe_key_get_public (&contract_priv->ecdhe_priv, + &pch->econtract.contract_pub.ecdhe_pub); + TALER_wallet_econtract_upload_sign (pch->econtract.econtract, + pch->econtract.econtract_size, + &pch->econtract.contract_pub, + purse_priv, + &pch->econtract.econtract_sig); + } + create_obj = GNUNET_JSON_PACK ( + TALER_JSON_pack_amount ("amount", + &pch->purse_value_after_fees), + GNUNET_JSON_pack_uint64 ("min_age", + min_age), + GNUNET_JSON_pack_allow_null ( + TALER_JSON_pack_econtract ("econtract", + upload_contract + ? &pch->econtract + : NULL)), + GNUNET_JSON_pack_data_auto ("purse_sig", + &pch->purse_sig), + GNUNET_JSON_pack_data_auto ("merge_pub", + &pch->merge_pub), + GNUNET_JSON_pack_data_auto ("h_contract_terms", + &pch->h_contract_terms), + GNUNET_JSON_pack_timestamp ("purse_expiration", + pch->purse_expiration), + GNUNET_JSON_pack_array_steal ("deposits", + deposit_arr)); + GNUNET_assert (NULL != create_obj); + eh = TALER_EXCHANGE_curl_easy_get_ (pch->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&pch->ctx, + eh, + create_obj)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (create_obj); + GNUNET_free (pch->econtract.econtract); + GNUNET_array_grow (pch->deposits, + pch->num_deposits, + 0); + GNUNET_free (pch->url); + GNUNET_free (pch); + return NULL; + } + json_decref (create_obj); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "URL for purse create with deposit: `%s'\n", + pch->url); + pch->keys = TALER_EXCHANGE_keys_incref (keys); + pch->exchange_url = GNUNET_strdup (url); + pch->job = GNUNET_CURL_job_add2 (ctx, + eh, + pch->ctx.headers, + &handle_purse_create_deposit_finished, + pch); + return pch; +} + + +void +TALER_EXCHANGE_purse_create_with_deposit_cancel ( + struct TALER_EXCHANGE_PurseCreateDepositHandle *pch) +{ + if (NULL != pch->job) + { + GNUNET_CURL_job_cancel (pch->job); + pch->job = NULL; + } + GNUNET_free (pch->econtract.econtract); + GNUNET_free (pch->exchange_url); + GNUNET_free (pch->url); + GNUNET_array_grow (pch->deposits, + pch->num_deposits, + 0); + TALER_EXCHANGE_keys_decref (pch->keys); + TALER_curl_easy_post_finished (&pch->ctx); + GNUNET_free (pch); +} + + +/* end of exchange_api_purse_create_with_deposit.c */ diff --git a/src/lib/exchange_api_post-purses-PURSE_PUB-deposit.c b/src/lib/exchange_api_post-purses-PURSE_PUB-deposit.c @@ -0,0 +1,520 @@ +/* + This file is part of TALER + Copyright (C) 2022-2023 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> + */ +/** + * @file lib/exchange_api_post-purses-PURSE_PUB-deposit.c + * @brief Implementation of the client to create a purse with + * an initial set of deposits (and a contract) + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_json_lib.h" +#include "taler/taler_exchange_service.h" +#include "exchange_api_common.h" +#include "exchange_api_handle.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * Information we track per coin. + */ +struct Coin +{ + /** + * Coin's public key. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + + /** + * Signature made with the coin. + */ + struct TALER_CoinSpendSignatureP coin_sig; + + /** + * Coin's denomination. + */ + struct TALER_DenominationHashP h_denom_pub; + + /** + * Age restriction hash for the coin. + */ + struct TALER_AgeCommitmentHashP ahac; + + /** + * How much did we say the coin contributed. + */ + struct TALER_Amount contribution; +}; + + +/** + * @brief A purse create with deposit handle + */ +struct TALER_EXCHANGE_PurseDepositHandle +{ + + /** + * The keys of the exchange this request handle will use + */ + struct TALER_EXCHANGE_Keys *keys; + + /** + * The url for this request. + */ + char *url; + + /** + * The base url of the exchange we are talking to. + */ + char *base_url; + + /** + * Context for #TEH_curl_easy_post(). Keeps the data that must + * persist for Curl to make the upload. + */ + struct TALER_CURL_PostContext ctx; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_PurseDepositCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Public key of the purse. + */ + struct TALER_PurseContractPublicKeyP purse_pub; + + /** + * Array of @e num_deposits coins we are depositing. + */ + struct Coin *coins; + + /** + * Number of coins we are depositing. + */ + unsigned int num_deposits; +}; + + +/** + * Function called when we're done processing the + * HTTP /purses/$PID/deposit request. + * + * @param cls the `struct TALER_EXCHANGE_PurseDepositHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_purse_deposit_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_PurseDepositHandle *pch = cls; + const json_t *j = response; + struct TALER_EXCHANGE_PurseDepositResponse dr = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code + }; + const struct TALER_EXCHANGE_Keys *keys = pch->keys; + + pch->job = NULL; + switch (response_code) + { + case 0: + dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + { + struct GNUNET_TIME_Timestamp etime; + struct TALER_ExchangeSignatureP exchange_sig; + struct TALER_ExchangePublicKeyP exchange_pub; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &exchange_pub), + GNUNET_JSON_spec_fixed_auto ("h_contract_terms", + &dr.details.ok.h_contract_terms), + GNUNET_JSON_spec_timestamp ("exchange_timestamp", + &etime), + GNUNET_JSON_spec_timestamp ("purse_expiration", + &dr.details.ok.purse_expiration), + TALER_JSON_spec_amount ("total_deposited", + keys->currency, + &dr.details.ok.total_deposited), + TALER_JSON_spec_amount ("purse_value_after_fees", + keys->currency, + &dr.details.ok.purse_value_after_fees), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (keys, + &exchange_pub)) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_EXCHANGE_PURSE_DEPOSIT_EXCHANGE_SIGNATURE_INVALID; + break; + } + if (GNUNET_OK != + TALER_exchange_online_purse_created_verify ( + etime, + dr.details.ok.purse_expiration, + &dr.details.ok.purse_value_after_fees, + &dr.details.ok.total_deposited, + &pch->purse_pub, + &dr.details.ok.h_contract_terms, + &exchange_pub, + &exchange_sig)) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_EXCHANGE_PURSE_DEPOSIT_EXCHANGE_SIGNATURE_INVALID; + break; + } + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + dr.hr.ec = TALER_JSON_get_error_code (j); + break; + case MHD_HTTP_FORBIDDEN: + dr.hr.ec = TALER_JSON_get_error_code (j); + /* Nothing really to verify, exchange says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + dr.hr.ec = TALER_JSON_get_error_code (j); + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + break; + case MHD_HTTP_CONFLICT: + dr.hr.ec = TALER_JSON_get_error_code (j); + switch (dr.hr.ec) + { + case TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA: + { + struct TALER_CoinSpendPublicKeyP coin_pub; + struct TALER_CoinSpendSignatureP coin_sig; + struct TALER_DenominationHashP h_denom_pub; + struct TALER_AgeCommitmentHashP phac; + bool found = false; + + if (GNUNET_OK != + TALER_EXCHANGE_check_purse_coin_conflict_ ( + &pch->purse_pub, + pch->base_url, + j, + &h_denom_pub, + &phac, + &coin_pub, + &coin_sig)) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + for (unsigned int i = 0; i<pch->num_deposits; i++) + { + struct Coin *coin = &pch->coins[i]; + if (0 != GNUNET_memcmp (&coin_pub, + &coin->coin_pub)) + continue; + if (0 != + GNUNET_memcmp (&coin->h_denom_pub, + &h_denom_pub)) + { + found = true; + break; + } + if (0 != + GNUNET_memcmp (&coin->ahac, + &phac)) + { + found = true; + break; + } + if (0 == GNUNET_memcmp (&coin_sig, + &coin->coin_sig)) + { + /* identical signature => not a conflict */ + continue; + } + found = true; + break; + } + if (! found) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + /* meta data conflict is real! */ + break; + } + case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS: + /* Nothing to check anymore here, proof needs to be + checked in the GET /coins/$COIN_PUB handler */ + break; + case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY: + break; + default: + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } /* ec switch */ + break; + case MHD_HTTP_GONE: + /* could happen if denomination was revoked or purse expired */ + /* Note: one might want to check /keys for revocation + signature here, alas tricky in case our /keys + is outdated => left to clients */ + dr.hr.ec = TALER_JSON_get_error_code (j); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + dr.hr.ec = TALER_JSON_get_error_code (j); + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + dr.hr.ec = TALER_JSON_get_error_code (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange deposit\n", + (unsigned int) response_code, + dr.hr.ec); + GNUNET_break_op (0); + break; + } + if (TALER_EC_NONE == dr.hr.ec) + dr.hr.hint = NULL; + else + dr.hr.hint = TALER_ErrorCode_get_hint (dr.hr.ec); + pch->cb (pch->cb_cls, + &dr); + TALER_EXCHANGE_purse_deposit_cancel (pch); +} + + +struct TALER_EXCHANGE_PurseDepositHandle * +TALER_EXCHANGE_purse_deposit ( + struct GNUNET_CURL_Context *ctx, + const char *url, + struct TALER_EXCHANGE_Keys *keys, + const char *purse_exchange_url, + const struct TALER_PurseContractPublicKeyP *purse_pub, + uint8_t min_age, + unsigned int num_deposits, + const struct TALER_EXCHANGE_PurseDeposit deposits[static num_deposits], + TALER_EXCHANGE_PurseDepositCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_PurseDepositHandle *pch; + json_t *create_obj; + json_t *deposit_arr; + CURL *eh; + char arg_str[sizeof (pch->purse_pub) * 2 + 32]; + + // FIXME: use purse_exchange_url for wad transfers (#7271) + (void) purse_exchange_url; + if (0 == num_deposits) + { + GNUNET_break (0); + return NULL; + } + pch = GNUNET_new (struct TALER_EXCHANGE_PurseDepositHandle); + pch->purse_pub = *purse_pub; + pch->cb = cb; + pch->cb_cls = cb_cls; + { + char pub_str[sizeof (pch->purse_pub) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string ( + &pch->purse_pub, + sizeof (pch->purse_pub), + pub_str, + sizeof (pub_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "purses/%s/deposit", + pub_str); + } + pch->url = TALER_url_join (url, + arg_str, + NULL); + if (NULL == pch->url) + { + GNUNET_break (0); + GNUNET_free (pch); + return NULL; + } + deposit_arr = json_array (); + GNUNET_assert (NULL != deposit_arr); + pch->base_url = GNUNET_strdup (url); + pch->num_deposits = num_deposits; + pch->coins = GNUNET_new_array (num_deposits, + struct Coin); + for (unsigned int i = 0; i<num_deposits; i++) + { + const struct TALER_EXCHANGE_PurseDeposit *deposit = &deposits[i]; + const struct TALER_AgeCommitmentProof *acp = deposit->age_commitment_proof; + struct Coin *coin = &pch->coins[i]; + json_t *jdeposit; + struct TALER_AgeCommitmentHashP *achp = NULL; + struct TALER_AgeAttestationP attest; + struct TALER_AgeAttestationP *attestp = NULL; + + if (NULL != acp) + { + TALER_age_commitment_hash (&acp->commitment, + &coin->ahac); + achp = &coin->ahac; + if (GNUNET_OK != + TALER_age_commitment_attest (acp, + min_age, + &attest)) + { + GNUNET_break (0); + json_decref (deposit_arr); + GNUNET_free (pch->base_url); + GNUNET_free (pch->coins); + GNUNET_free (pch->url); + GNUNET_free (pch); + return NULL; + } + attestp = &attest; + } + GNUNET_CRYPTO_eddsa_key_get_public (&deposit->coin_priv.eddsa_priv, + &coin->coin_pub.eddsa_pub); + coin->h_denom_pub = deposit->h_denom_pub; + coin->contribution = deposit->amount; + TALER_wallet_purse_deposit_sign ( + pch->base_url, + &pch->purse_pub, + &deposit->amount, + &coin->h_denom_pub, + &coin->ahac, + &deposit->coin_priv, + &coin->coin_sig); + jdeposit = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_data_auto ("h_age_commitment", + achp)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_data_auto ("age_attestation", + attestp)), + TALER_JSON_pack_amount ("amount", + &deposit->amount), + GNUNET_JSON_pack_data_auto ("denom_pub_hash", + &deposit->h_denom_pub), + TALER_JSON_pack_denom_sig ("ub_sig", + &deposit->denom_sig), + GNUNET_JSON_pack_data_auto ("coin_pub", + &coin->coin_pub), + GNUNET_JSON_pack_data_auto ("coin_sig", + &coin->coin_sig)); + GNUNET_assert (0 == + json_array_append_new (deposit_arr, + jdeposit)); + } + create_obj = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_array_steal ("deposits", + deposit_arr)); + GNUNET_assert (NULL != create_obj); + eh = TALER_EXCHANGE_curl_easy_get_ (pch->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&pch->ctx, + eh, + create_obj)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (create_obj); + GNUNET_free (pch->base_url); + GNUNET_free (pch->url); + GNUNET_free (pch->coins); + GNUNET_free (pch); + return NULL; + } + json_decref (create_obj); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "URL for purse deposit: `%s'\n", + pch->url); + pch->keys = TALER_EXCHANGE_keys_incref (keys); + pch->job = GNUNET_CURL_job_add2 (ctx, + eh, + pch->ctx.headers, + &handle_purse_deposit_finished, + pch); + return pch; +} + + +void +TALER_EXCHANGE_purse_deposit_cancel ( + struct TALER_EXCHANGE_PurseDepositHandle *pch) +{ + if (NULL != pch->job) + { + GNUNET_CURL_job_cancel (pch->job); + pch->job = NULL; + } + GNUNET_free (pch->base_url); + GNUNET_free (pch->url); + GNUNET_free (pch->coins); + TALER_EXCHANGE_keys_decref (pch->keys); + TALER_curl_easy_post_finished (&pch->ctx); + GNUNET_free (pch); +} + + +/* end of exchange_api_purse_deposit.c */ diff --git a/src/lib/exchange_api_post-purses-PURSE_PUB-merge.c b/src/lib/exchange_api_post-purses-PURSE_PUB-merge.c @@ -0,0 +1,454 @@ +/* + This file is part of TALER + Copyright (C) 2022-2023 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> + */ +/** + * @file lib/exchange_api_post-purses-PURSE_PUB-merge.c + * @brief Implementation of the client to merge a purse + * into an account + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_json_lib.h" +#include "taler/taler_exchange_service.h" +#include "exchange_api_handle.h" +#include "exchange_api_common.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A purse merge with deposit handle + */ +struct TALER_EXCHANGE_AccountMergeHandle +{ + + /** + * The keys of the exchange this request handle will use + */ + struct TALER_EXCHANGE_Keys *keys; + + /** + * The url for this request. + */ + char *url; + + /** + * Context for #TEH_curl_easy_post(). Keeps the data that must + * persist for Curl to make the upload. + */ + struct TALER_CURL_PostContext ctx; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_AccountMergeCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Base URL of the provider hosting the @e reserve_pub. + */ + char *provider_url; + + /** + * Signature for our operation. + */ + struct TALER_PurseMergeSignatureP merge_sig; + + /** + * Expected value in the purse after fees. + */ + struct TALER_Amount purse_value_after_fees; + + /** + * Public key of the reserve public key. + */ + struct TALER_ReservePublicKeyP reserve_pub; + + /** + * Public key of the purse. + */ + struct TALER_PurseContractPublicKeyP purse_pub; + + /** + * Hash over the purse's contrac terms. + */ + struct TALER_PrivateContractHashP h_contract_terms; + + /** + * When does the purse expire. + */ + struct GNUNET_TIME_Timestamp purse_expiration; + + /** + * Our merge key. + */ + struct TALER_PurseMergePrivateKeyP merge_priv; + + /** + * Reserve signature affirming the merge. + */ + struct TALER_ReserveSignatureP reserve_sig; + +}; + + +/** + * Function called when we're done processing the + * HTTP /purse/$PID/merge request. + * + * @param cls the `struct TALER_EXCHANGE_AccountMergeHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_purse_merge_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_AccountMergeHandle *pch = cls; + const json_t *j = response; + struct TALER_EXCHANGE_AccountMergeResponse dr = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code, + .reserve_sig = &pch->reserve_sig + }; + + pch->job = NULL; + switch (response_code) + { + case 0: + dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + { + struct TALER_Amount total_deposited; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &dr.details.ok.exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &dr.details.ok.exchange_pub), + GNUNET_JSON_spec_timestamp ("exchange_timestamp", + &dr.details.ok.etime), + TALER_JSON_spec_amount ("merge_amount", + pch->purse_value_after_fees.currency, + &total_deposited), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (pch->keys, + &dr.details.ok.exchange_pub)) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_EXCHANGE_PURSE_MERGE_EXCHANGE_SIGNATURE_INVALID; + break; + } + if (GNUNET_OK != + TALER_exchange_online_purse_merged_verify ( + dr.details.ok.etime, + pch->purse_expiration, + &pch->purse_value_after_fees, + &pch->purse_pub, + &pch->h_contract_terms, + &pch->reserve_pub, + pch->provider_url, + &dr.details.ok.exchange_pub, + &dr.details.ok.exchange_sig)) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_EXCHANGE_PURSE_MERGE_EXCHANGE_SIGNATURE_INVALID; + break; + } + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_PAYMENT_REQUIRED: + /* purse was not (yet) full */ + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_FORBIDDEN: + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + /* Nothing really to verify, exchange says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + break; + case MHD_HTTP_CONFLICT: + { + struct TALER_PurseMergePublicKeyP merge_pub; + + GNUNET_CRYPTO_eddsa_key_get_public (&pch->merge_priv.eddsa_priv, + &merge_pub.eddsa_pub); + if (GNUNET_OK != + TALER_EXCHANGE_check_purse_merge_conflict_ ( + &pch->merge_sig, + &merge_pub, + &pch->purse_pub, + pch->provider_url, + j)) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + break; + } + break; + case MHD_HTTP_GONE: + /* could happen if denomination was revoked */ + /* Note: one might want to check /keys for revocation + signature here, alas tricky in case our /keys + is outdated => left to clients */ + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_uint64 ( + "requirement_row", + &dr.details.unavailable_for_legal_reasons.requirement_row), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + } + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange deposit\n", + (unsigned int) response_code, + dr.hr.ec); + GNUNET_break_op (0); + break; + } + pch->cb (pch->cb_cls, + &dr); + TALER_EXCHANGE_account_merge_cancel (pch); +} + + +struct TALER_EXCHANGE_AccountMergeHandle * +TALER_EXCHANGE_account_merge ( + struct GNUNET_CURL_Context *ctx, + const char *url, + struct TALER_EXCHANGE_Keys *keys, + const char *reserve_exchange_url, + const struct TALER_ReservePrivateKeyP *reserve_priv, + const struct TALER_PurseContractPublicKeyP *purse_pub, + const struct TALER_PurseMergePrivateKeyP *merge_priv, + const struct TALER_PrivateContractHashP *h_contract_terms, + uint8_t min_age, + const struct TALER_Amount *purse_value_after_fees, + struct GNUNET_TIME_Timestamp purse_expiration, + struct GNUNET_TIME_Timestamp merge_timestamp, + TALER_EXCHANGE_AccountMergeCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_AccountMergeHandle *pch; + json_t *merge_obj; + CURL *eh; + char arg_str[sizeof (pch->purse_pub) * 2 + 32]; + struct TALER_NormalizedPayto reserve_url; + + pch = GNUNET_new (struct TALER_EXCHANGE_AccountMergeHandle); + pch->merge_priv = *merge_priv; + pch->cb = cb; + pch->cb_cls = cb_cls; + pch->purse_pub = *purse_pub; + pch->h_contract_terms = *h_contract_terms; + pch->purse_expiration = purse_expiration; + pch->purse_value_after_fees = *purse_value_after_fees; + if (NULL == reserve_exchange_url) + pch->provider_url = GNUNET_strdup (url); + else + pch->provider_url = GNUNET_strdup (reserve_exchange_url); + GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, + &pch->reserve_pub.eddsa_pub); + + { + char pub_str[sizeof (*purse_pub) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string ( + purse_pub, + sizeof (*purse_pub), + pub_str, + sizeof (pub_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "purses/%s/merge", + pub_str); + } + reserve_url = TALER_reserve_make_payto (pch->provider_url, + &pch->reserve_pub); + if (NULL == reserve_url.normalized_payto) + { + GNUNET_break (0); + GNUNET_free (pch->provider_url); + GNUNET_free (pch); + return NULL; + } + pch->url = TALER_url_join (url, + arg_str, + NULL); + if (NULL == pch->url) + { + GNUNET_break (0); + GNUNET_free (reserve_url.normalized_payto); + GNUNET_free (pch->provider_url); + GNUNET_free (pch); + return NULL; + } + TALER_wallet_purse_merge_sign (reserve_url, + merge_timestamp, + purse_pub, + merge_priv, + &pch->merge_sig); + { + struct TALER_Amount zero_purse_fee; + + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (purse_value_after_fees->currency, + &zero_purse_fee)); + TALER_wallet_account_merge_sign (merge_timestamp, + purse_pub, + purse_expiration, + h_contract_terms, + purse_value_after_fees, + &zero_purse_fee, + min_age, + TALER_WAMF_MODE_MERGE_FULLY_PAID_PURSE, + reserve_priv, + &pch->reserve_sig); + } + merge_obj = GNUNET_JSON_PACK ( + TALER_JSON_pack_normalized_payto ("payto_uri", + reserve_url), + GNUNET_JSON_pack_data_auto ("merge_sig", + &pch->merge_sig), + GNUNET_JSON_pack_data_auto ("reserve_sig", + &pch->reserve_sig), + GNUNET_JSON_pack_timestamp ("merge_timestamp", + merge_timestamp)); + GNUNET_assert (NULL != merge_obj); + GNUNET_free (reserve_url.normalized_payto); + eh = TALER_EXCHANGE_curl_easy_get_ (pch->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&pch->ctx, + eh, + merge_obj)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (merge_obj); + GNUNET_free (pch->provider_url); + GNUNET_free (pch->url); + GNUNET_free (pch); + return NULL; + } + json_decref (merge_obj); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "URL for purse merge: `%s'\n", + pch->url); + pch->keys = TALER_EXCHANGE_keys_incref (keys); + pch->job = GNUNET_CURL_job_add2 (ctx, + eh, + pch->ctx.headers, + &handle_purse_merge_finished, + pch); + return pch; +} + + +void +TALER_EXCHANGE_account_merge_cancel ( + struct TALER_EXCHANGE_AccountMergeHandle *pch) +{ + if (NULL != pch->job) + { + GNUNET_CURL_job_cancel (pch->job); + pch->job = NULL; + } + GNUNET_free (pch->url); + GNUNET_free (pch->provider_url); + TALER_curl_easy_post_finished (&pch->ctx); + TALER_EXCHANGE_keys_decref (pch->keys); + GNUNET_free (pch); +} + + +/* end of exchange_api_purse_merge.c */ diff --git a/src/lib/exchange_api_post-recoup-refresh.c b/src/lib/exchange_api_post-recoup-refresh.c @@ -0,0 +1,361 @@ +/* + This file is part of TALER + Copyright (C) 2017-2023 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_post-recoup-refresh.c + * @brief Implementation of the /recoup-refresh request of the exchange's HTTP API + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_json_lib.h" +#include "taler/taler_exchange_service.h" +#include "exchange_api_common.h" +#include "exchange_api_handle.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A Recoup Handle + */ +struct TALER_EXCHANGE_RecoupRefreshHandle +{ + + /** + * The keys of the exchange this request handle will use + */ + struct TALER_EXCHANGE_Keys *keys; + + /** + * The url for this request. + */ + char *url; + + /** + * Context for #TEH_curl_easy_post(). Keeps the data that must + * persist for Curl to make the upload. + */ + struct TALER_CURL_PostContext ctx; + + /** + * Denomination key of the coin. + */ + struct TALER_EXCHANGE_DenomPublicKey pk; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_RecoupRefreshResultCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Public key of the coin we are trying to get paid back. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + + /** + * Signature affirming the recoup-refresh operation. + */ + struct TALER_CoinSpendSignatureP coin_sig; + +}; + + +/** + * Parse a recoup-refresh response. If it is valid, call the callback. + * + * @param ph recoup handle + * @param json json reply with the signature + * @return #GNUNET_OK if the signature is valid and we called the callback; + * #GNUNET_SYSERR if not (callback must still be called) + */ +static enum GNUNET_GenericReturnValue +process_recoup_response ( + const struct TALER_EXCHANGE_RecoupRefreshHandle *ph, + const json_t *json) +{ + struct TALER_EXCHANGE_RecoupRefreshResponse rrr = { + .hr.reply = json, + .hr.http_status = MHD_HTTP_OK + }; + struct GNUNET_JSON_Specification spec_refresh[] = { + GNUNET_JSON_spec_fixed_auto ("old_coin_pub", + &rrr.details.ok.old_coin_pub), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec_refresh, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + ph->cb (ph->cb_cls, + &rrr); + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /recoup-refresh request. + * + * @param cls the `struct TALER_EXCHANGE_RecoupRefreshHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_recoup_refresh_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_RecoupRefreshHandle *ph = cls; + const json_t *j = response; + struct TALER_EXCHANGE_RecoupRefreshResponse rrr = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code + }; + + ph->job = NULL; + switch (response_code) + { + case 0: + rrr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + process_recoup_response (ph, + j)) + { + GNUNET_break_op (0); + rrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + rrr.hr.http_status = 0; + break; + } + TALER_EXCHANGE_recoup_refresh_cancel (ph); + return; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + rrr.hr.ec = TALER_JSON_get_error_code (j); + rrr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_FORBIDDEN: + /* Nothing really to verify, exchange says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + rrr.hr.ec = TALER_JSON_get_error_code (j); + rrr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + rrr.hr.ec = TALER_JSON_get_error_code (j); + rrr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_CONFLICT: + rrr.hr.ec = TALER_JSON_get_error_code (j); + rrr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_GONE: + /* Kind of normal: the money was already sent to the merchant + (it was too late for the refund). */ + rrr.hr.ec = TALER_JSON_get_error_code (j); + rrr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + rrr.hr.ec = TALER_JSON_get_error_code (j); + rrr.hr.hint = TALER_JSON_get_error_hint (j); + break; + default: + /* unexpected response code */ + rrr.hr.ec = TALER_JSON_get_error_code (j); + rrr.hr.hint = TALER_JSON_get_error_hint (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange recoup\n", + (unsigned int) response_code, + (int) rrr.hr.ec); + GNUNET_break (0); + break; + } + ph->cb (ph->cb_cls, + &rrr); + TALER_EXCHANGE_recoup_refresh_cancel (ph); +} + + +#pragma message "TODO[oec]: rewrite TALER_EXCHANGE_RecoupRefreshHandle" +struct TALER_EXCHANGE_RecoupRefreshHandle * +TALER_EXCHANGE_recoup_refresh ( + struct GNUNET_CURL_Context *ctx, + const char *url, + struct TALER_EXCHANGE_Keys *keys, + const struct TALER_EXCHANGE_DenomPublicKey *pk, + const struct TALER_DenominationSignature *denom_sig, + const struct TALER_ExchangeBlindingValues *blinding_values, + const struct TALER_PublicRefreshMasterSeedP *rms, + const struct TALER_PlanchetMasterSecretP *ps, + unsigned int idx, + TALER_EXCHANGE_RecoupRefreshResultCallback recoup_cb, + void *recoup_cb_cls) +{ + struct TALER_EXCHANGE_RecoupRefreshHandle *ph; + struct TALER_DenominationHashP h_denom_pub; + json_t *recoup_obj; + CURL *eh; + char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32]; + struct TALER_CoinSpendPrivateKeyP coin_priv; + union GNUNET_CRYPTO_BlindingSecretP bks; + + GNUNET_assert (NULL != recoup_cb); + ph = GNUNET_new (struct TALER_EXCHANGE_RecoupRefreshHandle); + ph->pk = *pk; + memset (&ph->pk.key, + 0, + sizeof (ph->pk.key)); /* zero out, as lifetime cannot be warranted */ + ph->cb = recoup_cb; + ph->cb_cls = recoup_cb_cls; + TALER_planchet_setup_coin_priv (ps, + blinding_values, + &coin_priv); + TALER_planchet_blinding_secret_create (ps, + blinding_values, + &bks); + GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv, + &ph->coin_pub.eddsa_pub); + TALER_denom_pub_hash (&pk->key, + &h_denom_pub); + TALER_wallet_recoup_refresh_sign (&h_denom_pub, + &bks, + &coin_priv, + &ph->coin_sig); + recoup_obj = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("denom_pub_hash", + &h_denom_pub), + TALER_JSON_pack_denom_sig ("denom_sig", + denom_sig), + TALER_JSON_pack_exchange_blinding_values ("ewv", + blinding_values), + GNUNET_JSON_pack_data_auto ("coin_sig", + &ph->coin_sig), + GNUNET_JSON_pack_data_auto ("coin_blind_key_secret", + &bks)); + + switch (denom_sig->unblinded_sig->cipher) + { + case GNUNET_CRYPTO_BSA_INVALID: + json_decref (recoup_obj); + GNUNET_break (0); + GNUNET_free (ph); + return NULL; + case GNUNET_CRYPTO_BSA_RSA: + break; + case GNUNET_CRYPTO_BSA_CS: + { + + } + break; + } + + { + char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string ( + &ph->coin_pub, + sizeof (struct TALER_CoinSpendPublicKeyP), + pub_str, + sizeof (pub_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "coins/%s/recoup-refresh", + pub_str); + } + + ph->url = TALER_url_join (url, + arg_str, + NULL); + if (NULL == ph->url) + { + json_decref (recoup_obj); + GNUNET_free (ph); + return NULL; + } + eh = TALER_EXCHANGE_curl_easy_get_ (ph->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&ph->ctx, + eh, + recoup_obj)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (recoup_obj); + GNUNET_free (ph->url); + GNUNET_free (ph); + return NULL; + } + json_decref (recoup_obj); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "URL for recoup-refresh: `%s'\n", + ph->url); + ph->keys = TALER_EXCHANGE_keys_incref (keys); + ph->job = GNUNET_CURL_job_add2 (ctx, + eh, + ph->ctx.headers, + &handle_recoup_refresh_finished, + ph); + return ph; +} + + +void +TALER_EXCHANGE_recoup_refresh_cancel ( + struct TALER_EXCHANGE_RecoupRefreshHandle *ph) +{ + if (NULL != ph->job) + { + GNUNET_CURL_job_cancel (ph->job); + ph->job = NULL; + } + GNUNET_free (ph->url); + TALER_curl_easy_post_finished (&ph->ctx); + TALER_EXCHANGE_keys_decref (ph->keys); + GNUNET_free (ph); +} + + +/* end of exchange_api_recoup_refresh.c */ diff --git a/src/lib/exchange_api_post-recoup-withdraw.c b/src/lib/exchange_api_post-recoup-withdraw.c @@ -0,0 +1,382 @@ +/* + This file is part of TALER + Copyright (C) 2017-2023 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_post-recoup-withdraw.c + * @brief Implementation of the /recoup request of the exchange's HTTP API + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_json_lib.h" +#include "taler/taler_exchange_service.h" +#include "exchange_api_common.h" +#include "exchange_api_handle.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A Recoup Handle + */ +struct TALER_EXCHANGE_RecoupHandle +{ + + /** + * The keys of the exchange this request handle will use + */ + struct TALER_EXCHANGE_Keys *keys; + + /** + * The url for this request. + */ + char *url; + + /** + * Context for #TEH_curl_easy_post(). Keeps the data that must + * persist for Curl to make the upload. + */ + struct TALER_CURL_PostContext ctx; + + /** + * Denomination key of the coin. + */ + struct TALER_EXCHANGE_DenomPublicKey pk; + + /** + * Our signature requesting the recoup. + */ + struct TALER_CoinSpendSignatureP coin_sig; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_RecoupResultCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Public key of the coin we are trying to get paid back. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + +}; + + +/** + * Parse a recoup response. If it is valid, call the callback. + * + * @param ph recoup handle + * @param json json reply with the signature + * @return #GNUNET_OK if the signature is valid and we called the callback; + * #GNUNET_SYSERR if not (callback must still be called) + */ +static enum GNUNET_GenericReturnValue +process_recoup_response (const struct TALER_EXCHANGE_RecoupHandle *ph, + const json_t *json) +{ + struct TALER_EXCHANGE_RecoupResponse rr = { + .hr.reply = json, + .hr.http_status = MHD_HTTP_OK + }; + struct GNUNET_JSON_Specification spec_withdraw[] = { + GNUNET_JSON_spec_fixed_auto ("reserve_pub", + &rr.details.ok.reserve_pub), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec_withdraw, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + ph->cb (ph->cb_cls, + &rr); + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /recoup request. + * + * @param cls the `struct TALER_EXCHANGE_RecoupHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_recoup_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_RecoupHandle *ph = cls; + const json_t *j = response; + struct TALER_EXCHANGE_RecoupResponse rr = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code + }; + + ph->job = NULL; + switch (response_code) + { + case 0: + rr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + process_recoup_response (ph, + j)) + { + GNUNET_break_op (0); + rr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + rr.hr.http_status = 0; + break; + } + TALER_EXCHANGE_recoup_cancel (ph); + return; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + rr.hr.ec = TALER_JSON_get_error_code (j); + rr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_CONFLICT: + { + struct TALER_Amount min_key; + + rr.hr.ec = TALER_JSON_get_error_code (j); + rr.hr.hint = TALER_JSON_get_error_hint (j); + if (GNUNET_OK != + TALER_EXCHANGE_get_min_denomination_ (ph->keys, + &min_key)) + { + GNUNET_break (0); + rr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + rr.hr.http_status = 0; + break; + } + break; + } + case MHD_HTTP_FORBIDDEN: + /* Nothing really to verify, exchange says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + rr.hr.ec = TALER_JSON_get_error_code (j); + rr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + rr.hr.ec = TALER_JSON_get_error_code (j); + rr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_GONE: + /* Kind of normal: the money was already sent to the merchant + (it was too late for the refund). */ + rr.hr.ec = TALER_JSON_get_error_code (j); + rr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + rr.hr.ec = TALER_JSON_get_error_code (j); + rr.hr.hint = TALER_JSON_get_error_hint (j); + break; + default: + /* unexpected response code */ + rr.hr.ec = TALER_JSON_get_error_code (j); + rr.hr.hint = TALER_JSON_get_error_hint (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange recoup\n", + (unsigned int) response_code, + (int) rr.hr.ec); + GNUNET_break (0); + break; + } + ph->cb (ph->cb_cls, + &rr); + TALER_EXCHANGE_recoup_cancel (ph); +} + + +struct TALER_EXCHANGE_RecoupHandle * +TALER_EXCHANGE_recoup ( + struct GNUNET_CURL_Context *ctx, + const char *url, + struct TALER_EXCHANGE_Keys *keys, + const struct TALER_EXCHANGE_DenomPublicKey *pk, + const struct TALER_DenominationSignature *denom_sig, + const struct TALER_ExchangeBlindingValues *blinding_values, + const struct TALER_PlanchetMasterSecretP *ps, + const struct TALER_HashBlindedPlanchetsP *h_planchets, + TALER_EXCHANGE_RecoupResultCallback recoup_cb, + void *recoup_cb_cls) +{ + struct TALER_EXCHANGE_RecoupHandle *ph; + struct TALER_DenominationHashP h_denom_pub; + json_t *recoup_obj; + CURL *eh; + char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32]; + struct TALER_CoinSpendPrivateKeyP coin_priv; + union GNUNET_CRYPTO_BlindingSecretP bks; + + ph = GNUNET_new (struct TALER_EXCHANGE_RecoupHandle); + TALER_planchet_setup_coin_priv (ps, + blinding_values, + &coin_priv); + TALER_planchet_blinding_secret_create (ps, + blinding_values, + &bks); + GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv, + &ph->coin_pub.eddsa_pub); + TALER_denom_pub_hash (&pk->key, + &h_denom_pub); + TALER_wallet_recoup_sign (&h_denom_pub, + &bks, + &coin_priv, + &ph->coin_sig); + recoup_obj = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("denom_pub_hash", + &h_denom_pub), + TALER_JSON_pack_denom_sig ("denom_sig", + denom_sig), + TALER_JSON_pack_exchange_blinding_values ("ewv", + blinding_values), + GNUNET_JSON_pack_data_auto ("coin_sig", + &ph->coin_sig), + GNUNET_JSON_pack_data_auto ("h_planchets", + h_planchets), + GNUNET_JSON_pack_data_auto ("coin_blind_key_secret", + &bks)); + switch (denom_sig->unblinded_sig->cipher) + { + case GNUNET_CRYPTO_BSA_INVALID: + json_decref (recoup_obj); + GNUNET_break (0); + GNUNET_free (ph); + return NULL; + case GNUNET_CRYPTO_BSA_RSA: + break; + case GNUNET_CRYPTO_BSA_CS: + { + union GNUNET_CRYPTO_BlindSessionNonce nonce; + + /* NOTE: this is not elegant, and as per the note in TALER_coin_ev_hash() + it is not strictly clear that the nonce is needed. Best case would be + to find a way to include it more 'naturally' somehow, for example with + the variant union version of bks! */ + TALER_cs_withdraw_nonce_derive (ps, + &nonce.cs_nonce); + GNUNET_assert ( + 0 == + json_object_set_new (recoup_obj, + "nonce", + GNUNET_JSON_from_data_auto ( + &nonce))); + } + } + + { + char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string ( + &ph->coin_pub, + sizeof (struct TALER_CoinSpendPublicKeyP), + pub_str, + sizeof (pub_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "coins/%s/recoup", + pub_str); + } + + ph->pk = *pk; + memset (&ph->pk.key, + 0, + sizeof (ph->pk.key)); /* zero out, as lifetime cannot be warranted */ + ph->cb = recoup_cb; + ph->cb_cls = recoup_cb_cls; + ph->url = TALER_url_join (url, + arg_str, + NULL); + if (NULL == ph->url) + { + json_decref (recoup_obj); + GNUNET_free (ph); + return NULL; + } + eh = TALER_EXCHANGE_curl_easy_get_ (ph->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&ph->ctx, + eh, + recoup_obj)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (recoup_obj); + GNUNET_free (ph->url); + GNUNET_free (ph); + return NULL; + } + json_decref (recoup_obj); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "URL for recoup: `%s'\n", + ph->url); + ph->keys = TALER_EXCHANGE_keys_incref (keys); + ph->job = GNUNET_CURL_job_add2 (ctx, + eh, + ph->ctx.headers, + &handle_recoup_finished, + ph); + return ph; +} + + +void +TALER_EXCHANGE_recoup_cancel (struct TALER_EXCHANGE_RecoupHandle *ph) +{ + if (NULL != ph->job) + { + GNUNET_CURL_job_cancel (ph->job); + ph->job = NULL; + } + GNUNET_free (ph->url); + TALER_curl_easy_post_finished (&ph->ctx); + TALER_EXCHANGE_keys_decref (ph->keys); + GNUNET_free (ph); +} + + +/* end of exchange_api_recoup.c */ diff --git a/src/lib/exchange_api_post-reserves-RESERVE_PUB-close.c b/src/lib/exchange_api_post-reserves-RESERVE_PUB-close.c @@ -0,0 +1,373 @@ +/* + This file is part of TALER + 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 General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_post-reserves-RESERVE_PUB-close.c + * @brief Implementation of the POST /reserves/$RESERVE_PUB/close requests + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP close codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_exchange_service.h" +#include "taler/taler_json_lib.h" +#include "exchange_api_handle.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A /reserves/$RID/close Handle + */ +struct TALER_EXCHANGE_ReservesCloseHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Context for #TEH_curl_easy_post(). Keeps the data that must + * persist for Curl to make the upload. + */ + struct TALER_CURL_PostContext post_ctx; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_ReservesCloseCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Public key of the reserve we are querying. + */ + struct TALER_ReservePublicKeyP reserve_pub; + + /** + * Our signature. + */ + struct TALER_ReserveSignatureP reserve_sig; + + /** + * When did we make the request. + */ + struct GNUNET_TIME_Timestamp ts; + +}; + + +/** + * We received an #MHD_HTTP_OK close code. Handle the JSON + * response. + * + * @param rch handle of the request + * @param j JSON response + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +handle_reserves_close_ok (struct TALER_EXCHANGE_ReservesCloseHandle *rch, + const json_t *j) +{ + struct TALER_EXCHANGE_ReserveCloseResult rs = { + .hr.reply = j, + .hr.http_status = MHD_HTTP_OK, + }; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount_any ("wire_amount", + &rs.details.ok.wire_amount), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + rch->cb (rch->cb_cls, + &rs); + rch->cb = NULL; + GNUNET_JSON_parse_free (spec); + return GNUNET_OK; +} + + +/** + * We received an #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS close code. Handle the JSON + * response. + * + * @param rch handle of the request + * @param j JSON response + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +handle_reserves_close_kyc (struct TALER_EXCHANGE_ReservesCloseHandle *rch, + const json_t *j) +{ + struct TALER_EXCHANGE_ReserveCloseResult rs = { + .hr.reply = j, + .hr.http_status = MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS, + }; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ( + "h_payto", + &rs.details.unavailable_for_legal_reasons.h_payto), + GNUNET_JSON_spec_uint64 ( + "requirement_row", + &rs.details.unavailable_for_legal_reasons.requirement_row), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + rch->cb (rch->cb_cls, + &rs); + rch->cb = NULL; + GNUNET_JSON_parse_free (spec); + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /reserves/$RID/close request. + * + * @param cls the `struct TALER_EXCHANGE_ReservesCloseHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_reserves_close_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_ReservesCloseHandle *rch = cls; + const json_t *j = response; + struct TALER_EXCHANGE_ReserveCloseResult rs = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code + }; + + rch->job = NULL; + switch (response_code) + { + case 0: + rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + handle_reserves_close_ok (rch, + j)) + { + GNUNET_break_op (0); + rs.hr.http_status = 0; + rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + GNUNET_break (0); + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_FORBIDDEN: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + GNUNET_break (0); + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_CONFLICT: + /* Insufficient balance to inquire for reserve close */ + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: + if (GNUNET_OK != + handle_reserves_close_kyc (rch, + j)) + { + GNUNET_break_op (0); + rs.hr.http_status = 0; + rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + } + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for reserves close\n", + (unsigned int) response_code, + (int) rs.hr.ec); + break; + } + if (NULL != rch->cb) + { + rch->cb (rch->cb_cls, + &rs); + rch->cb = NULL; + } + TALER_EXCHANGE_reserves_close_cancel (rch); +} + + +struct TALER_EXCHANGE_ReservesCloseHandle * +TALER_EXCHANGE_reserves_close ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const struct TALER_ReservePrivateKeyP *reserve_priv, + const struct TALER_FullPayto target_payto_uri, + TALER_EXCHANGE_ReservesCloseCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_ReservesCloseHandle *rch; + CURL *eh; + char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32]; + struct TALER_FullPaytoHashP h_payto; + + rch = GNUNET_new (struct TALER_EXCHANGE_ReservesCloseHandle); + rch->cb = cb; + rch->cb_cls = cb_cls; + rch->ts = GNUNET_TIME_timestamp_get (); + GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, + &rch->reserve_pub.eddsa_pub); + { + char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string ( + &rch->reserve_pub, + sizeof (rch->reserve_pub), + pub_str, + sizeof (pub_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "reserves/%s/close", + pub_str); + } + rch->url = TALER_url_join (url, + arg_str, + NULL); + if (NULL == rch->url) + { + GNUNET_free (rch); + return NULL; + } + eh = TALER_EXCHANGE_curl_easy_get_ (rch->url); + if (NULL == eh) + { + GNUNET_break (0); + GNUNET_free (rch->url); + GNUNET_free (rch); + return NULL; + } + if (NULL != target_payto_uri.full_payto) + TALER_full_payto_hash (target_payto_uri, + &h_payto); + TALER_wallet_reserve_close_sign (rch->ts, + (NULL != target_payto_uri.full_payto) + ? &h_payto + : NULL, + reserve_priv, + &rch->reserve_sig); + { + json_t *close_obj = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_allow_null ( + TALER_JSON_pack_full_payto ("payto_uri", + target_payto_uri)), + GNUNET_JSON_pack_timestamp ("request_timestamp", + rch->ts), + GNUNET_JSON_pack_data_auto ("reserve_sig", + &rch->reserve_sig)); + + if (GNUNET_OK != + TALER_curl_easy_post (&rch->post_ctx, + eh, + close_obj)) + { + GNUNET_break (0); + curl_easy_cleanup (eh); + json_decref (close_obj); + GNUNET_free (rch->url); + GNUNET_free (rch); + return NULL; + } + json_decref (close_obj); + } + rch->job = GNUNET_CURL_job_add2 (ctx, + eh, + rch->post_ctx.headers, + &handle_reserves_close_finished, + rch); + return rch; +} + + +void +TALER_EXCHANGE_reserves_close_cancel ( + struct TALER_EXCHANGE_ReservesCloseHandle *rch) +{ + if (NULL != rch->job) + { + GNUNET_CURL_job_cancel (rch->job); + rch->job = NULL; + } + TALER_curl_easy_post_finished (&rch->post_ctx); + GNUNET_free (rch->url); + GNUNET_free (rch); +} + + +/* end of exchange_api_reserves_close.c */ diff --git a/src/lib/exchange_api_post-reserves-RESERVE_PUB-open.c b/src/lib/exchange_api_post-reserves-RESERVE_PUB-open.c @@ -0,0 +1,567 @@ +/* + This file is part of TALER + 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 General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_post-reserves-RESERVE_PUB-open.c + * @brief Implementation of the POST /reserves/$RESERVE_PUB/open requests + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP open codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_exchange_service.h" +#include "taler/taler_json_lib.h" +#include "exchange_api_common.h" +#include "exchange_api_handle.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * Information we keep per coin to validate the reply. + */ +struct CoinData +{ + /** + * Public key of the coin. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + + /** + * Signature by the coin. + */ + struct TALER_CoinSpendSignatureP coin_sig; + + /** + * The hash of the denomination's public key + */ + struct TALER_DenominationHashP h_denom_pub; + + /** + * How much did this coin contribute. + */ + struct TALER_Amount contribution; +}; + + +/** + * @brief A /reserves/$RID/open Handle + */ +struct TALER_EXCHANGE_ReservesOpenHandle +{ + + /** + * The keys of the exchange this request handle will use + */ + struct TALER_EXCHANGE_Keys *keys; + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Context for #TEH_curl_easy_post(). Keeps the data that must + * persist for Curl to make the upload. + */ + struct TALER_CURL_PostContext post_ctx; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_ReservesOpenCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Information we keep per coin to validate the reply. + */ + struct CoinData *coins; + + /** + * Length of the @e coins array. + */ + unsigned int num_coins; + + /** + * Public key of the reserve we are querying. + */ + struct TALER_ReservePublicKeyP reserve_pub; + + /** + * Our signature. + */ + struct TALER_ReserveSignatureP reserve_sig; + + /** + * When did we make the request. + */ + struct GNUNET_TIME_Timestamp ts; + +}; + + +/** + * We received an #MHD_HTTP_OK open code. Handle the JSON + * response. + * + * @param roh handle of the request + * @param j JSON response + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +handle_reserves_open_ok (struct TALER_EXCHANGE_ReservesOpenHandle *roh, + const json_t *j) +{ + struct TALER_EXCHANGE_ReserveOpenResult rs = { + .hr.reply = j, + .hr.http_status = MHD_HTTP_OK, + }; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount_any ("open_cost", + &rs.details.ok.open_cost), + GNUNET_JSON_spec_timestamp ("reserve_expiration", + &rs.details.ok.expiration_time), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + roh->cb (roh->cb_cls, + &rs); + roh->cb = NULL; + GNUNET_JSON_parse_free (spec); + return GNUNET_OK; +} + + +/** + * We received an #MHD_HTTP_PAYMENT_REQUIRED open code. Handle the JSON + * response. + * + * @param roh handle of the request + * @param j JSON response + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +handle_reserves_open_pr (struct TALER_EXCHANGE_ReservesOpenHandle *roh, + const json_t *j) +{ + struct TALER_EXCHANGE_ReserveOpenResult rs = { + .hr.reply = j, + .hr.http_status = MHD_HTTP_PAYMENT_REQUIRED, + }; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount_any ("open_cost", + &rs.details.payment_required.open_cost), + GNUNET_JSON_spec_timestamp ("reserve_expiration", + &rs.details.payment_required.expiration_time), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + roh->cb (roh->cb_cls, + &rs); + roh->cb = NULL; + GNUNET_JSON_parse_free (spec); + return GNUNET_OK; +} + + +/** + * We received an #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS open code. Handle the JSON + * response. + * + * @param roh handle of the request + * @param j JSON response + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +handle_reserves_open_kyc (struct TALER_EXCHANGE_ReservesOpenHandle *roh, + const json_t *j) +{ + struct TALER_EXCHANGE_ReserveOpenResult rs = { + .hr.reply = j, + .hr.http_status = MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS, + }; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ( + "h_payto", + &rs.details.unavailable_for_legal_reasons.h_payto), + GNUNET_JSON_spec_uint64 ( + "requirement_row", + &rs.details.unavailable_for_legal_reasons.requirement_row), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + roh->cb (roh->cb_cls, + &rs); + roh->cb = NULL; + GNUNET_JSON_parse_free (spec); + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /reserves/$RID/open request. + * + * @param cls the `struct TALER_EXCHANGE_ReservesOpenHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_reserves_open_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_ReservesOpenHandle *roh = cls; + const json_t *j = response; + struct TALER_EXCHANGE_ReserveOpenResult rs = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code + }; + + roh->job = NULL; + switch (response_code) + { + case 0: + rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + handle_reserves_open_ok (roh, + j)) + { + GNUNET_break_op (0); + rs.hr.http_status = 0; + rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + GNUNET_break (0); + json_dumpf (j, + stderr, + JSON_INDENT (2)); + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_PAYMENT_REQUIRED: + if (GNUNET_OK != + handle_reserves_open_pr (roh, + j)) + { + GNUNET_break_op (0); + rs.hr.http_status = 0; + rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + } + break; + case MHD_HTTP_FORBIDDEN: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + GNUNET_break (0); + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_CONFLICT: + { + const struct CoinData *cd = NULL; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("coin_pub", + &rs.details.conflict.coin_pub), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + rs.hr.http_status = 0; + rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + for (unsigned int i = 0; i<roh->num_coins; i++) + { + const struct CoinData *cdi = &roh->coins[i]; + + if (0 == GNUNET_memcmp (&rs.details.conflict.coin_pub, + &cdi->coin_pub)) + { + cd = cdi; + break; + } + } + if (NULL == cd) + { + GNUNET_break_op (0); + rs.hr.http_status = 0; + rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + } + case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: + if (GNUNET_OK != + handle_reserves_open_kyc (roh, + j)) + { + GNUNET_break_op (0); + rs.hr.http_status = 0; + rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + } + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for reserves open\n", + (unsigned int) response_code, + (int) rs.hr.ec); + break; + } + if (NULL != roh->cb) + { + roh->cb (roh->cb_cls, + &rs); + roh->cb = NULL; + } + TALER_EXCHANGE_reserves_open_cancel (roh); +} + + +struct TALER_EXCHANGE_ReservesOpenHandle * +TALER_EXCHANGE_reserves_open ( + struct GNUNET_CURL_Context *ctx, + const char *url, + struct TALER_EXCHANGE_Keys *keys, + const struct TALER_ReservePrivateKeyP *reserve_priv, + const struct TALER_Amount *reserve_contribution, + unsigned int coin_payments_length, + const struct TALER_EXCHANGE_PurseDeposit coin_payments[ + static coin_payments_length], + struct GNUNET_TIME_Timestamp expiration_time, + uint32_t min_purses, + TALER_EXCHANGE_ReservesOpenCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_ReservesOpenHandle *roh; + CURL *eh; + char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32]; + json_t *cpa; + + roh = GNUNET_new (struct TALER_EXCHANGE_ReservesOpenHandle); + roh->cb = cb; + roh->cb_cls = cb_cls; + roh->ts = GNUNET_TIME_timestamp_get (); + GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, + &roh->reserve_pub.eddsa_pub); + { + char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string ( + &roh->reserve_pub, + sizeof (roh->reserve_pub), + pub_str, + sizeof (pub_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "reserves/%s/open", + pub_str); + } + roh->url = TALER_url_join (url, + arg_str, + NULL); + if (NULL == roh->url) + { + GNUNET_free (roh); + return NULL; + } + eh = TALER_EXCHANGE_curl_easy_get_ (roh->url); + if (NULL == eh) + { + GNUNET_break (0); + GNUNET_free (roh->url); + GNUNET_free (roh); + return NULL; + } + TALER_wallet_reserve_open_sign (reserve_contribution, + roh->ts, + expiration_time, + min_purses, + reserve_priv, + &roh->reserve_sig); + roh->coins = GNUNET_new_array (coin_payments_length, + struct CoinData); + cpa = json_array (); + GNUNET_assert (NULL != cpa); + for (unsigned int i = 0; i<coin_payments_length; i++) + { + const struct TALER_EXCHANGE_PurseDeposit *pd = &coin_payments[i]; + const struct TALER_AgeCommitmentProof *acp = pd->age_commitment_proof; + struct TALER_AgeCommitmentHashP ahac; + struct TALER_AgeCommitmentHashP *achp = NULL; + struct CoinData *cd = &roh->coins[i]; + json_t *cp; + + cd->contribution = pd->amount; + cd->h_denom_pub = pd->h_denom_pub; + if (NULL != acp) + { + TALER_age_commitment_hash (&acp->commitment, + &ahac); + achp = &ahac; + } + TALER_wallet_reserve_open_deposit_sign (&pd->amount, + &roh->reserve_sig, + &pd->coin_priv, + &cd->coin_sig); + GNUNET_CRYPTO_eddsa_key_get_public (&pd->coin_priv.eddsa_priv, + &cd->coin_pub.eddsa_pub); + + cp = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_data_auto ("h_age_commitment", + achp)), + TALER_JSON_pack_amount ("amount", + &pd->amount), + GNUNET_JSON_pack_data_auto ("denom_pub_hash", + &pd->h_denom_pub), + TALER_JSON_pack_denom_sig ("ub_sig", + &pd->denom_sig), + GNUNET_JSON_pack_data_auto ("coin_pub", + &cd->coin_pub), + GNUNET_JSON_pack_data_auto ("coin_sig", + &cd->coin_sig)); + GNUNET_assert (0 == + json_array_append_new (cpa, + cp)); + } + { + json_t *open_obj = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_timestamp ("request_timestamp", + roh->ts), + GNUNET_JSON_pack_timestamp ("reserve_expiration", + expiration_time), + GNUNET_JSON_pack_array_steal ("payments", + cpa), + TALER_JSON_pack_amount ("reserve_payment", + reserve_contribution), + GNUNET_JSON_pack_uint64 ("purse_limit", + min_purses), + GNUNET_JSON_pack_data_auto ("reserve_sig", + &roh->reserve_sig)); + + if (GNUNET_OK != + TALER_curl_easy_post (&roh->post_ctx, + eh, + open_obj)) + { + GNUNET_break (0); + curl_easy_cleanup (eh); + json_decref (open_obj); + GNUNET_free (roh->coins); + GNUNET_free (roh->url); + GNUNET_free (roh); + return NULL; + } + json_decref (open_obj); + } + roh->keys = TALER_EXCHANGE_keys_incref (keys); + roh->job = GNUNET_CURL_job_add2 (ctx, + eh, + roh->post_ctx.headers, + &handle_reserves_open_finished, + roh); + return roh; +} + + +void +TALER_EXCHANGE_reserves_open_cancel ( + struct TALER_EXCHANGE_ReservesOpenHandle *roh) +{ + if (NULL != roh->job) + { + GNUNET_CURL_job_cancel (roh->job); + roh->job = NULL; + } + TALER_curl_easy_post_finished (&roh->post_ctx); + GNUNET_free (roh->coins); + GNUNET_free (roh->url); + TALER_EXCHANGE_keys_decref (roh->keys); + GNUNET_free (roh); +} + + +/* end of exchange_api_reserves_open.c */ diff --git a/src/lib/exchange_api_post-reserves-RESERVE_PUB-purse.c b/src/lib/exchange_api_post-reserves-RESERVE_PUB-purse.c @@ -0,0 +1,580 @@ +/* + This file is part of TALER + Copyright (C) 2022-2023 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> + */ +/** + * @file lib/exchange_api_post-reserves-RESERVE_PUB-purse.c + * @brief Implementation of the client to create a + * purse for an account + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_json_lib.h" +#include "taler/taler_exchange_service.h" +#include "exchange_api_handle.h" +#include "exchange_api_common.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A purse create with merge handle + */ +struct TALER_EXCHANGE_PurseCreateMergeHandle +{ + + /** + * The keys of the exchange this request handle will use + */ + struct TALER_EXCHANGE_Keys *keys; + + /** + * The url for this request. + */ + char *url; + + /** + * The exchange base URL. + */ + char *exchange_url; + + /** + * Context for #TEH_curl_easy_post(). Keeps the data that must + * persist for Curl to make the upload. + */ + struct TALER_CURL_PostContext ctx; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_PurseCreateMergeCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * The encrypted contract (if any). + */ + struct TALER_EncryptedContract econtract; + + /** + * Expected value in the purse after fees. + */ + struct TALER_Amount purse_value_after_fees; + + /** + * Public key of the reserve public key. + */ + struct TALER_ReservePublicKeyP reserve_pub; + + /** + * Reserve signature affirming our merge. + */ + struct TALER_ReserveSignatureP reserve_sig; + + /** + * Merge capability key. + */ + struct TALER_PurseMergePublicKeyP merge_pub; + + /** + * Our merge signature (if any). + */ + struct TALER_PurseMergeSignatureP merge_sig; + + /** + * Public key of the purse. + */ + struct TALER_PurseContractPublicKeyP purse_pub; + + /** + * Request data we signed over. + */ + struct TALER_PurseContractSignatureP purse_sig; + + /** + * Hash over the purse's contrac terms. + */ + struct TALER_PrivateContractHashP h_contract_terms; + + /** + * When does the purse expire. + */ + struct GNUNET_TIME_Timestamp purse_expiration; + + /** + * When does the purse get merged/created. + */ + struct GNUNET_TIME_Timestamp merge_timestamp; +}; + + +/** + * Function called when we're done processing the + * HTTP /reserves/$RID/purse request. + * + * @param cls the `struct TALER_EXCHANGE_PurseCreateMergeHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_purse_create_with_merge_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_PurseCreateMergeHandle *pcm = cls; + const json_t *j = response; + struct TALER_EXCHANGE_PurseCreateMergeResponse dr = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code, + .reserve_sig = &pcm->reserve_sig + }; + + pcm->job = NULL; + switch (response_code) + { + case 0: + dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + { + struct GNUNET_TIME_Timestamp etime; + struct TALER_Amount total_deposited; + struct TALER_ExchangeSignatureP exchange_sig; + struct TALER_ExchangePublicKeyP exchange_pub; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount_any ("total_deposited", + &total_deposited), + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &exchange_pub), + GNUNET_JSON_spec_timestamp ("exchange_timestamp", + &etime), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (pcm->keys, + &exchange_pub)) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + if (GNUNET_OK != + TALER_exchange_online_purse_created_verify ( + etime, + pcm->purse_expiration, + &pcm->purse_value_after_fees, + &total_deposited, + &pcm->purse_pub, + &pcm->h_contract_terms, + &exchange_pub, + &exchange_sig)) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_FORBIDDEN: + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + /* Nothing really to verify, exchange says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + break; + case MHD_HTTP_CONFLICT: + dr.hr.ec = TALER_JSON_get_error_code (j); + switch (dr.hr.ec) + { + case TALER_EC_EXCHANGE_RESERVES_PURSE_CREATE_CONFLICTING_META_DATA: + if (GNUNET_OK != + TALER_EXCHANGE_check_purse_create_conflict_ ( + &pcm->purse_sig, + &pcm->purse_pub, + j)) + { + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + break; + case TALER_EC_EXCHANGE_RESERVES_PURSE_MERGE_CONFLICTING_META_DATA: + if (GNUNET_OK != + TALER_EXCHANGE_check_purse_merge_conflict_ ( + &pcm->merge_sig, + &pcm->merge_pub, + &pcm->purse_pub, + pcm->exchange_url, + j)) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + break; + case TALER_EC_EXCHANGE_RESERVES_PURSE_CREATE_INSUFFICIENT_FUNDS: + /* nothing to verify */ + break; + case TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA: + if (GNUNET_OK != + TALER_EXCHANGE_check_purse_econtract_conflict_ ( + &pcm->econtract.econtract_sig, + &pcm->purse_pub, + j)) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + break; + default: + /* unexpected EC! */ + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } /* end inner (EC) switch */ + break; + case MHD_HTTP_GONE: + /* could happen if denomination was revoked */ + /* Note: one might want to check /keys for revocation + signature here, alas tricky in case our /keys + is outdated => left to clients */ + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_uint64 ( + "requirement_row", + &dr.details.unavailable_for_legal_reasons.requirement_row), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + dr.hr.http_status = 0; + dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + } + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + dr.hr.ec = TALER_JSON_get_error_code (j); + dr.hr.hint = TALER_JSON_get_error_hint (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange deposit\n", + (unsigned int) response_code, + dr.hr.ec); + GNUNET_break_op (0); + break; + } + pcm->cb (pcm->cb_cls, + &dr); + TALER_EXCHANGE_purse_create_with_merge_cancel (pcm); +} + + +struct TALER_EXCHANGE_PurseCreateMergeHandle * +TALER_EXCHANGE_purse_create_with_merge ( + struct GNUNET_CURL_Context *ctx, + const char *url, + struct TALER_EXCHANGE_Keys *keys, + const struct TALER_ReservePrivateKeyP *reserve_priv, + const struct TALER_PurseContractPrivateKeyP *purse_priv, + const struct TALER_PurseMergePrivateKeyP *merge_priv, + const struct TALER_ContractDiffiePrivateP *contract_priv, + const json_t *contract_terms, + bool upload_contract, + bool pay_for_purse, + struct GNUNET_TIME_Timestamp merge_timestamp, + TALER_EXCHANGE_PurseCreateMergeCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_PurseCreateMergeHandle *pcm; + json_t *create_with_merge_obj; + CURL *eh; + char arg_str[sizeof (pcm->reserve_pub) * 2 + 32]; + uint32_t min_age = 0; + struct TALER_Amount purse_fee; + enum TALER_WalletAccountMergeFlags flags; + + pcm = GNUNET_new (struct TALER_EXCHANGE_PurseCreateMergeHandle); + pcm->cb = cb; + pcm->cb_cls = cb_cls; + if (GNUNET_OK != + TALER_JSON_contract_hash (contract_terms, + &pcm->h_contract_terms)) + { + GNUNET_break (0); + GNUNET_free (pcm); + return NULL; + } + pcm->merge_timestamp = merge_timestamp; + GNUNET_CRYPTO_eddsa_key_get_public (&purse_priv->eddsa_priv, + &pcm->purse_pub.eddsa_pub); + GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, + &pcm->reserve_pub.eddsa_pub); + GNUNET_CRYPTO_eddsa_key_get_public (&merge_priv->eddsa_priv, + &pcm->merge_pub.eddsa_pub); + + { + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount_any ("amount", + &pcm->purse_value_after_fees), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("minimum_age", + &min_age), + NULL), + GNUNET_JSON_spec_timestamp ("pay_deadline", + &pcm->purse_expiration), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (contract_terms, + spec, + NULL, NULL)) + { + GNUNET_break (0); + GNUNET_free (pcm); + return NULL; + } + } + if (pay_for_purse) + { + const struct TALER_EXCHANGE_GlobalFee *gf; + + gf = TALER_EXCHANGE_get_global_fee ( + keys, + GNUNET_TIME_timestamp_get ()); + purse_fee = gf->fees.purse; + flags = TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE; + } + else + { + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (pcm->purse_value_after_fees.currency, + &purse_fee)); + flags = TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA; + } + + { + char pub_str[sizeof (pcm->reserve_pub) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string ( + &pcm->reserve_pub, + sizeof (pcm->reserve_pub), + pub_str, + sizeof (pub_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "reserves/%s/purse", + pub_str); + } + pcm->url = TALER_url_join (url, + arg_str, + NULL); + if (NULL == pcm->url) + { + GNUNET_break (0); + GNUNET_free (pcm); + return NULL; + } + TALER_wallet_purse_create_sign (pcm->purse_expiration, + &pcm->h_contract_terms, + &pcm->merge_pub, + min_age, + &pcm->purse_value_after_fees, + purse_priv, + &pcm->purse_sig); + { + struct TALER_NormalizedPayto payto_uri; + + payto_uri = TALER_reserve_make_payto (url, + &pcm->reserve_pub); + TALER_wallet_purse_merge_sign (payto_uri, + merge_timestamp, + &pcm->purse_pub, + merge_priv, + &pcm->merge_sig); + GNUNET_free (payto_uri.normalized_payto); + } + TALER_wallet_account_merge_sign (merge_timestamp, + &pcm->purse_pub, + pcm->purse_expiration, + &pcm->h_contract_terms, + &pcm->purse_value_after_fees, + &purse_fee, + min_age, + flags, + reserve_priv, + &pcm->reserve_sig); + if (upload_contract) + { + TALER_CRYPTO_contract_encrypt_for_deposit ( + &pcm->purse_pub, + contract_priv, + contract_terms, + &pcm->econtract.econtract, + &pcm->econtract.econtract_size); + GNUNET_CRYPTO_ecdhe_key_get_public (&contract_priv->ecdhe_priv, + &pcm->econtract.contract_pub.ecdhe_pub); + TALER_wallet_econtract_upload_sign ( + pcm->econtract.econtract, + pcm->econtract.econtract_size, + &pcm->econtract.contract_pub, + purse_priv, + &pcm->econtract.econtract_sig); + } + create_with_merge_obj = GNUNET_JSON_PACK ( + TALER_JSON_pack_amount ("purse_value", + &pcm->purse_value_after_fees), + GNUNET_JSON_pack_uint64 ("min_age", + min_age), + GNUNET_JSON_pack_allow_null ( + TALER_JSON_pack_econtract ("econtract", + upload_contract + ? &pcm->econtract + : NULL)), + GNUNET_JSON_pack_allow_null ( + pay_for_purse + ? TALER_JSON_pack_amount ("purse_fee", + &purse_fee) + : GNUNET_JSON_pack_string ("dummy2", + NULL)), + GNUNET_JSON_pack_data_auto ("merge_pub", + &pcm->merge_pub), + GNUNET_JSON_pack_data_auto ("merge_sig", + &pcm->merge_sig), + GNUNET_JSON_pack_data_auto ("reserve_sig", + &pcm->reserve_sig), + GNUNET_JSON_pack_data_auto ("purse_pub", + &pcm->purse_pub), + GNUNET_JSON_pack_data_auto ("purse_sig", + &pcm->purse_sig), + GNUNET_JSON_pack_data_auto ("h_contract_terms", + &pcm->h_contract_terms), + GNUNET_JSON_pack_timestamp ("merge_timestamp", + merge_timestamp), + GNUNET_JSON_pack_timestamp ("purse_expiration", + pcm->purse_expiration)); + GNUNET_assert (NULL != create_with_merge_obj); + eh = TALER_EXCHANGE_curl_easy_get_ (pcm->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&pcm->ctx, + eh, + create_with_merge_obj)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (create_with_merge_obj); + GNUNET_free (pcm->econtract.econtract); + GNUNET_free (pcm->url); + GNUNET_free (pcm); + return NULL; + } + json_decref (create_with_merge_obj); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "URL for purse create_with_merge: `%s'\n", + pcm->url); + pcm->keys = TALER_EXCHANGE_keys_incref (keys); + pcm->exchange_url = GNUNET_strdup (url); + pcm->job = GNUNET_CURL_job_add2 (ctx, + eh, + pcm->ctx.headers, + &handle_purse_create_with_merge_finished, + pcm); + return pcm; +} + + +void +TALER_EXCHANGE_purse_create_with_merge_cancel ( + struct TALER_EXCHANGE_PurseCreateMergeHandle *pcm) +{ + if (NULL != pcm->job) + { + GNUNET_CURL_job_cancel (pcm->job); + pcm->job = NULL; + } + GNUNET_free (pcm->url); + GNUNET_free (pcm->exchange_url); + TALER_curl_easy_post_finished (&pcm->ctx); + TALER_EXCHANGE_keys_decref (pcm->keys); + GNUNET_free (pcm->econtract.econtract); + GNUNET_free (pcm); +} + + +/* end of exchange_api_purse_create_with_merge.c */ diff --git a/src/lib/exchange_api_post-reserves-attest-RESERVE_PUB.c b/src/lib/exchange_api_post-reserves-attest-RESERVE_PUB.c @@ -0,0 +1,365 @@ +/* + This file is part of TALER + 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 General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_post-reserves-attest-RESERVE_PUB.c + * @brief Implementation of the POST /reserves-attest/$RESERVE_PUB requests + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP attest codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_exchange_service.h" +#include "taler/taler_json_lib.h" +#include "exchange_api_handle.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A /reserves-attest/$RID Handle + */ +struct TALER_EXCHANGE_ReservesAttestHandle +{ + + /** + * The keys of the this request handle will use + */ + struct TALER_EXCHANGE_Keys *keys; + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Context for #TEH_curl_easy_post(). Keeps the data that must + * persist for Curl to make the upload. + */ + struct TALER_CURL_PostContext post_ctx; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_ReservesPostAttestCallback cb; + + /** + * Public key of the reserve we are querying. + */ + struct TALER_ReservePublicKeyP reserve_pub; + + /** + * Closure for @a cb. + */ + void *cb_cls; + +}; + + +/** + * We received an #MHD_HTTP_OK attest code. Handle the JSON + * response. + * + * @param rsh handle of the request + * @param j JSON response + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +handle_reserves_attest_ok (struct TALER_EXCHANGE_ReservesAttestHandle *rsh, + const json_t *j) +{ + struct TALER_EXCHANGE_ReservePostAttestResult rs = { + .hr.reply = j, + .hr.http_status = MHD_HTTP_OK + }; + const json_t *attributes; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_timestamp ("exchange_timestamp", + &rs.details.ok.exchange_time), + GNUNET_JSON_spec_timestamp ("expiration_time", + &rs.details.ok.expiration_time), + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &rs.details.ok.exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &rs.details.ok.exchange_pub), + GNUNET_JSON_spec_object_const ("attributes", + &attributes), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (rsh->keys, + &rs.details.ok.exchange_pub)) + { + GNUNET_break_op (0); + rs.hr.http_status = 0; + rs.hr.ec = TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_SIGNATURE_BY_EXCHANGE; + rsh->cb (rsh->cb_cls, + &rs); + rsh->cb = NULL; + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + rs.details.ok.attributes = attributes; + if (GNUNET_OK != + TALER_exchange_online_reserve_attest_details_verify ( + rs.details.ok.exchange_time, + rs.details.ok.expiration_time, + &rsh->reserve_pub, + attributes, + &rs.details.ok.exchange_pub, + &rs.details.ok.exchange_sig)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + rsh->cb (rsh->cb_cls, + &rs); + rsh->cb = NULL; + GNUNET_JSON_parse_free (spec); + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /reserves-attest/$RID request. + * + * @param cls the `struct TALER_EXCHANGE_ReservesAttestHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_reserves_attest_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_ReservesAttestHandle *rsh = cls; + const json_t *j = response; + struct TALER_EXCHANGE_ReservePostAttestResult rs = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code + }; + + rsh->job = NULL; + switch (response_code) + { + case 0: + rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + handle_reserves_attest_ok (rsh, + j)) + { + rs.hr.http_status = 0; + rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + } + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + GNUNET_break (0); + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_FORBIDDEN: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + GNUNET_break (0); + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_CONFLICT: + /* Server doesn't have the requested attributes */ + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + rs.hr.ec = TALER_JSON_get_error_code (j); + rs.hr.hint = TALER_JSON_get_error_hint (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for reserves attest\n", + (unsigned int) response_code, + (int) rs.hr.ec); + break; + } + if (NULL != rsh->cb) + { + rsh->cb (rsh->cb_cls, + &rs); + rsh->cb = NULL; + } + TALER_EXCHANGE_reserves_attest_cancel (rsh); +} + + +struct TALER_EXCHANGE_ReservesAttestHandle * +TALER_EXCHANGE_reserves_attest ( + struct GNUNET_CURL_Context *ctx, + const char *url, + struct TALER_EXCHANGE_Keys *keys, + const struct TALER_ReservePrivateKeyP *reserve_priv, + unsigned int attributes_length, + const char *attributes[const static attributes_length], + TALER_EXCHANGE_ReservesPostAttestCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_ReservesAttestHandle *rsh; + CURL *eh; + char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32]; + struct TALER_ReserveSignatureP reserve_sig; + json_t *details; + struct GNUNET_TIME_Timestamp ts; + + if (0 == attributes_length) + { + GNUNET_break (0); + return NULL; + } + details = json_array (); + GNUNET_assert (NULL != details); + for (unsigned int i = 0; i<attributes_length; i++) + { + GNUNET_assert (0 == + json_array_append_new (details, + json_string (attributes[i]))); + } + rsh = GNUNET_new (struct TALER_EXCHANGE_ReservesAttestHandle); + rsh->cb = cb; + rsh->cb_cls = cb_cls; + GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, + &rsh->reserve_pub.eddsa_pub); + { + char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string ( + &rsh->reserve_pub, + sizeof (rsh->reserve_pub), + pub_str, + sizeof (pub_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "reserves-attest/%s", + pub_str); + } + rsh->url = TALER_url_join (url, + arg_str, + NULL); + if (NULL == rsh->url) + { + json_decref (details); + GNUNET_free (rsh); + return NULL; + } + eh = TALER_EXCHANGE_curl_easy_get_ (rsh->url); + if (NULL == eh) + { + GNUNET_break (0); + json_decref (details); + GNUNET_free (rsh->url); + GNUNET_free (rsh); + return NULL; + } + ts = GNUNET_TIME_timestamp_get (); + TALER_wallet_reserve_attest_request_sign (ts, + details, + reserve_priv, + &reserve_sig); + { + json_t *attest_obj = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("reserve_sig", + &reserve_sig), + GNUNET_JSON_pack_timestamp ("request_timestamp", + ts), + GNUNET_JSON_pack_array_steal ("details", + details)); + + if (GNUNET_OK != + TALER_curl_easy_post (&rsh->post_ctx, + eh, + attest_obj)) + { + GNUNET_break (0); + curl_easy_cleanup (eh); + json_decref (attest_obj); + GNUNET_free (rsh->url); + GNUNET_free (rsh); + return NULL; + } + json_decref (attest_obj); + } + rsh->job = GNUNET_CURL_job_add2 (ctx, + eh, + rsh->post_ctx.headers, + &handle_reserves_attest_finished, + rsh); + rsh->keys = TALER_EXCHANGE_keys_incref (keys); + return rsh; +} + + +void +TALER_EXCHANGE_reserves_attest_cancel ( + struct TALER_EXCHANGE_ReservesAttestHandle *rsh) +{ + if (NULL != rsh->job) + { + GNUNET_CURL_job_cancel (rsh->job); + rsh->job = NULL; + } + TALER_curl_easy_post_finished (&rsh->post_ctx); + TALER_EXCHANGE_keys_decref (rsh->keys); + GNUNET_free (rsh->url); + GNUNET_free (rsh); +} + + +/* end of exchange_api_reserves_attest.c */ diff --git a/src/lib/exchange_api_post-reveal-melt.c b/src/lib/exchange_api_post-reveal-melt.c @@ -0,0 +1,416 @@ +/* + This file is part of TALER + Copyright (C) 2025 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_post-reveal-melt.c + * @brief Implementation of the /reveal-melt request + * @author Özgür Kesim + */ +#include "taler/platform.h" +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_json_lib.h" +#include "taler/taler_exchange_service.h" +#include "exchange_api_common.h" +#include "exchange_api_handle.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" +#include "exchange_api_refresh_common.h" + + +/** + * Handler for a running reveal-melt request + */ +struct TALER_EXCHANGE_RevealMeltHandle +{ + /** + * The url for the request + */ + char *request_url; + + /** + * CURL handle for the request job. + */ + struct GNUNET_CURL_Job *job; + + /** + * Post Context + */ + struct TALER_CURL_PostContext post_ctx; + + /** + * Number of coins to expect + */ + size_t num_expected_coins; + + /** + * The input provided + */ + const struct TALER_EXCHANGE_RevealMeltInput *reveal_input; + + /** + * The melt data + */ + struct MeltData md; + + /** + * Callback to pass the result onto + */ + TALER_EXCHANGE_RevealMeltCallback callback; + + /** + * Closure for @e callback + */ + void *callback_cls; + +}; + +/** + * We got a 200 OK response for the /reveal-melt operation. + * Extract the signed blinded coins and return it to the caller. + * + * @param mrh operation handle + * @param j_response reply from the exchange + * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors + */ +static enum GNUNET_GenericReturnValue +reveal_melt_ok ( + struct TALER_EXCHANGE_RevealMeltHandle *mrh, + const json_t *j_response) +{ + struct TALER_EXCHANGE_RevealMeltResponse response = { + .hr.reply = j_response, + .hr.http_status = MHD_HTTP_OK, + }; + struct TALER_BlindedDenominationSignature blind_sigs[mrh->num_expected_coins]; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_array_of_blinded_denom_sigs ("ev_sigs", + mrh->num_expected_coins, + blind_sigs), + GNUNET_JSON_spec_end () + }; + if (GNUNET_OK != + GNUNET_JSON_parse (j_response, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + { + struct TALER_EXCHANGE_RevealedCoinInfo coins[mrh->num_expected_coins]; + + /* Reconstruct the coins and unblind the signatures */ + for (unsigned int i = 0; i<mrh->num_expected_coins; i++) + { + struct TALER_EXCHANGE_RevealedCoinInfo *rci = &coins[i]; + const struct FreshCoinData *fcd = &mrh->md.fcds[i]; + const struct TALER_DenominationPublicKey *pk; + struct TALER_CoinSpendPublicKeyP coin_pub; + struct TALER_CoinPubHashP coin_hash; + struct TALER_FreshCoin coin; + union GNUNET_CRYPTO_BlindingSecretP bks; + const struct TALER_AgeCommitmentHashP *pah = NULL; + + rci->ps = fcd->ps[mrh->reveal_input->noreveal_index]; + rci->bks = fcd->bks[mrh->reveal_input->noreveal_index]; + rci->age_commitment_proof = NULL; + pk = &fcd->fresh_pk; + if (NULL != mrh->md.melted_coin.age_commitment_proof) + { + rci->age_commitment_proof + = fcd->age_commitment_proofs[mrh->reveal_input->noreveal_index]; + TALER_age_commitment_hash ( + &rci->age_commitment_proof->commitment, + &rci->h_age_commitment); + pah = &rci->h_age_commitment; + } + + TALER_planchet_setup_coin_priv (&rci->ps, + &mrh->reveal_input->blinding_values[i], + &rci->coin_priv); + TALER_planchet_blinding_secret_create (&rci->ps, + &mrh->reveal_input->blinding_values + [i], + &bks); + /* needed to verify the signature, and we didn't store it earlier, + hence recomputing it here... */ + GNUNET_CRYPTO_eddsa_key_get_public (&rci->coin_priv.eddsa_priv, + &coin_pub.eddsa_pub); + TALER_coin_pub_hash (&coin_pub, + pah, + &coin_hash); + if (GNUNET_OK != + TALER_planchet_to_coin (pk, + &blind_sigs[i], + &bks, + &rci->coin_priv, + pah, + &coin_hash, + &mrh->reveal_input->blinding_values[i], + &coin)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + GNUNET_JSON_parse_free (spec); + rci->sig = coin.sig; + } + + response.details.ok.num_coins = mrh->num_expected_coins; + response.details.ok.coins = coins; + mrh->callback (mrh->callback_cls, + &response); + /* Make sure the callback isn't called again */ + mrh->callback = NULL; + /* Free resources */ + for (size_t i = 0; i < mrh->num_expected_coins; i++) + { + struct TALER_EXCHANGE_RevealedCoinInfo *rci = &coins[i]; + + TALER_denom_sig_free (&rci->sig); + TALER_blinded_denom_sig_free (&blind_sigs[i]); + } + } + + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /reveal-melt request. + * + * @param cls the `struct TALER_EXCHANGE_RevealMeltHandle` + * @param response_code The HTTP response code + * @param response response data + */ +static void +handle_reveal_melt_finished ( + void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_RevealMeltHandle *mrh = cls; + const json_t *j_response = response; + struct TALER_EXCHANGE_RevealMeltResponse awr = { + .hr.reply = j_response, + .hr.http_status = (unsigned int) response_code + }; + + mrh->job = NULL; + switch (response_code) + { + case 0: + awr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + { + enum GNUNET_GenericReturnValue ret; + + ret = reveal_melt_ok (mrh, + j_response); + if (GNUNET_OK != ret) + { + GNUNET_break_op (0); + awr.hr.http_status = 0; + awr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + GNUNET_assert (NULL == mrh->callback); + TALER_EXCHANGE_reveal_melt_cancel (mrh); + return; + } + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + awr.hr.ec = TALER_JSON_get_error_code (j_response); + awr.hr.hint = TALER_JSON_get_error_hint (j_response); + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, the exchange basically just says + that it doesn't know this age-melt commitment. */ + awr.hr.ec = TALER_JSON_get_error_code (j_response); + awr.hr.hint = TALER_JSON_get_error_hint (j_response); + break; + case MHD_HTTP_CONFLICT: + /* An age commitment for one of the coins did not fulfill + * the required maximum age requirement of the corresponding + * reserve. + * Error code: TALER_EC_EXCHANGE_GENERIC_COIN_AGE_REQUIREMENT_FAILURE + * or TALER_EC_EXCHANGE_AGE_WITHDRAW_REVEAL_INVALID_HASH. + */ + awr.hr.ec = TALER_JSON_get_error_code (j_response); + awr.hr.hint = TALER_JSON_get_error_hint (j_response); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + awr.hr.ec = TALER_JSON_get_error_code (j_response); + awr.hr.hint = TALER_JSON_get_error_hint (j_response); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + awr.hr.ec = TALER_JSON_get_error_code (j_response); + awr.hr.hint = TALER_JSON_get_error_hint (j_response); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange melt\n", + (unsigned int) response_code, + (int) awr.hr.ec); + break; + } + mrh->callback (mrh->callback_cls, + &awr); + TALER_EXCHANGE_reveal_melt_cancel (mrh); +} + + +/** + * Call /reveal-melt + * + * @param curl_ctx The context for CURL + * @param mrh The handler + */ +static void +perform_protocol ( + struct GNUNET_CURL_Context *curl_ctx, + struct TALER_EXCHANGE_RevealMeltHandle *mrh) +{ + CURL *curlh; + json_t *j_batch_seeds; + + + j_batch_seeds = json_array (); + GNUNET_assert (NULL != j_batch_seeds); + + for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++) + { + if (mrh->reveal_input->noreveal_index == k) + continue; + + GNUNET_assert (0 == json_array_append_new ( + j_batch_seeds, + GNUNET_JSON_from_data_auto ( + &mrh->md.kappa_batch_seeds.tuple[k]))); + } + { + json_t *j_request_body; + + j_request_body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("rc", + &mrh->md.rc), + GNUNET_JSON_pack_array_steal ("batch_seeds", + j_batch_seeds)); + GNUNET_assert (NULL != j_request_body); + + if (NULL != mrh->reveal_input->melt_input->melt_age_commitment_proof) + { + json_t *j_age = GNUNET_JSON_PACK ( + TALER_JSON_pack_age_commitment ( + "age_commitment", + &mrh->reveal_input->melt_input->melt_age_commitment_proof->commitment) + ); + GNUNET_assert (NULL != j_age); + GNUNET_assert (0 == + json_object_update_new (j_request_body, + j_age)); + } + curlh = TALER_EXCHANGE_curl_easy_get_ (mrh->request_url); + GNUNET_assert (NULL != curlh); + GNUNET_assert (GNUNET_OK == + TALER_curl_easy_post (&mrh->post_ctx, + curlh, + j_request_body)); + json_decref (j_request_body); + } + mrh->job = GNUNET_CURL_job_add2 ( + curl_ctx, + curlh, + mrh->post_ctx.headers, + &handle_reveal_melt_finished, + mrh); + if (NULL == mrh->job) + { + GNUNET_break (0); + if (NULL != curlh) + curl_easy_cleanup (curlh); + TALER_EXCHANGE_reveal_melt_cancel (mrh); + } +} + + +struct TALER_EXCHANGE_RevealMeltHandle * +TALER_EXCHANGE_reveal_melt ( + struct GNUNET_CURL_Context *curl_ctx, + const char *exchange_url, + const struct TALER_EXCHANGE_RevealMeltInput *reveal_melt_input, + TALER_EXCHANGE_RevealMeltCallback reveal_cb, + void *reveal_cb_cls) +{ + struct TALER_EXCHANGE_RevealMeltHandle *mrh = + GNUNET_new (struct TALER_EXCHANGE_RevealMeltHandle); + mrh->callback = reveal_cb; + mrh->callback_cls = reveal_cb_cls; + mrh->reveal_input = reveal_melt_input; + mrh->num_expected_coins = reveal_melt_input->melt_input->num_fresh_denom_pubs; + mrh->request_url = TALER_url_join (exchange_url, + "reveal-melt", + NULL); + if (NULL == mrh->request_url) + { + GNUNET_break (0); + GNUNET_free (mrh); + return NULL; + } + if (reveal_melt_input->num_blinding_values != + reveal_melt_input->melt_input->num_fresh_denom_pubs) + { + GNUNET_break (0); + GNUNET_free (mrh); + return NULL; + } + TALER_EXCHANGE_get_melt_data ( + reveal_melt_input->rms, + reveal_melt_input->melt_input, + reveal_melt_input->blinding_seed, + reveal_melt_input->blinding_values, + &mrh->md); + perform_protocol (curl_ctx, + mrh); + return mrh; +} + + +void +TALER_EXCHANGE_reveal_melt_cancel ( + struct TALER_EXCHANGE_RevealMeltHandle *mrh) +{ + if (NULL != mrh->job) + { + GNUNET_CURL_job_cancel (mrh->job); + mrh->job = NULL; + } + TALER_curl_easy_post_finished (&mrh->post_ctx); + TALER_EXCHANGE_free_melt_data (&mrh->md); + GNUNET_free (mrh->request_url); + GNUNET_free (mrh); +} diff --git a/src/lib/exchange_api_post-reveal-withdraw.c b/src/lib/exchange_api_post-reveal-withdraw.c @@ -0,0 +1,366 @@ +/* + This file is part of TALER + Copyright (C) 2023-2025 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_post-reveal-withdraw.c + * @brief Implementation of /reveal-withdraw requests + * @author Özgür Kesim + */ + +#include "taler/platform.h" +#include <gnunet/gnunet_common.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler/taler_curl_lib.h" +#include "taler/taler_json_lib.h" +#include "taler/taler_exchange_service.h" +#include "exchange_api_common.h" +#include "exchange_api_handle.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" + +/** + * Handler for a running reveal-withdraw request + */ +struct TALER_EXCHANGE_RevealWithdrawHandle +{ + /** + * The commitment from the previous call withdraw + */ + const struct TALER_HashBlindedPlanchetsP *planchets_h; + + /** + * Number of coins for which to reveal tuples of seeds + */ + size_t num_coins; + + /** + * The TALER_CNC_KAPPA-1 tuple of seeds to reveal + */ + struct TALER_RevealWithdrawMasterSeedsP seeds; + + /** + * The url for the reveal request + */ + char *request_url; + + /** + * CURL handle for the request job. + */ + struct GNUNET_CURL_Job *job; + + /** + * Post Context + */ + struct TALER_CURL_PostContext post_ctx; + + /** + * Callback + */ + TALER_EXCHANGE_RevealWithdrawCallback callback; + + /** + * Reveal + */ + void *callback_cls; +}; + + +/** + * We got a 200 OK response for the /reveal-withdraw operation. + * Extract the signed blindedcoins and return it to the caller. + * + * @param wrh operation handle + * @param j_response reply from the exchange + * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors + */ +static enum GNUNET_GenericReturnValue +reveal_withdraw_ok ( + struct TALER_EXCHANGE_RevealWithdrawHandle *wrh, + const json_t *j_response) +{ + struct TALER_EXCHANGE_RevealWithdrawResponse response = { + .hr.reply = j_response, + .hr.http_status = MHD_HTTP_OK, + }; + const json_t *j_sigs; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_array_const ("ev_sigs", + &j_sigs), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j_response, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + if (wrh->num_coins != json_array_size (j_sigs)) + { + /* Number of coins generated does not match our expectation */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + { + struct TALER_BlindedDenominationSignature denom_sigs[wrh->num_coins]; + json_t *j_sig; + size_t n; + + /* Reconstruct the coins and unblind the signatures */ + json_array_foreach (j_sigs, n, j_sig) + { + struct GNUNET_JSON_Specification ispec[] = { + TALER_JSON_spec_blinded_denom_sig (NULL, + &denom_sigs[n]), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j_sig, + ispec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + + response.details.ok.num_sigs = wrh->num_coins; + response.details.ok.blinded_denom_sigs = denom_sigs; + wrh->callback (wrh->callback_cls, + &response); + /* Make sure the callback isn't called again */ + wrh->callback = NULL; + /* Free resources */ + for (size_t i = 0; i < wrh->num_coins; i++) + TALER_blinded_denom_sig_free (&denom_sigs[i]); + } + + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /reveal-withdraw request. + * + * @param cls the `struct TALER_EXCHANGE_RevealWithdrawHandle` + * @param response_code The HTTP response code + * @param response response data + */ +static void +handle_reveal_withdraw_finished ( + void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_RevealWithdrawHandle *wrh = cls; + const json_t *j_response = response; + struct TALER_EXCHANGE_RevealWithdrawResponse awr = { + .hr.reply = j_response, + .hr.http_status = (unsigned int) response_code + }; + + wrh->job = NULL; + switch (response_code) + { + case 0: + awr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + { + enum GNUNET_GenericReturnValue ret; + + ret = reveal_withdraw_ok (wrh, + j_response); + if (GNUNET_OK != ret) + { + GNUNET_break_op (0); + awr.hr.http_status = 0; + awr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + GNUNET_assert (NULL == wrh->callback); + TALER_EXCHANGE_reveal_withdraw_cancel (wrh); + return; + } + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + awr.hr.ec = TALER_JSON_get_error_code (j_response); + awr.hr.hint = TALER_JSON_get_error_hint (j_response); + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, the exchange basically just says + that it doesn't know this age-withdraw commitment. */ + awr.hr.ec = TALER_JSON_get_error_code (j_response); + awr.hr.hint = TALER_JSON_get_error_hint (j_response); + break; + case MHD_HTTP_CONFLICT: + /* An age commitment for one of the coins did not fulfill + * the required maximum age requirement of the corresponding + * reserve. + * Error code: TALER_EC_EXCHANGE_GENERIC_COIN_AGE_REQUIREMENT_FAILURE + * or TALER_EC_EXCHANGE_AGE_WITHDRAW_REVEAL_INVALID_HASH. + */ + awr.hr.ec = TALER_JSON_get_error_code (j_response); + awr.hr.hint = TALER_JSON_get_error_hint (j_response); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + awr.hr.ec = TALER_JSON_get_error_code (j_response); + awr.hr.hint = TALER_JSON_get_error_hint (j_response); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + awr.hr.ec = TALER_JSON_get_error_code (j_response); + awr.hr.hint = TALER_JSON_get_error_hint (j_response); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange age-withdraw\n", + (unsigned int) response_code, + (int) awr.hr.ec); + break; + } + wrh->callback (wrh->callback_cls, + &awr); + TALER_EXCHANGE_reveal_withdraw_cancel (wrh); +} + + +/** + * Call /reveal-withdraw + * + * @param curl_ctx The context for CURL + * @param wrh The handler + */ +static void +perform_protocol ( + struct GNUNET_CURL_Context *curl_ctx, + struct TALER_EXCHANGE_RevealWithdrawHandle *wrh) +{ + CURL *curlh; + json_t *j_array_of_secrets; + + j_array_of_secrets = json_array (); + GNUNET_assert (NULL != j_array_of_secrets); + + for (uint8_t k = 0; k < TALER_CNC_KAPPA - 1; k++) + { + json_t *j_sec = GNUNET_JSON_from_data_auto (&wrh->seeds.tuple[k]); + GNUNET_assert (NULL != j_sec); + GNUNET_assert (0 == json_array_append_new (j_array_of_secrets, + j_sec)); + } + { + json_t *j_request_body; + + j_request_body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("planchets_h", + wrh->planchets_h), + GNUNET_JSON_pack_array_steal ("disclosed_batch_seeds", + j_array_of_secrets)); + GNUNET_assert (NULL != j_request_body); + + curlh = TALER_EXCHANGE_curl_easy_get_ (wrh->request_url); + GNUNET_assert (NULL != curlh); + GNUNET_assert (GNUNET_OK == + TALER_curl_easy_post (&wrh->post_ctx, + curlh, + j_request_body)); + + json_decref (j_request_body); + } + + wrh->job = GNUNET_CURL_job_add2 ( + curl_ctx, + curlh, + wrh->post_ctx.headers, + &handle_reveal_withdraw_finished, + wrh); + if (NULL == wrh->job) + { + GNUNET_break (0); + if (NULL != curlh) + curl_easy_cleanup (curlh); + TALER_EXCHANGE_reveal_withdraw_cancel (wrh); + } + + return; +} + + +struct TALER_EXCHANGE_RevealWithdrawHandle * +TALER_EXCHANGE_reveal_withdraw ( + struct GNUNET_CURL_Context *curl_ctx, + const char *exchange_url, + size_t num_coins, + const struct TALER_HashBlindedPlanchetsP *planchets_h, + const struct TALER_RevealWithdrawMasterSeedsP *seeds, + TALER_EXCHANGE_RevealWithdrawCallback reveal_cb, + void *reveal_cb_cls) +{ + struct TALER_EXCHANGE_RevealWithdrawHandle *wrh = + GNUNET_new (struct TALER_EXCHANGE_RevealWithdrawHandle); + wrh->planchets_h = planchets_h; + wrh->num_coins = num_coins; + wrh->seeds = *seeds; + wrh->callback = reveal_cb; + wrh->callback_cls = reveal_cb_cls; + wrh->request_url = TALER_url_join (exchange_url, + "reveal-withdraw", + NULL); + if (NULL == wrh->request_url) + { + GNUNET_break (0); + GNUNET_free (wrh); + return NULL; + } + + perform_protocol (curl_ctx, wrh); + + return wrh; +} + + +void +TALER_EXCHANGE_reveal_withdraw_cancel ( + struct TALER_EXCHANGE_RevealWithdrawHandle *wrh) +{ + if (NULL != wrh->job) + { + GNUNET_CURL_job_cancel (wrh->job); + wrh->job = NULL; + } + TALER_curl_easy_post_finished (&wrh->post_ctx); + + if (NULL != wrh->request_url) + GNUNET_free (wrh->request_url); + + GNUNET_free (wrh); +} + + +/* exchange_api_reveal_withdraw.c */ diff --git a/src/lib/exchange_api_post-withdraw.c b/src/lib/exchange_api_post-withdraw.c @@ -0,0 +1,1928 @@ +/* + This file is part of TALER + Copyright (C) 2023-2025 Taler Systems SA + + TALER 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. + + TALER 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 + TALER; see the file COPYING. If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/exchange_api_post-withdraw.c + * @brief Implementation of /withdraw requests + * @author Özgür Kesim + */ +/** + * We want the "dangerous" exports here as these are OUR exports + * and we want to check that the prototypes match. + */ +#define TALER_TESTING_EXPORTS_DANGEROUS 1 +#include "taler/platform.h" +#include <gnunet/gnunet_common.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include <sys/wait.h> +#include "taler/taler_curl_lib.h" +#include "taler/taler_error_codes.h" +#include "taler/taler_json_lib.h" +#include "taler/taler_exchange_service.h" +#include "exchange_api_common.h" +#include "exchange_api_handle.h" +#include "taler/taler_signatures.h" +#include "exchange_api_curl_defaults.h" +#include "taler/taler_util.h" + +/** + * A CoinCandidate is populated from a master secret. + * The data is copied from and generated out of the client's input. + */ +struct CoinCandidate +{ + /** + * The details derived form the master secrets + */ + struct TALER_EXCHANGE_WithdrawCoinPrivateDetails details; + + /** + * Blinded hash of the coin + **/ + struct TALER_BlindedCoinHashP blinded_coin_h; + +}; + + +/** + * Closure for a call to /blinding-prepare, contains data that is needed to process + * the result. + */ +struct BlindingPrepareClosure +{ + /** + * Number of coins in the blinding-prepare step. + * Not that this number might be smaller than the total number + * of coins in the withdraw, as the prepare is only necessary + * for CS denominations + */ + size_t num_prepare_coins; + + /** + * Array of @e num_prepare_coins of data per coin + */ + struct BlindingPrepareCoinData + { + /** + * Pointer to the candidate in CoinData.candidates, + * to continue to build its contents based on the results from /blinding-prepare + */ + struct CoinCandidate *candidate; + + /** + * Planchet to finally generate in the corresponding candidate + * in CoindData.planchet_details + */ + struct TALER_PlanchetDetail *planchet; + + /** + * Denomination information, needed for the + * step after /blinding-prepare + */ + const struct TALER_DenominationPublicKey *denom_pub; + + /** + * True, if denomination supports age restriction + */ + bool age_denom; + + /** + * The index into the array of returned values from the call to + * /blinding-prepare that are to be used for this coin. + */ + size_t cs_idx; + + } *coins; + + /** + * Number of seeds requested. This may differ from @e num_prepare_coins + * in case of a withdraw with required age proof, in which case + * @e num_prepare_coins = TALER_CNC_KAPPA * @e num_seeds + */ + size_t num_nonces; + + /** + * Array of @e num_nonces calculated nonces. + */ + union GNUNET_CRYPTO_BlindSessionNonce *nonces; + + /** + * Handler to the originating call to /withdraw, needed to either + * cancel the running withdraw request (on failure of the current call + * to /blinding-prepare), or to eventually perform the protocol, once all + * blinding-prepare requests have successfully finished. + */ + struct TALER_EXCHANGE_WithdrawHandle *withdraw_handle; + +}; + + +/** + * Data we keep per coin in the batch. + * This is copied from and generated out of the input provided + * by the client. + */ +struct CoinData +{ + /** + * The denomination of the coin. + */ + struct TALER_EXCHANGE_DenomPublicKey denom_pub; + + /** + * The Candidates for the coin. If the batch is not age-restricted, + * only index 0 is used. + */ + struct CoinCandidate candidates[TALER_CNC_KAPPA]; + + /** + * Details of the planchet(s). If the batch is not age-restricted, + * only index 0 is used. + */ + struct TALER_PlanchetDetail planchet_details[TALER_CNC_KAPPA]; +}; + + +/** + * A /withdraw request-handle for calls with pre-blinded planchets. + * Returned by TALER_EXCHANGE_withdraw_blinded. + */ +struct TALER_EXCHANGE_WithdrawBlindedHandle +{ + + /** + * Reserve private key. + */ + const struct TALER_ReservePrivateKeyP *reserve_priv; + + /** + * Reserve public key, calculated + */ + struct TALER_ReservePublicKeyP reserve_pub; + + /** + * Signature of the reserve for the request, calculated after all + * parameters for the coins are collected. + */ + struct TALER_ReserveSignatureP reserve_sig; + + /* + * The denomination keys of the exchange + */ + struct TALER_EXCHANGE_Keys *keys; + + /** + * The hash of all the planchets + */ + struct TALER_HashBlindedPlanchetsP planchets_h; + + /** + * Seed used for the derival of blinding factors for denominations + * with Clause-Schnorr cipher. We derive this from the master seed + * for the withdraw, but independent from the other planchet seeds. + */ + const struct TALER_BlindingMasterSeedP *blinding_seed; + + /** + * Total amount requested (without fee). + */ + struct TALER_Amount amount; + + /** + * Total withdraw fee + */ + struct TALER_Amount fee; + + /** + * Is this call for age-restriced coins, with age proof? + */ + bool with_age_proof; + + /** + * If @e with_age_proof is true or @max_age is > 0, + * the age mask to use, extracted from the denominations. + * MUST be the same for all denominations. + */ + struct TALER_AgeMask age_mask; + + /** + * The maximum age to commit to. If @e with_age_proof + * is true, the client will need to proof the correct setting + * of age-restriction on the coins via an additional call + * to /reveal-withdraw. + */ + uint8_t max_age; + + /** + * If @e with_age_proof is true, the hash of all the selected planchets + */ + struct TALER_HashBlindedPlanchetsP selected_h; + + /** + * Length of the either the @e blinded.input or + * the @e blinded.with_age_proof_input array, + * depending on @e with_age_proof. + */ + size_t num_input; + + union + { + /** + * The blinded planchet input candidates for age-restricted coins + * for the call to /withdraw + */ + const struct + TALER_EXCHANGE_WithdrawBlindedAgeRestrictedCoinInput *with_age_proof_input; + + /** + * The blinded planchet input for the call to /withdraw via + * TALER_EXCHANGE_withdraw_blinded, for age-unrestricted coins. + */ + const struct TALER_EXCHANGE_WithdrawBlindedCoinInput *input; + + } blinded; + + /** + * The url for this request. + */ + char *request_url; + + /** + * Context for curl. + */ + struct GNUNET_CURL_Context *curl_ctx; + + /** + * CURL handle for the request job. + */ + struct GNUNET_CURL_Job *job; + + /** + * Post Context + */ + struct TALER_CURL_PostContext post_ctx; + + /** + * Function to call with withdraw response results. + */ + TALER_EXCHANGE_WithdrawBlindedCallback callback; + + /** + * Closure for @e blinded_callback + */ + void *callback_cls; +}; + +/** + * A /withdraw request-handle for calls from + * a wallet, i. e. when blinding data is available. + */ +struct TALER_EXCHANGE_WithdrawHandle +{ + + /** + * The base-URL of the exchange. + */ + const char *exchange_url; + + /** + * Seed to derive of all seeds for the coins. + */ + struct TALER_WithdrawMasterSeedP seed; + + /** + * If @e with_age_proof is true, the derived TALER_CNC_KAPPA many + * seeds for candidate batches. + */ + struct TALER_KappaWithdrawMasterSeedP kappa_seed; + + /** + * True if @e blinding_seed is filled, that is, if + * any of the denominations is of cipher type CS + */ + bool has_blinding_seed; + + /** + * Seed used for the derivation of blinding factors for denominations + * with Clause-Schnorr cipher. We derive this from the master seed + * for the withdraw, but independent from the other planchet seeds. + * Only valid when @e has_blinding_seed is true; + */ + struct TALER_BlindingMasterSeedP blinding_seed; + + /** + * Reserve private key. + */ + const struct TALER_ReservePrivateKeyP *reserve_priv; + + /** + * Reserve public key, calculated + */ + struct TALER_ReservePublicKeyP reserve_pub; + + /** + * Signature of the reserve for the request, calculated after all + * parameters for the coins are collected. + */ + struct TALER_ReserveSignatureP reserve_sig; + + /* + * The denomination keys of the exchange + */ + struct TALER_EXCHANGE_Keys *keys; + + /** + * True, if the withdraw is for age-restricted coins, with age-proof. + * The denominations MUST support age restriction. + */ + bool with_age_proof; + + /** + * If @e with_age_proof is true, the age mask, extracted + * from the denominations. + * MUST be the same for all denominations. + * + */ + struct TALER_AgeMask age_mask; + + /** + * The maximum age to commit to. If @e with_age_proof + * is true, the client will need to proof the correct setting + * of age-restriction on the coins via an additional call + * to /reveal-withdraw. + */ + uint8_t max_age; + + /** + * Length of the @e coin_data Array + */ + size_t num_coins; + + /** + * Array of per-coin data + */ + struct CoinData *coin_data; + + /** + * Context for curl. + */ + struct GNUNET_CURL_Context *curl_ctx; + + /** + * Function to call with withdraw response results. + */ + TALER_EXCHANGE_WithdrawCallback callback; + + /** + * Closure for @e callback + */ + void *callback_cls; + + /* The handler for the call to /blinding-prepare, needed for CS denominations */ + struct TALER_EXCHANGE_BlindingPrepareHandle *blinding_prepare_handle; + + /* The Handler for the actual call to the exchange */ + struct TALER_EXCHANGE_WithdrawBlindedHandle *withdraw_blinded_handle; +}; + + +/** + * We got a 200 OK response for the /withdraw operation. + * Extract the signatures and return them to the caller. + * + * @param wbh operation handle + * @param j_response reply from the exchange + * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors + */ +static enum GNUNET_GenericReturnValue +withdraw_blinded_ok ( + struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh, + const json_t *j_response) +{ + struct TALER_EXCHANGE_WithdrawBlindedResponse response = { + .hr.reply = j_response, + .hr.http_status = MHD_HTTP_OK, + }; + const json_t *j_sigs; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_array_const ("ev_sigs", + &j_sigs), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j_response, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + if (wbh->num_input != json_array_size (j_sigs)) + { + /* Number of coins generated does not match our expectation */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + { + struct TALER_BlindedDenominationSignature denoms_sig[wbh->num_input]; + + memset (denoms_sig, + 0, + sizeof(denoms_sig)); + + /* Reconstruct the coins and unblind the signatures */ + { + json_t *j_sig; + size_t i; + + json_array_foreach (j_sigs, i, j_sig) + { + struct GNUNET_JSON_Specification ispec[] = { + TALER_JSON_spec_blinded_denom_sig (NULL, + &denoms_sig[i]), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j_sig, + ispec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + } + + response.details.ok.num_sigs = wbh->num_input; + response.details.ok.blinded_denom_sigs = denoms_sig; + response.details.ok.planchets_h = wbh->planchets_h; + wbh->callback ( + wbh->callback_cls, + &response); + /* Make sure the callback isn't called again */ + wbh->callback = NULL; + /* Free resources */ + for (size_t i = 0; i < wbh->num_input; i++) + TALER_blinded_denom_sig_free (&denoms_sig[i]); + } + + return GNUNET_OK; +} + + +/** + * We got a 201 CREATED response for the /withdraw operation. + * Extract the noreveal_index and return it to the caller. + * + * @param wbh operation handle + * @param j_response reply from the exchange + * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors + */ +static enum GNUNET_GenericReturnValue +withdraw_blinded_created ( + struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh, + const json_t *j_response) +{ + struct TALER_EXCHANGE_WithdrawBlindedResponse response = { + .hr.reply = j_response, + .hr.http_status = MHD_HTTP_CREATED, + .details.created.planchets_h = wbh->planchets_h, + .details.created.num_coins = wbh->num_input, + }; + struct TALER_ExchangeSignatureP exchange_sig; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_uint8 ("noreveal_index", + &response.details.created.noreveal_index), + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &response.details.created.exchange_pub), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK!= + GNUNET_JSON_parse (j_response, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != + TALER_exchange_online_withdraw_age_confirmation_verify ( + &wbh->planchets_h, + response.details.created.noreveal_index, + &response.details.created.exchange_pub, + &exchange_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + + } + + wbh->callback (wbh->callback_cls, + &response); + /* make sure the callback isn't called again */ + wbh->callback = NULL; + + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /withdraw request. + * + * @param cls the `struct TALER_EXCHANGE_WithdrawBlindedHandle` + * @param response_code The HTTP response code + * @param response response data + */ +static void +handle_withdraw_blinded_finished ( + void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh = cls; + const json_t *j_response = response; + struct TALER_EXCHANGE_WithdrawBlindedResponse wbr = { + .hr.reply = j_response, + .hr.http_status = (unsigned int) response_code + }; + + wbh->job = NULL; + switch (response_code) + { + case 0: + wbr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + { + if (GNUNET_OK != + withdraw_blinded_ok ( + wbh, + j_response)) + { + GNUNET_break_op (0); + wbr.hr.http_status = 0; + wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + GNUNET_assert (NULL == wbh->callback); + TALER_EXCHANGE_withdraw_blinded_cancel (wbh); + return; + } + case MHD_HTTP_CREATED: + if (GNUNET_OK != + withdraw_blinded_created ( + wbh, + j_response)) + { + GNUNET_break_op (0); + wbr.hr.http_status = 0; + wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + GNUNET_assert (NULL == wbh->callback); + TALER_EXCHANGE_withdraw_blinded_cancel (wbh); + return; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + wbr.hr.ec = TALER_JSON_get_error_code (j_response); + wbr.hr.hint = TALER_JSON_get_error_hint (j_response); + break; + case MHD_HTTP_FORBIDDEN: + GNUNET_break_op (0); + /* Nothing really to verify, exchange says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + wbr.hr.ec = TALER_JSON_get_error_code (j_response); + wbr.hr.hint = TALER_JSON_get_error_hint (j_response); + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, the exchange basically just says + that it doesn't know this reserve. Can happen if we + query before the wire transfer went through. + We should simply pass the JSON reply to the application. */ + wbr.hr.ec = TALER_JSON_get_error_code (j_response); + wbr.hr.hint = TALER_JSON_get_error_hint (j_response); + break; + case MHD_HTTP_CONFLICT: + /* The age requirements might not have been met */ + wbr.hr.ec = TALER_JSON_get_error_code (j_response); + wbr.hr.hint = TALER_JSON_get_error_hint (j_response); + break; + case MHD_HTTP_GONE: + /* could happen if denomination was revoked */ + /* Note: one might want to check /keys for revocation + signature here, alas tricky in case our /keys + is outdated => left to clients */ + wbr.hr.ec = TALER_JSON_get_error_code (j_response); + wbr.hr.hint = TALER_JSON_get_error_hint (j_response); + break; + case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: + /* only validate reply is well-formed */ + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ( + "h_payto", + &wbr.details.unavailable_for_legal_reasons.h_payto), + GNUNET_JSON_spec_uint64 ( + "requirement_row", + &wbr.details.unavailable_for_legal_reasons.requirement_row), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j_response, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + wbr.hr.http_status = 0; + wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + break; + } + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + wbr.hr.ec = TALER_JSON_get_error_code (j_response); + wbr.hr.hint = TALER_JSON_get_error_hint (j_response); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + wbr.hr.ec = TALER_JSON_get_error_code (j_response); + wbr.hr.hint = TALER_JSON_get_error_hint (j_response); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange withdraw\n", + (unsigned int) response_code, + (int) wbr.hr.ec); + break; + } + wbh->callback (wbh->callback_cls, + &wbr); + TALER_EXCHANGE_withdraw_blinded_cancel (wbh); +} + + +/** + * Runs the actual withdraw operation with the blinded planchets. + * + * @param[in,out] wbh withdraw blinded handle + */ +static void +perform_withdraw_protocol ( + struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh) +{ +#define FAIL_IF(cond) \ + do { \ + if ((cond)) \ + { \ + GNUNET_break (! (cond)); \ + goto ERROR; \ + } \ + } while (0) + + json_t *j_denoms = NULL; + json_t *j_planchets = NULL; + json_t *j_request_body = NULL; + CURL *curlh = NULL; + struct GNUNET_HashContext *coins_hctx = NULL; + struct TALER_BlindedCoinHashP bch; + + GNUNET_assert (0 < wbh->num_input); + + FAIL_IF (GNUNET_OK != + TALER_amount_set_zero (wbh->keys->currency, + &wbh->amount)); + FAIL_IF (GNUNET_OK != + TALER_amount_set_zero (wbh->keys->currency, + &wbh->fee)); + + /* Accumulate total value with fees */ + for (size_t i = 0; i < wbh->num_input; i++) + { + const struct TALER_EXCHANGE_DenomPublicKey *dpub = + wbh->with_age_proof ? + wbh->blinded.with_age_proof_input[i].denom_pub : + wbh->blinded.input[i].denom_pub; + + FAIL_IF (0 > + TALER_amount_add (&wbh->amount, + &wbh->amount, + &dpub->value)); + FAIL_IF (0 > + TALER_amount_add (&wbh->fee, + &wbh->fee, + &dpub->fees.withdraw)); + + if (GNUNET_CRYPTO_BSA_CS == + dpub->key.bsign_pub_key->cipher) + GNUNET_assert (NULL != wbh->blinding_seed); + + } + + if (wbh->with_age_proof || wbh->max_age > 0) + { + wbh->age_mask = + wbh->blinded.with_age_proof_input[0].denom_pub->key.age_mask; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Attempting to withdraw from reserve %s with maximum age %d to proof\n", + TALER_B2S (&wbh->reserve_pub), + wbh->max_age); + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Attempting to withdraw from reserve %s\n", + TALER_B2S (&wbh->reserve_pub)); + } + + coins_hctx = GNUNET_CRYPTO_hash_context_start (); + FAIL_IF (NULL == coins_hctx); + + j_denoms = json_array (); + j_planchets = json_array (); + FAIL_IF ((NULL == j_denoms) || + (NULL == j_planchets)); + + for (size_t i = 0; i< wbh->num_input; i++) + { + /* Build the denomination array */ + const struct TALER_EXCHANGE_DenomPublicKey *denom_pub = + wbh->with_age_proof ? + wbh->blinded.with_age_proof_input[i].denom_pub : + wbh->blinded.input[i].denom_pub; + const struct TALER_DenominationHashP *denom_h = &denom_pub->h_key; + json_t *jdenom; + + /* The mask must be the same for all coins */ + FAIL_IF (wbh->with_age_proof && + (wbh->age_mask.bits != denom_pub->key.age_mask.bits)); + + jdenom = GNUNET_JSON_from_data_auto (denom_h); + FAIL_IF (NULL == jdenom); + FAIL_IF (0 > json_array_append_new (j_denoms, + jdenom)); + } + + + /* Build the planchet array and calculate the hash over all planchets. */ + if (! wbh->with_age_proof) + { + for (size_t i = 0; i< wbh->num_input; i++) + { + const struct TALER_PlanchetDetail *planchet = + &wbh->blinded.input[i].planchet_details; + json_t *jc = GNUNET_JSON_PACK ( + TALER_JSON_pack_blinded_planchet ( + NULL, + &planchet->blinded_planchet)); + FAIL_IF (NULL == jc); + FAIL_IF (0 > json_array_append_new (j_planchets, + jc)); + + TALER_coin_ev_hash (&planchet->blinded_planchet, + &planchet->denom_pub_hash, + &bch); + + GNUNET_CRYPTO_hash_context_read (coins_hctx, + &bch, + sizeof(bch)); + } + } + else + { /* Age restricted case with required age-proof. */ + + /** + * We collect the run of all coin candidates for the same γ index + * first, then γ+1 etc. + */ + for (size_t k = 0; k < TALER_CNC_KAPPA; k++) + { + struct GNUNET_HashContext *batch_ctx; + struct TALER_BlindedCoinHashP batch_h; + + batch_ctx = GNUNET_CRYPTO_hash_context_start (); + FAIL_IF (NULL == batch_ctx); + + for (size_t i = 0; i< wbh->num_input; i++) + { + const struct TALER_PlanchetDetail *planchet = + &wbh->blinded.with_age_proof_input[i].planchet_details[k]; + json_t *jc = GNUNET_JSON_PACK ( + TALER_JSON_pack_blinded_planchet ( + NULL, + &planchet->blinded_planchet)); + + FAIL_IF (NULL == jc); + FAIL_IF (0 > json_array_append_new ( + j_planchets, + jc)); + + TALER_coin_ev_hash ( + &planchet->blinded_planchet, + &planchet->denom_pub_hash, + &bch); + + GNUNET_CRYPTO_hash_context_read ( + batch_ctx, + &bch, + sizeof(bch)); + } + + GNUNET_CRYPTO_hash_context_finish ( + batch_ctx, + &batch_h.hash); + GNUNET_CRYPTO_hash_context_read ( + coins_hctx, + &batch_h, + sizeof(batch_h)); + } + } + + /* Build the hash of the planchets */ + GNUNET_CRYPTO_hash_context_finish ( + coins_hctx, + &wbh->planchets_h.hash); + coins_hctx = NULL; + + /* Sign the request */ + TALER_wallet_withdraw_sign ( + &wbh->amount, + &wbh->fee, + &wbh->planchets_h, + wbh->blinding_seed, + wbh->with_age_proof ? &wbh->age_mask: NULL, + wbh->with_age_proof ? wbh->max_age : 0, + wbh->reserve_priv, + &wbh->reserve_sig); + + /* Initiate the POST-request */ + j_request_body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("cipher", + "ED25519"), + GNUNET_JSON_pack_data_auto ("reserve_pub", + &wbh->reserve_pub), + GNUNET_JSON_pack_array_steal ("denoms_h", + j_denoms), + GNUNET_JSON_pack_array_steal ("coin_evs", + j_planchets), + GNUNET_JSON_pack_allow_null ( + wbh->with_age_proof + ? GNUNET_JSON_pack_int64 ("max_age", + wbh->max_age) + : GNUNET_JSON_pack_string ("max_age", + NULL) ), + GNUNET_JSON_pack_data_auto ("reserve_sig", + &wbh->reserve_sig)); + FAIL_IF (NULL == j_request_body); + + if (NULL != wbh->blinding_seed) + { + json_t *j_seed = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("blinding_seed", + wbh->blinding_seed)); + GNUNET_assert (NULL != j_seed); + GNUNET_assert (0 == + json_object_update_new ( + j_request_body, + j_seed)); + } + + curlh = TALER_EXCHANGE_curl_easy_get_ (wbh->request_url); + FAIL_IF (NULL == curlh); + FAIL_IF (GNUNET_OK != + TALER_curl_easy_post ( + &wbh->post_ctx, + curlh, + j_request_body)); + json_decref (j_request_body); + j_request_body = NULL; + + wbh->job = GNUNET_CURL_job_add2 ( + wbh->curl_ctx, + curlh, + wbh->post_ctx.headers, + &handle_withdraw_blinded_finished, + wbh); + FAIL_IF (NULL == wbh->job); + + /* No errors, return */ + return; + +ERROR: + if (NULL != coins_hctx) + GNUNET_CRYPTO_hash_context_abort (coins_hctx); + if (NULL != j_denoms) + json_decref (j_denoms); + if (NULL != j_planchets) + json_decref (j_planchets); + if (NULL != j_request_body) + json_decref (j_request_body); + if (NULL != curlh) + curl_easy_cleanup (curlh); + TALER_EXCHANGE_withdraw_blinded_cancel (wbh); + return; +#undef FAIL_IF +} + + +/** + * @brief Callback to copy the results from the call to TALER_withdraw_blinded + * in the non-age-restricted case to the result for the originating call from TALER_withdraw. + * + * @param cls struct TALER_WithdrawHandle + * @param wbr The response + */ +static void +copy_results ( + void *cls, + const struct TALER_EXCHANGE_WithdrawBlindedResponse *wbr) +{ + /* The original handle from the top-level call to withdraw */ + struct TALER_EXCHANGE_WithdrawHandle *wh = cls; + struct TALER_EXCHANGE_WithdrawResponse resp = { + .hr = wbr->hr, + }; + + wh->withdraw_blinded_handle = NULL; + + /** + * The withdraw protocol has been performed with blinded data. + * Now the response can be copied as is, except for the MHD_HTTP_OK case, + * in which we now need to perform the unblinding. + */ + switch (wbr->hr.http_status) + { + case MHD_HTTP_OK: + { + struct TALER_EXCHANGE_WithdrawCoinPrivateDetails + details[GNUNET_NZL (wh->num_coins)]; + bool ok = true; + + GNUNET_assert (wh->num_coins == wbr->details.ok.num_sigs); + memset (details, + 0, + sizeof(details)); + resp.details.ok.num_sigs = wbr->details.ok.num_sigs; + resp.details.ok.coin_details = details; + resp.details.ok.planchets_h = wbr->details.ok.planchets_h; + for (size_t n = 0; n<wh->num_coins; n++) + { + const struct TALER_BlindedDenominationSignature *bsig = + &wbr->details.ok.blinded_denom_sigs[n]; + struct CoinData *cd = &wh->coin_data[n]; + struct TALER_EXCHANGE_WithdrawCoinPrivateDetails *coin = &details[n]; + struct TALER_FreshCoin fresh_coin; + + *coin = wh->coin_data[n].candidates[0].details; + coin->planchet = wh->coin_data[n].planchet_details[0]; + GNUNET_CRYPTO_eddsa_key_get_public ( + &coin->coin_priv.eddsa_priv, + &coin->coin_pub.eddsa_pub); + + if (GNUNET_OK != + TALER_planchet_to_coin (&cd->denom_pub.key, + bsig, + &coin->blinding_key, + &coin->coin_priv, + &coin->h_age_commitment, + &coin->h_coin_pub, + &coin->blinding_values, + &fresh_coin)) + { + resp.hr.http_status = 0; + resp.hr.ec = TALER_EC_EXCHANGE_WITHDRAW_UNBLIND_FAILURE; + GNUNET_break_op (0); + ok = false; + break; + } + coin->denom_sig = fresh_coin.sig; + } + if (ok) + { + wh->callback ( + wh->callback_cls, + &resp); + wh->callback = NULL; + } + for (size_t n = 0; n<wh->num_coins; n++) + { + struct TALER_EXCHANGE_WithdrawCoinPrivateDetails *coin = &details[n]; + + TALER_denom_sig_free (&coin->denom_sig); + } + break; + } + case MHD_HTTP_CREATED: + resp.details.created = wbr->details.created; + break; + + case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: + resp.details.unavailable_for_legal_reasons = + wbr->details.unavailable_for_legal_reasons; + break; + + default: + /* nothing to do here, .hr.ec and .hr.hint are all set already from previous response */ + break; + } + if (NULL != wh->callback) + { + wh->callback ( + wh->callback_cls, + &resp); + wh->callback = NULL; + } + TALER_EXCHANGE_withdraw_cancel (wh); +} + + +/** + * @brief Callback to copy the results from the call to TALER_withdraw_blinded + * in the age-restricted case to the result for the originating call from TALER_withdraw. + * + * @param cls struct TALER_WithdrawHandle + * @param wbr The response + */ +static void +copy_results_with_age_proof ( + void *cls, + const struct TALER_EXCHANGE_WithdrawBlindedResponse *wbr) +{ + /* The original handle from the top-level call to withdraw */ + struct TALER_EXCHANGE_WithdrawHandle *wh = cls; + uint8_t k = wbr->details.created.noreveal_index; + struct TALER_EXCHANGE_WithdrawCoinPrivateDetails details[wh->num_coins]; + struct TALER_EXCHANGE_WithdrawResponse resp = { + .hr = wbr->hr, + }; + + wh->withdraw_blinded_handle = NULL; + + switch (wbr->hr.http_status) + { + case MHD_HTTP_OK: + /* in the age-restricted case, this should not happen */ + GNUNET_break_op (0); + break; + + case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: + resp.details.unavailable_for_legal_reasons = + wbr->details.unavailable_for_legal_reasons; + break; + + case MHD_HTTP_CREATED: + { + GNUNET_assert (wh->num_coins == wbr->details.created.num_coins); + resp.details.created = wbr->details.created; + resp.details.created.coin_details = details; + resp.details.created.kappa_seed = wh->kappa_seed; + memset (details, + 0, + sizeof(details)); + for (size_t n = 0; n< wh->num_coins; n++) + { + details[n] = wh->coin_data[n].candidates[k].details; + details[n].planchet = wh->coin_data[n].planchet_details[k]; + } + break; + } + + default: + break; + } + + wh->callback ( + wh->callback_cls, + &resp); + wh->callback = NULL; + TALER_EXCHANGE_withdraw_cancel (wh); +} + + +/** + * @brief Prepares and executes TALER_EXCHANGE_withdraw_blinded. + * If there were CS-denominations involved, started once the all calls + * to /blinding-prepare are done. + */ +static void +call_withdraw_blinded ( + struct TALER_EXCHANGE_WithdrawHandle *wh) +{ + + GNUNET_assert (NULL == wh->blinding_prepare_handle); + + if (! wh->with_age_proof) + { + struct TALER_EXCHANGE_WithdrawBlindedCoinInput input[wh->num_coins]; + + memset (input, + 0, + sizeof(input)); + + /* Prepare the blinded planchets as input */ + for (size_t n = 0; n < wh->num_coins; n++) + { + input[n].denom_pub = + &wh->coin_data[n].denom_pub; + input[n].planchet_details = + *wh->coin_data[n].planchet_details; + } + + wh->withdraw_blinded_handle = + TALER_EXCHANGE_withdraw_blinded ( + wh->curl_ctx, + wh->keys, + wh->exchange_url, + wh->reserve_priv, + wh->has_blinding_seed ? &wh->blinding_seed : NULL, + wh->num_coins, + input, + &copy_results, + wh); + } + else + { /* age restricted case */ + struct TALER_EXCHANGE_WithdrawBlindedAgeRestrictedCoinInput + ari[wh->num_coins]; + + memset (ari, + 0, + sizeof(ari)); + + /* Prepare the blinded planchets as input */ + for (size_t n = 0; n < wh->num_coins; n++) + { + ari[n].denom_pub = &wh->coin_data[n].denom_pub; + for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++) + ari[n].planchet_details[k] = + wh->coin_data[n].planchet_details[k]; + } + + wh->withdraw_blinded_handle = + TALER_EXCHANGE_withdraw_blinded_with_age_proof ( + wh->curl_ctx, + wh->keys, + wh->exchange_url, + wh->reserve_priv, + wh->has_blinding_seed ? &wh->blinding_seed : NULL, + wh->max_age, + wh->num_coins, + ari, + &copy_results_with_age_proof, + wh); + } +} + + +/** + * @brief Function called when /blinding-prepare is finished + * + * @param cls the `struct BlindingPrepareClosure *` + * @param bpr replies from the /blinding-prepare request + */ +static void +blinding_prepare_done ( + void *cls, + const struct TALER_EXCHANGE_BlindingPrepareResponse *bpr) +{ + struct BlindingPrepareClosure *bpcls = cls; + struct TALER_EXCHANGE_WithdrawHandle *wh = bpcls->withdraw_handle; + + wh->blinding_prepare_handle = NULL; + switch (bpr->hr.http_status) + { + case MHD_HTTP_OK: + { + bool success = false; + size_t num = bpr->details.ok.num_blinding_values; + + GNUNET_assert (0 != num); + GNUNET_assert (num == bpcls->num_nonces); + for (size_t i = 0; i < bpcls->num_prepare_coins; i++) + { + struct TALER_PlanchetDetail *planchet = bpcls->coins[i].planchet; + struct CoinCandidate *can = bpcls->coins[i].candidate; + size_t cs_idx = bpcls->coins[i].cs_idx; + + GNUNET_assert (NULL != can); + GNUNET_assert (NULL != planchet); + success = false; + + /* Complete the initialization of the coin with CS denomination */ + TALER_denom_ewv_copy ( + &can->details.blinding_values, + &bpr->details.ok.blinding_values[cs_idx]); + + GNUNET_assert (GNUNET_CRYPTO_BSA_CS == + can->details.blinding_values.blinding_inputs->cipher); + + TALER_planchet_setup_coin_priv ( + &can->details.secret, + &can->details.blinding_values, + &can->details.coin_priv); + + TALER_planchet_blinding_secret_create ( + &can->details.secret, + &can->details.blinding_values, + &can->details.blinding_key); + + /* This initializes the 2nd half of the + can->planchet_detail.blinded_planchet */ + if (GNUNET_OK != + TALER_planchet_prepare ( + bpcls->coins[i].denom_pub, + &can->details.blinding_values, + &can->details.blinding_key, + &bpcls->nonces[cs_idx], + &can->details.coin_priv, + &can->details.h_age_commitment, + &can->details.h_coin_pub, + planchet)) + { + GNUNET_break (0); + break; + } + + TALER_coin_ev_hash (&planchet->blinded_planchet, + &planchet->denom_pub_hash, + &can->blinded_coin_h); + success = true; + } + + /* /blinding-prepare is done, we can now perform the + * actual withdraw operation */ + if (success) + call_withdraw_blinded (wh); + goto cleanup; + } + default: + { + /* We got an error condition during blinding prepare that we need to report */ + struct TALER_EXCHANGE_WithdrawResponse resp = { + .hr = bpr->hr + }; + + wh->callback ( + wh->callback_cls, + &resp); + + wh->callback = NULL; + break; + } + } + TALER_EXCHANGE_withdraw_cancel (wh); +cleanup: + GNUNET_free (bpcls->coins); + GNUNET_free (bpcls->nonces); + GNUNET_free (bpcls); +} + + +/** + * @brief Prepares non age-restricted coins for the call to withdraw and + * calculates the total amount with fees. + * For denomination with CS as cipher, initiates the preflight to retrieve the + * bpcls-parameter via /blinding-prepare. + * Note that only one of the three parameters seed, tuples or secrets must not be NULL + * + * @param wh The handler to the withdraw + * @param num_coins Number of coins to withdraw + * @param max_age The maximum age to commit to + * @param denoms_pub Array @e num_coins of denominations + * @param seed master seed from which to derive @e num_coins secrets and blinding, if @e blinding_seed is NULL + * @param blinding_seed master seed for the blinding. Might be NULL, in which case the blinding_seed is derived from @e seed + * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure + */ +static enum GNUNET_GenericReturnValue +prepare_coins ( + struct TALER_EXCHANGE_WithdrawHandle *wh, + size_t num_coins, + uint8_t max_age, + const struct TALER_EXCHANGE_DenomPublicKey *denoms_pub, + const struct TALER_WithdrawMasterSeedP *seed, + const struct TALER_BlindingMasterSeedP *blinding_seed) +{ + size_t cs_num = 0; + struct BlindingPrepareClosure *cs_closure; + uint8_t kappa; + +#define FAIL_IF(cond) \ + do \ + { \ + if ((cond)) \ + { \ + GNUNET_break (! (cond)); \ + goto ERROR; \ + } \ + } while (0) + + GNUNET_assert (0 < num_coins); + + wh->num_coins = num_coins; + wh->max_age = max_age; + wh->age_mask = denoms_pub[0].key.age_mask; + wh->coin_data = GNUNET_new_array ( + wh->num_coins, + struct CoinData); + + /* First, figure out how many Clause-Schnorr denominations we have */ + for (size_t i =0; i< wh->num_coins; i++) + { + if (GNUNET_CRYPTO_BSA_CS == + denoms_pub[i].key.bsign_pub_key->cipher) + cs_num++; + } + + if (wh->with_age_proof) + { + kappa = TALER_CNC_KAPPA; + } + else + { + kappa = 1; + } + + { + struct TALER_PlanchetMasterSecretP secrets[kappa][num_coins]; + struct TALER_EXCHANGE_NonceKey cs_nonce_keys[GNUNET_NZL (cs_num)]; + uint32_t cs_indices[GNUNET_NZL (cs_num)]; + + size_t cs_denom_idx = 0; + size_t cs_coin_idx = 0; + + if (wh->with_age_proof) + { + TALER_withdraw_expand_kappa_seed (seed, + &wh->kappa_seed); + for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++) + { + TALER_withdraw_expand_secrets ( + num_coins, + &wh->kappa_seed.tuple[k], + secrets[k]); + } + } + else + { + TALER_withdraw_expand_secrets ( + num_coins, + seed, + secrets[0]); + } + + if (0 < cs_num) + { + memset (cs_nonce_keys, + 0, + sizeof(cs_nonce_keys)); + cs_closure = GNUNET_new (struct BlindingPrepareClosure); + cs_closure->withdraw_handle = wh; + cs_closure->num_prepare_coins = cs_num * kappa; + GNUNET_assert ((1 == kappa) || (cs_num * kappa > cs_num)); + cs_closure->coins = + GNUNET_new_array (cs_closure->num_prepare_coins, + struct BlindingPrepareCoinData); + cs_closure->num_nonces = cs_num; + cs_closure->nonces = + GNUNET_new_array (cs_closure->num_nonces, + union GNUNET_CRYPTO_BlindSessionNonce); + } + else + { + cs_closure = NULL; + } + + for (uint32_t i = 0; i < wh->num_coins; i++) + { + struct CoinData *cd = &wh->coin_data[i]; + bool age_denom = (0 != denoms_pub[i].key.age_mask.bits); + + cd->denom_pub = denoms_pub[i]; + /* The age mask must be the same for all coins */ + FAIL_IF (wh->with_age_proof && + (0 == denoms_pub[i].key.age_mask.bits)); + FAIL_IF (wh->age_mask.bits != + denoms_pub[i].key.age_mask.bits); + TALER_denom_pub_copy (&cd->denom_pub.key, + &denoms_pub[i].key); + + /* Mark the indices of the coins which are of type Clause-Schnorr + * and add their denomination public key hash to the list. + */ + if (GNUNET_CRYPTO_BSA_CS == + cd->denom_pub.key.bsign_pub_key->cipher) + { + GNUNET_assert (cs_denom_idx<cs_num); + cs_indices[cs_denom_idx] = i; + cs_nonce_keys[cs_denom_idx].cnc_num = i; + cs_nonce_keys[cs_denom_idx].pk = &cd->denom_pub; + cs_denom_idx++; + } + + /* + * Note that we "loop" here either only once (if with_age_proof is false), + * or TALER_CNC_KAPPA times. + */ + for (uint8_t k = 0; k < kappa; k++) + { + struct CoinCandidate *can = &cd->candidates[k]; + struct TALER_PlanchetDetail *planchet = &cd->planchet_details[k]; + + can->details.secret = secrets[k][i]; + /* + * The age restriction needs to be set on a coin if the denomination + * support age restriction. Note that his is regardless of weither + * with_age_proof is set or not. + */ + if (age_denom) + { + /* Derive the age restriction from the given secret and + * the maximum age */ + TALER_age_restriction_from_secret ( + &can->details.secret, + &wh->age_mask, + wh->max_age, + &can->details.age_commitment_proof); + + TALER_age_commitment_hash ( + &can->details.age_commitment_proof.commitment, + &can->details.h_age_commitment); + } + + switch (cd->denom_pub.key.bsign_pub_key->cipher) + { + case GNUNET_CRYPTO_BSA_RSA: + TALER_denom_ewv_copy (&can->details.blinding_values, + TALER_denom_ewv_rsa_singleton ()); + TALER_planchet_setup_coin_priv (&can->details.secret, + &can->details.blinding_values, + &can->details.coin_priv); + TALER_planchet_blinding_secret_create (&can->details.secret, + &can->details.blinding_values, + &can->details.blinding_key); + FAIL_IF (GNUNET_OK != + TALER_planchet_prepare (&cd->denom_pub.key, + &can->details.blinding_values, + &can->details.blinding_key, + NULL, + &can->details.coin_priv, + (age_denom) + ? &can->details.h_age_commitment + : NULL, + &can->details.h_coin_pub, + planchet)); + TALER_coin_ev_hash (&planchet->blinded_planchet, + &planchet->denom_pub_hash, + &can->blinded_coin_h); + + break; + + case GNUNET_CRYPTO_BSA_CS: + { + /** + * Prepare the nonce and save the index and the denomination for the callback + * after the call to blinding-prepare + */ + cs_closure->coins[cs_coin_idx].candidate = can; + cs_closure->coins[cs_coin_idx].planchet = planchet; + cs_closure->coins[cs_coin_idx].denom_pub = &cd->denom_pub.key; + cs_closure->coins[cs_coin_idx].cs_idx = i; + cs_closure->coins[cs_coin_idx].age_denom = age_denom; + cs_coin_idx++; + break; + } + default: + FAIL_IF (1); + } + } + } + + if (0 < cs_num) + { + if (NULL != blinding_seed) + { + wh->blinding_seed = *blinding_seed; + } + else + { + TALER_cs_withdraw_seed_to_blinding_seed ( + seed, + &wh->blinding_seed); + } + wh->has_blinding_seed = true; + + TALER_cs_derive_only_cs_blind_nonces_from_seed ( + &wh->blinding_seed, + false, /* not for melt */ + cs_num, + cs_indices, + cs_closure->nonces); + + wh->blinding_prepare_handle = + TALER_EXCHANGE_blinding_prepare_for_withdraw ( + wh->curl_ctx, + wh->exchange_url, + &wh->blinding_seed, + cs_num, + cs_nonce_keys, + &blinding_prepare_done, + cs_closure); + FAIL_IF (NULL == wh->blinding_prepare_handle); + } + } + return GNUNET_OK; + +ERROR: + if (0<cs_num) + { + GNUNET_free (cs_closure->nonces); + GNUNET_free (cs_closure); + } + TALER_EXCHANGE_withdraw_cancel (wh); + return GNUNET_SYSERR; +#undef FAIL_IF + +} + + +/** + * Prepare a withdraw handle for both, the non-restricted + * and age-restricted case. + * + * @param curl_ctx The curl context to use + * @param keys The keys from the exchange + * @param exchange_url The base url to the exchange + * @param reserve_priv The private key of the exchange + * @param res_cb The callback to call on response + * @param res_cb_cls The closure to pass to the callback + */ +static struct TALER_EXCHANGE_WithdrawHandle * +setup_withdraw_handle ( + struct GNUNET_CURL_Context *curl_ctx, + struct TALER_EXCHANGE_Keys *keys, + const char *exchange_url, + const struct TALER_ReservePrivateKeyP *reserve_priv, + TALER_EXCHANGE_WithdrawCallback res_cb, + void *res_cb_cls) +{ + struct TALER_EXCHANGE_WithdrawHandle *wh; + + wh = GNUNET_new (struct TALER_EXCHANGE_WithdrawHandle); + wh->exchange_url = exchange_url; + wh->keys = TALER_EXCHANGE_keys_incref (keys); + wh->curl_ctx = curl_ctx; + wh->reserve_priv = reserve_priv; + wh->callback = res_cb; + wh->callback_cls = res_cb_cls; + + return wh; +} + + +struct TALER_EXCHANGE_WithdrawHandle * +TALER_EXCHANGE_withdraw_extra_blinding_seed ( + struct GNUNET_CURL_Context *curl_ctx, + struct TALER_EXCHANGE_Keys *keys, + const char *exchange_url, + const struct TALER_ReservePrivateKeyP *reserve_priv, + size_t num_coins, + const struct TALER_EXCHANGE_DenomPublicKey denoms_pub[static num_coins], + const struct TALER_WithdrawMasterSeedP *seed, + const struct TALER_BlindingMasterSeedP *blinding_seed, + uint8_t opaque_max_age, + TALER_EXCHANGE_WithdrawCallback res_cb, + void *res_cb_cls) +{ + struct TALER_EXCHANGE_WithdrawHandle *wh; + + wh = setup_withdraw_handle (curl_ctx, + keys, + exchange_url, + reserve_priv, + res_cb, + res_cb_cls); + GNUNET_assert (NULL != wh); + wh->with_age_proof = false; + + if (GNUNET_OK != + prepare_coins (wh, + num_coins, + opaque_max_age, + denoms_pub, + seed, + blinding_seed)) + { + GNUNET_free (wh); + return NULL; + } + + /* If there were no CS denominations, we can now perform the actual + * withdraw protocol. Otherwise, there are calls to /blinding-prepare + * in flight and once they finish, the withdraw-protocol will be + * called from within the blinding_prepare_done-function. + */ + if (NULL == wh->blinding_prepare_handle) + call_withdraw_blinded (wh); + + return wh; +} + + +struct TALER_EXCHANGE_WithdrawHandle * +TALER_EXCHANGE_withdraw ( + struct GNUNET_CURL_Context *curl_ctx, + struct TALER_EXCHANGE_Keys *keys, + const char *exchange_url, + const struct TALER_ReservePrivateKeyP *reserve_priv, + size_t num_coins, + const struct TALER_EXCHANGE_DenomPublicKey denoms_pub[static num_coins], + const struct TALER_WithdrawMasterSeedP *seed, + uint8_t opaque_max_age, + TALER_EXCHANGE_WithdrawCallback res_cb, + void *res_cb_cls) +{ + return TALER_EXCHANGE_withdraw_extra_blinding_seed ( + curl_ctx, + keys, + exchange_url, + reserve_priv, + num_coins, + denoms_pub, + seed, + NULL, + opaque_max_age, + res_cb, + res_cb_cls + ); +} + + +struct TALER_EXCHANGE_WithdrawHandle * +TALER_EXCHANGE_withdraw_with_age_proof_extra_blinding_seed ( + struct GNUNET_CURL_Context *curl_ctx, + struct TALER_EXCHANGE_Keys *keys, + const char *exchange_url, + const struct TALER_ReservePrivateKeyP *reserve_priv, + size_t num_coins, + const struct TALER_EXCHANGE_DenomPublicKey denoms_pub[static num_coins], + const struct TALER_WithdrawMasterSeedP *seed, + const struct TALER_BlindingMasterSeedP *blinding_seed, + uint8_t max_age, + TALER_EXCHANGE_WithdrawCallback res_cb, + void *res_cb_cls) +{ + struct TALER_EXCHANGE_WithdrawHandle *wh; + + wh = setup_withdraw_handle (curl_ctx, + keys, + exchange_url, + reserve_priv, + res_cb, + res_cb_cls); + GNUNET_assert (NULL != wh); + + wh->with_age_proof = true; + + if (GNUNET_OK != + prepare_coins (wh, + num_coins, + max_age, + denoms_pub, + seed, + blinding_seed)) + { + GNUNET_free (wh); + return NULL; + } + + /* If there were no CS denominations, we can now perform the actual + * withdraw protocol. Otherwise, there are calls to /blinding-prepare + * in flight and once they finish, the withdraw-protocol will be + * called from within the blinding_prepare_done-function. + */ + if (NULL == wh->blinding_prepare_handle) + call_withdraw_blinded (wh); + + return wh; +} + + +struct TALER_EXCHANGE_WithdrawHandle * +TALER_EXCHANGE_withdraw_with_age_proof ( + struct GNUNET_CURL_Context *curl_ctx, + struct TALER_EXCHANGE_Keys *keys, + const char *exchange_url, + const struct TALER_ReservePrivateKeyP *reserve_priv, + size_t num_coins, + const struct TALER_EXCHANGE_DenomPublicKey denoms_pub[static num_coins], + const struct TALER_WithdrawMasterSeedP *seed, + uint8_t max_age, + TALER_EXCHANGE_WithdrawCallback res_cb, + void *res_cb_cls) +{ + return TALER_EXCHANGE_withdraw_with_age_proof_extra_blinding_seed ( + curl_ctx, + keys, + exchange_url, + reserve_priv, + num_coins, + denoms_pub, + seed, + NULL, + max_age, + res_cb, + res_cb_cls); +} + + +void +TALER_EXCHANGE_withdraw_cancel ( + struct TALER_EXCHANGE_WithdrawHandle *wh) +{ + uint8_t kappa = wh->with_age_proof ? TALER_CNC_KAPPA : 1; + + /* Cleanup coin data */ + for (unsigned int i = 0; i<wh->num_coins; i++) + { + struct CoinData *cd = &wh->coin_data[i]; + + for (uint8_t k = 0; k < kappa; k++) + { + struct TALER_PlanchetDetail *planchet = &cd->planchet_details[k]; + struct CoinCandidate *can = &cd->candidates[k]; + + TALER_blinded_planchet_free (&planchet->blinded_planchet); + TALER_denom_ewv_free (&can->details.blinding_values); + TALER_age_commitment_proof_free (&can->details.age_commitment_proof); + } + TALER_denom_pub_free (&cd->denom_pub.key); + } + + TALER_EXCHANGE_blinding_prepare_cancel (wh->blinding_prepare_handle); + TALER_EXCHANGE_withdraw_blinded_cancel (wh->withdraw_blinded_handle); + wh->blinding_prepare_handle = NULL; + wh->withdraw_blinded_handle = NULL; + + GNUNET_free (wh->coin_data); + TALER_EXCHANGE_keys_decref (wh->keys); + GNUNET_free (wh); +} + + +/** + * @brief Prepare the handler for blinded withdraw + * + * Allocates the handler struct and prepares all fields of the handler + * except the blinded planchets, + * which depend on them being age-restricted or not. + * + * @param curl_ctx the context for curl + * @param keys the exchange keys + * @param exchange_url the url to the exchange + * @param reserve_priv the reserve's private key + * @param res_cb the callback on result + * @param res_cb_cls the closure to pass on to the callback + * @return the handler + */ +static struct TALER_EXCHANGE_WithdrawBlindedHandle * +setup_handler_common ( + struct GNUNET_CURL_Context *curl_ctx, + struct TALER_EXCHANGE_Keys *keys, + const char *exchange_url, + const struct TALER_ReservePrivateKeyP *reserve_priv, + TALER_EXCHANGE_WithdrawBlindedCallback res_cb, + void *res_cb_cls) +{ + + struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh = + GNUNET_new (struct TALER_EXCHANGE_WithdrawBlindedHandle); + + wbh->keys = TALER_EXCHANGE_keys_incref (keys); + wbh->curl_ctx = curl_ctx; + wbh->reserve_priv = reserve_priv; + wbh->callback = res_cb; + wbh->callback_cls = res_cb_cls; + wbh->request_url = TALER_url_join (exchange_url, + "withdraw", + NULL); + GNUNET_CRYPTO_eddsa_key_get_public ( + &wbh->reserve_priv->eddsa_priv, + &wbh->reserve_pub.eddsa_pub); + + return wbh; +} + + +struct TALER_EXCHANGE_WithdrawBlindedHandle * +TALER_EXCHANGE_withdraw_blinded ( + struct GNUNET_CURL_Context *curl_ctx, + struct TALER_EXCHANGE_Keys *keys, + const char *exchange_url, + const struct TALER_ReservePrivateKeyP *reserve_priv, + const struct TALER_BlindingMasterSeedP *blinding_seed, + size_t num_input, + const struct TALER_EXCHANGE_WithdrawBlindedCoinInput + blinded_input[static num_input], + TALER_EXCHANGE_WithdrawBlindedCallback res_cb, + void *res_cb_cls) +{ + struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh = + setup_handler_common (curl_ctx, + keys, + exchange_url, + reserve_priv, + res_cb, + res_cb_cls); + + wbh->with_age_proof = false; + wbh->num_input = num_input; + wbh->blinded.input = blinded_input; + wbh->blinding_seed = blinding_seed; + + perform_withdraw_protocol (wbh); + return wbh; +} + + +struct TALER_EXCHANGE_WithdrawBlindedHandle * +TALER_EXCHANGE_withdraw_blinded_with_age_proof ( + struct GNUNET_CURL_Context *curl_ctx, + struct TALER_EXCHANGE_Keys *keys, + const char *exchange_url, + const struct TALER_ReservePrivateKeyP *reserve_priv, + const struct TALER_BlindingMasterSeedP *blinding_seed, + uint8_t max_age, + unsigned int num_input, + const struct TALER_EXCHANGE_WithdrawBlindedAgeRestrictedCoinInput + blinded_input[static num_input], + TALER_EXCHANGE_WithdrawBlindedCallback res_cb, + void *res_cb_cls) +{ + struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh = + setup_handler_common (curl_ctx, + keys, + exchange_url, + reserve_priv, + res_cb, + res_cb_cls); + + wbh->with_age_proof = true; + wbh->max_age = max_age; + wbh->num_input = num_input; + wbh->blinded.with_age_proof_input = blinded_input; + wbh->blinding_seed = blinding_seed; + + perform_withdraw_protocol (wbh); + return wbh; +} + + +void +TALER_EXCHANGE_withdraw_blinded_cancel ( + struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh) +{ + if (NULL == wbh) + return; + if (NULL != wbh->job) + { + GNUNET_CURL_job_cancel (wbh->job); + wbh->job = NULL; + } + GNUNET_free (wbh->request_url); + TALER_EXCHANGE_keys_decref (wbh->keys); + TALER_curl_easy_post_finished (&wbh->post_ctx); + GNUNET_free (wbh); +} + + +/* exchange_api_withdraw.c */ diff --git a/src/lib/exchange_api_purse_create_with_deposit.c b/src/lib/exchange_api_purse_create_with_deposit.c @@ -1,656 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2022-2023 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> - */ -/** - * @file lib/exchange_api_purse_create_with_deposit.c - * @brief Implementation of the client to create a purse with - * an initial set of deposits (and a contract) - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_json_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_json_lib.h" -#include "taler/taler_exchange_service.h" -#include "exchange_api_handle.h" -#include "exchange_api_common.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * Information we track per deposited coin. - */ -struct Deposit -{ - /** - * Coin's public key. - */ - struct TALER_CoinSpendPublicKeyP coin_pub; - - /** - * Signature made with the coin. - */ - struct TALER_CoinSpendSignatureP coin_sig; - - /** - * Coin's denomination. - */ - struct TALER_DenominationHashP h_denom_pub; - - /** - * Age restriction hash for the coin. - */ - struct TALER_AgeCommitmentHashP ahac; - - /** - * How much did we say the coin contributed. - */ - struct TALER_Amount contribution; -}; - - -/** - * @brief A purse create with deposit handle - */ -struct TALER_EXCHANGE_PurseCreateDepositHandle -{ - - /** - * The keys of the exchange this request handle will use - */ - struct TALER_EXCHANGE_Keys *keys; - - /** - * The url for this request. - */ - char *url; - - /** - * The base URL of the exchange. - */ - char *exchange_url; - - /** - * Context for #TEH_curl_easy_post(). Keeps the data that must - * persist for Curl to make the upload. - */ - struct TALER_CURL_PostContext ctx; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_PurseCreateDepositCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Expected value in the purse after fees. - */ - struct TALER_Amount purse_value_after_fees; - - /** - * Our encrypted contract (if we had any). - */ - struct TALER_EncryptedContract econtract; - - /** - * Public key of the merge capability. - */ - struct TALER_PurseMergePublicKeyP merge_pub; - - /** - * Public key of the purse. - */ - struct TALER_PurseContractPublicKeyP purse_pub; - - /** - * Signature with the purse key on the request. - */ - struct TALER_PurseContractSignatureP purse_sig; - - /** - * Hash over the purse's contract terms. - */ - struct TALER_PrivateContractHashP h_contract_terms; - - /** - * When does the purse expire. - */ - struct GNUNET_TIME_Timestamp purse_expiration; - - /** - * Array of @e num_deposit deposits. - */ - struct Deposit *deposits; - - /** - * How many deposits did we make? - */ - unsigned int num_deposits; - -}; - - -/** - * Function called when we're done processing the - * HTTP /deposit request. - * - * @param cls the `struct TALER_EXCHANGE_DepositHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_purse_create_deposit_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_PurseCreateDepositHandle *pch = cls; - const json_t *j = response; - struct TALER_EXCHANGE_PurseCreateDepositResponse dr = { - .hr.reply = j, - .hr.http_status = (unsigned int) response_code - }; - const struct TALER_EXCHANGE_Keys *keys = pch->keys; - - pch->job = NULL; - switch (response_code) - { - case 0: - dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - { - struct GNUNET_TIME_Timestamp etime; - struct TALER_Amount total_deposited; - struct TALER_ExchangeSignatureP exchange_sig; - struct TALER_ExchangePublicKeyP exchange_pub; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("exchange_sig", - &exchange_sig), - GNUNET_JSON_spec_fixed_auto ("exchange_pub", - &exchange_pub), - GNUNET_JSON_spec_timestamp ("exchange_timestamp", - &etime), - TALER_JSON_spec_amount ("total_deposited", - pch->purse_value_after_fees.currency, - &total_deposited), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - if (GNUNET_OK != - TALER_EXCHANGE_test_signing_key (keys, - &exchange_pub)) - { - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_EXCHANGE_PURSE_CREATE_EXCHANGE_SIGNATURE_INVALID; - break; - } - if (GNUNET_OK != - TALER_exchange_online_purse_created_verify ( - etime, - pch->purse_expiration, - &pch->purse_value_after_fees, - &total_deposited, - &pch->purse_pub, - &pch->h_contract_terms, - &exchange_pub, - &exchange_sig)) - { - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_EXCHANGE_PURSE_CREATE_EXCHANGE_SIGNATURE_INVALID; - break; - } - } - break; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_FORBIDDEN: - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - /* Nothing really to verify, exchange says one of the signatures is - invalid; as we checked them, this should never happen, we - should pass the JSON reply to the application */ - break; - case MHD_HTTP_NOT_FOUND: - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - break; - case MHD_HTTP_CONFLICT: - { - dr.hr.ec = TALER_JSON_get_error_code (j); - switch (dr.hr.ec) - { - case TALER_EC_EXCHANGE_PURSE_CREATE_CONFLICTING_META_DATA: - if (GNUNET_OK != - TALER_EXCHANGE_check_purse_create_conflict_ ( - &pch->purse_sig, - &pch->purse_pub, - j)) - { - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - break; - case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS: - /* Nothing to check anymore here, proof needs to be - checked in the GET /coins/$COIN_PUB handler */ - break; - case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY: - // FIXME #7267: write check (add to exchange_api_common! */ - break; - case TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA: - { - struct TALER_CoinSpendPublicKeyP coin_pub; - struct TALER_CoinSpendSignatureP coin_sig; - struct TALER_DenominationHashP h_denom_pub; - struct TALER_AgeCommitmentHashP phac; - bool found = false; - - if (GNUNET_OK != - TALER_EXCHANGE_check_purse_coin_conflict_ ( - &pch->purse_pub, - pch->exchange_url, - j, - &h_denom_pub, - &phac, - &coin_pub, - &coin_sig)) - { - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - for (unsigned int i = 0; i<pch->num_deposits; i++) - { - struct Deposit *deposit = &pch->deposits[i]; - - if (0 != - GNUNET_memcmp (&coin_pub, - &deposit->coin_pub)) - continue; - if (0 != - GNUNET_memcmp (&deposit->h_denom_pub, - &h_denom_pub)) - { - found = true; - break; - } - if (0 != - GNUNET_memcmp (&deposit->ahac, - &phac)) - { - found = true; - break; - } - if (0 == - GNUNET_memcmp (&coin_sig, - &deposit->coin_sig)) - { - GNUNET_break_op (0); - continue; - } - found = true; - break; - } - if (! found) - { - /* conflict is for a different coin! */ - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - } - case TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA: - if (GNUNET_OK != - TALER_EXCHANGE_check_purse_econtract_conflict_ ( - &pch->econtract.econtract_sig, - &pch->purse_pub, - j)) - { - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - break; - default: - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected error code %d for conflcting deposit\n", - dr.hr.ec); - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - } - } - break; - case MHD_HTTP_GONE: - /* could happen if denomination was revoked */ - /* Note: one might want to check /keys for revocation - signature here, alas tricky in case our /keys - is outdated => left to clients */ - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange deposit\n", - (unsigned int) response_code, - dr.hr.ec); - GNUNET_break_op (0); - break; - } - pch->cb (pch->cb_cls, - &dr); - TALER_EXCHANGE_purse_create_with_deposit_cancel (pch); -} - - -struct TALER_EXCHANGE_PurseCreateDepositHandle * -TALER_EXCHANGE_purse_create_with_deposit ( - struct GNUNET_CURL_Context *ctx, - const char *url, - struct TALER_EXCHANGE_Keys *keys, - const struct TALER_PurseContractPrivateKeyP *purse_priv, - const struct TALER_PurseMergePrivateKeyP *merge_priv, - const struct TALER_ContractDiffiePrivateP *contract_priv, - const json_t *contract_terms, - unsigned int num_deposits, - const struct TALER_EXCHANGE_PurseDeposit deposits[static num_deposits], - bool upload_contract, - TALER_EXCHANGE_PurseCreateDepositCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_PurseCreateDepositHandle *pch; - json_t *create_obj; - json_t *deposit_arr; - CURL *eh; - char arg_str[sizeof (pch->purse_pub) * 2 + 32]; - uint32_t min_age = 0; - - pch = GNUNET_new (struct TALER_EXCHANGE_PurseCreateDepositHandle); - pch->cb = cb; - pch->cb_cls = cb_cls; - { - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_timestamp ("pay_deadline", - &pch->purse_expiration), - TALER_JSON_spec_amount_any ("amount", - &pch->purse_value_after_fees), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_uint32 ("minimum_age", - &min_age), - NULL), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (contract_terms, - spec, - NULL, NULL)) - { - GNUNET_break (0); - return NULL; - } - } - if (GNUNET_OK != - TALER_JSON_contract_hash (contract_terms, - &pch->h_contract_terms)) - { - GNUNET_break (0); - return NULL; - } - GNUNET_CRYPTO_eddsa_key_get_public (&purse_priv->eddsa_priv, - &pch->purse_pub.eddsa_pub); - { - char pub_str[sizeof (pch->purse_pub) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string ( - &pch->purse_pub, - sizeof (pch->purse_pub), - pub_str, - sizeof (pub_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "purses/%s/create", - pub_str); - } - GNUNET_CRYPTO_eddsa_key_get_public (&merge_priv->eddsa_priv, - &pch->merge_pub.eddsa_pub); - pch->url = TALER_url_join (url, - arg_str, - NULL); - if (NULL == pch->url) - { - GNUNET_break (0); - GNUNET_free (pch); - return NULL; - } - pch->num_deposits = num_deposits; - pch->deposits = GNUNET_new_array (num_deposits, - struct Deposit); - deposit_arr = json_array (); - GNUNET_assert (NULL != deposit_arr); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Signing with URL `%s'\n", - url); - for (unsigned int i = 0; i<num_deposits; i++) - { - const struct TALER_EXCHANGE_PurseDeposit *deposit = &deposits[i]; - const struct TALER_AgeCommitmentProof *acp = deposit->age_commitment_proof; - struct Deposit *d = &pch->deposits[i]; - json_t *jdeposit; - struct TALER_AgeCommitmentHashP *aghp = NULL; - struct TALER_AgeAttestationP attest; - struct TALER_AgeAttestationP *attestp = NULL; - - if (NULL != acp) - { - TALER_age_commitment_hash (&acp->commitment, - &d->ahac); - aghp = &d->ahac; - if (GNUNET_OK != - TALER_age_commitment_attest (acp, - min_age, - &attest)) - { - GNUNET_break (0); - GNUNET_array_grow (pch->deposits, - pch->num_deposits, - 0); - GNUNET_free (pch->url); - json_decref (deposit_arr); - GNUNET_free (pch); - return NULL; - } - } - d->contribution = deposit->amount; - d->h_denom_pub = deposit->h_denom_pub; - GNUNET_CRYPTO_eddsa_key_get_public (&deposit->coin_priv.eddsa_priv, - &d->coin_pub.eddsa_pub); - TALER_wallet_purse_deposit_sign ( - url, - &pch->purse_pub, - &deposit->amount, - &d->h_denom_pub, - &d->ahac, - &deposit->coin_priv, - &d->coin_sig); - jdeposit = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_data_auto ("h_age_commitment", - aghp)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_data_auto ("age_attestation", - attestp)), - TALER_JSON_pack_amount ("amount", - &deposit->amount), - GNUNET_JSON_pack_data_auto ("denom_pub_hash", - &deposit->h_denom_pub), - TALER_JSON_pack_denom_sig ("ub_sig", - &deposit->denom_sig), - GNUNET_JSON_pack_data_auto ("coin_sig", - &d->coin_sig), - GNUNET_JSON_pack_data_auto ("coin_pub", - &d->coin_pub)); - GNUNET_assert (0 == - json_array_append_new (deposit_arr, - jdeposit)); - } - TALER_wallet_purse_create_sign (pch->purse_expiration, - &pch->h_contract_terms, - &pch->merge_pub, - min_age, - &pch->purse_value_after_fees, - purse_priv, - &pch->purse_sig); - if (upload_contract) - { - TALER_CRYPTO_contract_encrypt_for_merge (&pch->purse_pub, - contract_priv, - merge_priv, - contract_terms, - &pch->econtract.econtract, - &pch->econtract.econtract_size); - GNUNET_CRYPTO_ecdhe_key_get_public (&contract_priv->ecdhe_priv, - &pch->econtract.contract_pub.ecdhe_pub); - TALER_wallet_econtract_upload_sign (pch->econtract.econtract, - pch->econtract.econtract_size, - &pch->econtract.contract_pub, - purse_priv, - &pch->econtract.econtract_sig); - } - create_obj = GNUNET_JSON_PACK ( - TALER_JSON_pack_amount ("amount", - &pch->purse_value_after_fees), - GNUNET_JSON_pack_uint64 ("min_age", - min_age), - GNUNET_JSON_pack_allow_null ( - TALER_JSON_pack_econtract ("econtract", - upload_contract - ? &pch->econtract - : NULL)), - GNUNET_JSON_pack_data_auto ("purse_sig", - &pch->purse_sig), - GNUNET_JSON_pack_data_auto ("merge_pub", - &pch->merge_pub), - GNUNET_JSON_pack_data_auto ("h_contract_terms", - &pch->h_contract_terms), - GNUNET_JSON_pack_timestamp ("purse_expiration", - pch->purse_expiration), - GNUNET_JSON_pack_array_steal ("deposits", - deposit_arr)); - GNUNET_assert (NULL != create_obj); - eh = TALER_EXCHANGE_curl_easy_get_ (pch->url); - if ( (NULL == eh) || - (GNUNET_OK != - TALER_curl_easy_post (&pch->ctx, - eh, - create_obj)) ) - { - GNUNET_break (0); - if (NULL != eh) - curl_easy_cleanup (eh); - json_decref (create_obj); - GNUNET_free (pch->econtract.econtract); - GNUNET_array_grow (pch->deposits, - pch->num_deposits, - 0); - GNUNET_free (pch->url); - GNUNET_free (pch); - return NULL; - } - json_decref (create_obj); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "URL for purse create with deposit: `%s'\n", - pch->url); - pch->keys = TALER_EXCHANGE_keys_incref (keys); - pch->exchange_url = GNUNET_strdup (url); - pch->job = GNUNET_CURL_job_add2 (ctx, - eh, - pch->ctx.headers, - &handle_purse_create_deposit_finished, - pch); - return pch; -} - - -void -TALER_EXCHANGE_purse_create_with_deposit_cancel ( - struct TALER_EXCHANGE_PurseCreateDepositHandle *pch) -{ - if (NULL != pch->job) - { - GNUNET_CURL_job_cancel (pch->job); - pch->job = NULL; - } - GNUNET_free (pch->econtract.econtract); - GNUNET_free (pch->exchange_url); - GNUNET_free (pch->url); - GNUNET_array_grow (pch->deposits, - pch->num_deposits, - 0); - TALER_EXCHANGE_keys_decref (pch->keys); - TALER_curl_easy_post_finished (&pch->ctx); - GNUNET_free (pch); -} - - -/* end of exchange_api_purse_create_with_deposit.c */ diff --git a/src/lib/exchange_api_purse_create_with_merge.c b/src/lib/exchange_api_purse_create_with_merge.c @@ -1,580 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2022-2023 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> - */ -/** - * @file lib/exchange_api_purse_create_with_merge.c - * @brief Implementation of the client to create a - * purse for an account - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_json_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_json_lib.h" -#include "taler/taler_exchange_service.h" -#include "exchange_api_handle.h" -#include "exchange_api_common.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * @brief A purse create with merge handle - */ -struct TALER_EXCHANGE_PurseCreateMergeHandle -{ - - /** - * The keys of the exchange this request handle will use - */ - struct TALER_EXCHANGE_Keys *keys; - - /** - * The url for this request. - */ - char *url; - - /** - * The exchange base URL. - */ - char *exchange_url; - - /** - * Context for #TEH_curl_easy_post(). Keeps the data that must - * persist for Curl to make the upload. - */ - struct TALER_CURL_PostContext ctx; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_PurseCreateMergeCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * The encrypted contract (if any). - */ - struct TALER_EncryptedContract econtract; - - /** - * Expected value in the purse after fees. - */ - struct TALER_Amount purse_value_after_fees; - - /** - * Public key of the reserve public key. - */ - struct TALER_ReservePublicKeyP reserve_pub; - - /** - * Reserve signature affirming our merge. - */ - struct TALER_ReserveSignatureP reserve_sig; - - /** - * Merge capability key. - */ - struct TALER_PurseMergePublicKeyP merge_pub; - - /** - * Our merge signature (if any). - */ - struct TALER_PurseMergeSignatureP merge_sig; - - /** - * Public key of the purse. - */ - struct TALER_PurseContractPublicKeyP purse_pub; - - /** - * Request data we signed over. - */ - struct TALER_PurseContractSignatureP purse_sig; - - /** - * Hash over the purse's contrac terms. - */ - struct TALER_PrivateContractHashP h_contract_terms; - - /** - * When does the purse expire. - */ - struct GNUNET_TIME_Timestamp purse_expiration; - - /** - * When does the purse get merged/created. - */ - struct GNUNET_TIME_Timestamp merge_timestamp; -}; - - -/** - * Function called when we're done processing the - * HTTP /reserves/$RID/purse request. - * - * @param cls the `struct TALER_EXCHANGE_PurseCreateMergeHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_purse_create_with_merge_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_PurseCreateMergeHandle *pcm = cls; - const json_t *j = response; - struct TALER_EXCHANGE_PurseCreateMergeResponse dr = { - .hr.reply = j, - .hr.http_status = (unsigned int) response_code, - .reserve_sig = &pcm->reserve_sig - }; - - pcm->job = NULL; - switch (response_code) - { - case 0: - dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - { - struct GNUNET_TIME_Timestamp etime; - struct TALER_Amount total_deposited; - struct TALER_ExchangeSignatureP exchange_sig; - struct TALER_ExchangePublicKeyP exchange_pub; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount_any ("total_deposited", - &total_deposited), - GNUNET_JSON_spec_fixed_auto ("exchange_sig", - &exchange_sig), - GNUNET_JSON_spec_fixed_auto ("exchange_pub", - &exchange_pub), - GNUNET_JSON_spec_timestamp ("exchange_timestamp", - &etime), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - if (GNUNET_OK != - TALER_EXCHANGE_test_signing_key (pcm->keys, - &exchange_pub)) - { - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - if (GNUNET_OK != - TALER_exchange_online_purse_created_verify ( - etime, - pcm->purse_expiration, - &pcm->purse_value_after_fees, - &total_deposited, - &pcm->purse_pub, - &pcm->h_contract_terms, - &exchange_pub, - &exchange_sig)) - { - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - } - break; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_FORBIDDEN: - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - /* Nothing really to verify, exchange says one of the signatures is - invalid; as we checked them, this should never happen, we - should pass the JSON reply to the application */ - break; - case MHD_HTTP_NOT_FOUND: - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - break; - case MHD_HTTP_CONFLICT: - dr.hr.ec = TALER_JSON_get_error_code (j); - switch (dr.hr.ec) - { - case TALER_EC_EXCHANGE_RESERVES_PURSE_CREATE_CONFLICTING_META_DATA: - if (GNUNET_OK != - TALER_EXCHANGE_check_purse_create_conflict_ ( - &pcm->purse_sig, - &pcm->purse_pub, - j)) - { - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - break; - case TALER_EC_EXCHANGE_RESERVES_PURSE_MERGE_CONFLICTING_META_DATA: - if (GNUNET_OK != - TALER_EXCHANGE_check_purse_merge_conflict_ ( - &pcm->merge_sig, - &pcm->merge_pub, - &pcm->purse_pub, - pcm->exchange_url, - j)) - { - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - break; - case TALER_EC_EXCHANGE_RESERVES_PURSE_CREATE_INSUFFICIENT_FUNDS: - /* nothing to verify */ - break; - case TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA: - if (GNUNET_OK != - TALER_EXCHANGE_check_purse_econtract_conflict_ ( - &pcm->econtract.econtract_sig, - &pcm->purse_pub, - j)) - { - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - break; - default: - /* unexpected EC! */ - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } /* end inner (EC) switch */ - break; - case MHD_HTTP_GONE: - /* could happen if denomination was revoked */ - /* Note: one might want to check /keys for revocation - signature here, alas tricky in case our /keys - is outdated => left to clients */ - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: - { - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_uint64 ( - "requirement_row", - &dr.details.unavailable_for_legal_reasons.requirement_row), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - } - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange deposit\n", - (unsigned int) response_code, - dr.hr.ec); - GNUNET_break_op (0); - break; - } - pcm->cb (pcm->cb_cls, - &dr); - TALER_EXCHANGE_purse_create_with_merge_cancel (pcm); -} - - -struct TALER_EXCHANGE_PurseCreateMergeHandle * -TALER_EXCHANGE_purse_create_with_merge ( - struct GNUNET_CURL_Context *ctx, - const char *url, - struct TALER_EXCHANGE_Keys *keys, - const struct TALER_ReservePrivateKeyP *reserve_priv, - const struct TALER_PurseContractPrivateKeyP *purse_priv, - const struct TALER_PurseMergePrivateKeyP *merge_priv, - const struct TALER_ContractDiffiePrivateP *contract_priv, - const json_t *contract_terms, - bool upload_contract, - bool pay_for_purse, - struct GNUNET_TIME_Timestamp merge_timestamp, - TALER_EXCHANGE_PurseCreateMergeCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_PurseCreateMergeHandle *pcm; - json_t *create_with_merge_obj; - CURL *eh; - char arg_str[sizeof (pcm->reserve_pub) * 2 + 32]; - uint32_t min_age = 0; - struct TALER_Amount purse_fee; - enum TALER_WalletAccountMergeFlags flags; - - pcm = GNUNET_new (struct TALER_EXCHANGE_PurseCreateMergeHandle); - pcm->cb = cb; - pcm->cb_cls = cb_cls; - if (GNUNET_OK != - TALER_JSON_contract_hash (contract_terms, - &pcm->h_contract_terms)) - { - GNUNET_break (0); - GNUNET_free (pcm); - return NULL; - } - pcm->merge_timestamp = merge_timestamp; - GNUNET_CRYPTO_eddsa_key_get_public (&purse_priv->eddsa_priv, - &pcm->purse_pub.eddsa_pub); - GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, - &pcm->reserve_pub.eddsa_pub); - GNUNET_CRYPTO_eddsa_key_get_public (&merge_priv->eddsa_priv, - &pcm->merge_pub.eddsa_pub); - - { - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount_any ("amount", - &pcm->purse_value_after_fees), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_uint32 ("minimum_age", - &min_age), - NULL), - GNUNET_JSON_spec_timestamp ("pay_deadline", - &pcm->purse_expiration), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (contract_terms, - spec, - NULL, NULL)) - { - GNUNET_break (0); - GNUNET_free (pcm); - return NULL; - } - } - if (pay_for_purse) - { - const struct TALER_EXCHANGE_GlobalFee *gf; - - gf = TALER_EXCHANGE_get_global_fee ( - keys, - GNUNET_TIME_timestamp_get ()); - purse_fee = gf->fees.purse; - flags = TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE; - } - else - { - GNUNET_assert (GNUNET_OK == - TALER_amount_set_zero (pcm->purse_value_after_fees.currency, - &purse_fee)); - flags = TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA; - } - - { - char pub_str[sizeof (pcm->reserve_pub) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string ( - &pcm->reserve_pub, - sizeof (pcm->reserve_pub), - pub_str, - sizeof (pub_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "reserves/%s/purse", - pub_str); - } - pcm->url = TALER_url_join (url, - arg_str, - NULL); - if (NULL == pcm->url) - { - GNUNET_break (0); - GNUNET_free (pcm); - return NULL; - } - TALER_wallet_purse_create_sign (pcm->purse_expiration, - &pcm->h_contract_terms, - &pcm->merge_pub, - min_age, - &pcm->purse_value_after_fees, - purse_priv, - &pcm->purse_sig); - { - struct TALER_NormalizedPayto payto_uri; - - payto_uri = TALER_reserve_make_payto (url, - &pcm->reserve_pub); - TALER_wallet_purse_merge_sign (payto_uri, - merge_timestamp, - &pcm->purse_pub, - merge_priv, - &pcm->merge_sig); - GNUNET_free (payto_uri.normalized_payto); - } - TALER_wallet_account_merge_sign (merge_timestamp, - &pcm->purse_pub, - pcm->purse_expiration, - &pcm->h_contract_terms, - &pcm->purse_value_after_fees, - &purse_fee, - min_age, - flags, - reserve_priv, - &pcm->reserve_sig); - if (upload_contract) - { - TALER_CRYPTO_contract_encrypt_for_deposit ( - &pcm->purse_pub, - contract_priv, - contract_terms, - &pcm->econtract.econtract, - &pcm->econtract.econtract_size); - GNUNET_CRYPTO_ecdhe_key_get_public (&contract_priv->ecdhe_priv, - &pcm->econtract.contract_pub.ecdhe_pub); - TALER_wallet_econtract_upload_sign ( - pcm->econtract.econtract, - pcm->econtract.econtract_size, - &pcm->econtract.contract_pub, - purse_priv, - &pcm->econtract.econtract_sig); - } - create_with_merge_obj = GNUNET_JSON_PACK ( - TALER_JSON_pack_amount ("purse_value", - &pcm->purse_value_after_fees), - GNUNET_JSON_pack_uint64 ("min_age", - min_age), - GNUNET_JSON_pack_allow_null ( - TALER_JSON_pack_econtract ("econtract", - upload_contract - ? &pcm->econtract - : NULL)), - GNUNET_JSON_pack_allow_null ( - pay_for_purse - ? TALER_JSON_pack_amount ("purse_fee", - &purse_fee) - : GNUNET_JSON_pack_string ("dummy2", - NULL)), - GNUNET_JSON_pack_data_auto ("merge_pub", - &pcm->merge_pub), - GNUNET_JSON_pack_data_auto ("merge_sig", - &pcm->merge_sig), - GNUNET_JSON_pack_data_auto ("reserve_sig", - &pcm->reserve_sig), - GNUNET_JSON_pack_data_auto ("purse_pub", - &pcm->purse_pub), - GNUNET_JSON_pack_data_auto ("purse_sig", - &pcm->purse_sig), - GNUNET_JSON_pack_data_auto ("h_contract_terms", - &pcm->h_contract_terms), - GNUNET_JSON_pack_timestamp ("merge_timestamp", - merge_timestamp), - GNUNET_JSON_pack_timestamp ("purse_expiration", - pcm->purse_expiration)); - GNUNET_assert (NULL != create_with_merge_obj); - eh = TALER_EXCHANGE_curl_easy_get_ (pcm->url); - if ( (NULL == eh) || - (GNUNET_OK != - TALER_curl_easy_post (&pcm->ctx, - eh, - create_with_merge_obj)) ) - { - GNUNET_break (0); - if (NULL != eh) - curl_easy_cleanup (eh); - json_decref (create_with_merge_obj); - GNUNET_free (pcm->econtract.econtract); - GNUNET_free (pcm->url); - GNUNET_free (pcm); - return NULL; - } - json_decref (create_with_merge_obj); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "URL for purse create_with_merge: `%s'\n", - pcm->url); - pcm->keys = TALER_EXCHANGE_keys_incref (keys); - pcm->exchange_url = GNUNET_strdup (url); - pcm->job = GNUNET_CURL_job_add2 (ctx, - eh, - pcm->ctx.headers, - &handle_purse_create_with_merge_finished, - pcm); - return pcm; -} - - -void -TALER_EXCHANGE_purse_create_with_merge_cancel ( - struct TALER_EXCHANGE_PurseCreateMergeHandle *pcm) -{ - if (NULL != pcm->job) - { - GNUNET_CURL_job_cancel (pcm->job); - pcm->job = NULL; - } - GNUNET_free (pcm->url); - GNUNET_free (pcm->exchange_url); - TALER_curl_easy_post_finished (&pcm->ctx); - TALER_EXCHANGE_keys_decref (pcm->keys); - GNUNET_free (pcm->econtract.econtract); - GNUNET_free (pcm); -} - - -/* end of exchange_api_purse_create_with_merge.c */ diff --git a/src/lib/exchange_api_purse_delete.c b/src/lib/exchange_api_purse_delete.c @@ -1,243 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2022 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> - */ -/** - * @file lib/exchange_api_purse_delete.c - * @brief Implementation of the client to delete a purse - * into an account - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_json_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_json_lib.h" -#include "taler/taler_exchange_service.h" -#include "exchange_api_handle.h" -#include "exchange_api_common.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * @brief A purse delete with deposit handle - */ -struct TALER_EXCHANGE_PurseDeleteHandle -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_PurseDeleteCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Header with the purse_sig. - */ - struct curl_slist *xhdr; -}; - - -/** - * Function called when we're done processing the - * HTTP DELETE /purse/$PID request. - * - * @param cls the `struct TALER_EXCHANGE_PurseDeleteHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_purse_delete_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_PurseDeleteHandle *pdh = cls; - const json_t *j = response; - struct TALER_EXCHANGE_PurseDeleteResponse dr = { - .hr.reply = j, - .hr.http_status = (unsigned int) response_code - }; - - pdh->job = NULL; - switch (response_code) - { - case 0: - dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_NO_CONTENT: - break; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_FORBIDDEN: - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - /* Nothing really to verify, exchange says one of the signatures is - invalid; as we checked them, this should never happen, we - should pass the JSON reply to the application */ - break; - case MHD_HTTP_NOT_FOUND: - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - break; - case MHD_HTTP_CONFLICT: - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange deposit\n", - (unsigned int) response_code, - dr.hr.ec); - GNUNET_break_op (0); - break; - } - pdh->cb (pdh->cb_cls, - &dr); - TALER_EXCHANGE_purse_delete_cancel (pdh); -} - - -struct TALER_EXCHANGE_PurseDeleteHandle * -TALER_EXCHANGE_purse_delete ( - struct GNUNET_CURL_Context *ctx, - const char *url, - const struct TALER_PurseContractPrivateKeyP *purse_priv, - TALER_EXCHANGE_PurseDeleteCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_PurseDeleteHandle *pdh; - CURL *eh; - struct TALER_PurseContractPublicKeyP purse_pub; - struct TALER_PurseContractSignatureP purse_sig; - char arg_str[sizeof (purse_pub) * 2 + 32]; - - pdh = GNUNET_new (struct TALER_EXCHANGE_PurseDeleteHandle); - pdh->cb = cb; - pdh->cb_cls = cb_cls; - GNUNET_CRYPTO_eddsa_key_get_public (&purse_priv->eddsa_priv, - &purse_pub.eddsa_pub); - { - char pub_str[sizeof (purse_pub) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string (&purse_pub, - sizeof (purse_pub), - pub_str, - sizeof (pub_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "purses/%s", - pub_str); - } - pdh->url = TALER_url_join (url, - arg_str, - NULL); - if (NULL == pdh->url) - { - GNUNET_break (0); - GNUNET_free (pdh); - return NULL; - } - TALER_wallet_purse_delete_sign (purse_priv, - &purse_sig); - { - char *delete_str; - char *xhdr; - - delete_str = - GNUNET_STRINGS_data_to_string_alloc (&purse_sig, - sizeof (purse_sig)); - GNUNET_asprintf (&xhdr, - "Taler-Purse-Signature: %s", - delete_str); - GNUNET_free (delete_str); - pdh->xhdr = curl_slist_append (NULL, - xhdr); - GNUNET_free (xhdr); - } - eh = TALER_EXCHANGE_curl_easy_get_ (pdh->url); - if (NULL == eh) - { - GNUNET_break (0); - curl_slist_free_all (pdh->xhdr); - GNUNET_free (pdh->url); - GNUNET_free (pdh); - return NULL; - } - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_CUSTOMREQUEST, - MHD_HTTP_METHOD_DELETE)); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "URL for purse delete: `%s'\n", - pdh->url); - pdh->job = GNUNET_CURL_job_add2 (ctx, - eh, - pdh->xhdr, - &handle_purse_delete_finished, - pdh); - return pdh; -} - - -void -TALER_EXCHANGE_purse_delete_cancel ( - struct TALER_EXCHANGE_PurseDeleteHandle *pdh) -{ - if (NULL != pdh->job) - { - GNUNET_CURL_job_cancel (pdh->job); - pdh->job = NULL; - } - curl_slist_free_all (pdh->xhdr); - GNUNET_free (pdh->url); - GNUNET_free (pdh); -} - - -/* end of exchange_api_purse_delete.c */ diff --git a/src/lib/exchange_api_purse_deposit.c b/src/lib/exchange_api_purse_deposit.c @@ -1,520 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2022-2023 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> - */ -/** - * @file lib/exchange_api_purse_deposit.c - * @brief Implementation of the client to create a purse with - * an initial set of deposits (and a contract) - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_json_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_json_lib.h" -#include "taler/taler_exchange_service.h" -#include "exchange_api_common.h" -#include "exchange_api_handle.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * Information we track per coin. - */ -struct Coin -{ - /** - * Coin's public key. - */ - struct TALER_CoinSpendPublicKeyP coin_pub; - - /** - * Signature made with the coin. - */ - struct TALER_CoinSpendSignatureP coin_sig; - - /** - * Coin's denomination. - */ - struct TALER_DenominationHashP h_denom_pub; - - /** - * Age restriction hash for the coin. - */ - struct TALER_AgeCommitmentHashP ahac; - - /** - * How much did we say the coin contributed. - */ - struct TALER_Amount contribution; -}; - - -/** - * @brief A purse create with deposit handle - */ -struct TALER_EXCHANGE_PurseDepositHandle -{ - - /** - * The keys of the exchange this request handle will use - */ - struct TALER_EXCHANGE_Keys *keys; - - /** - * The url for this request. - */ - char *url; - - /** - * The base url of the exchange we are talking to. - */ - char *base_url; - - /** - * Context for #TEH_curl_easy_post(). Keeps the data that must - * persist for Curl to make the upload. - */ - struct TALER_CURL_PostContext ctx; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_PurseDepositCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Public key of the purse. - */ - struct TALER_PurseContractPublicKeyP purse_pub; - - /** - * Array of @e num_deposits coins we are depositing. - */ - struct Coin *coins; - - /** - * Number of coins we are depositing. - */ - unsigned int num_deposits; -}; - - -/** - * Function called when we're done processing the - * HTTP /purses/$PID/deposit request. - * - * @param cls the `struct TALER_EXCHANGE_PurseDepositHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_purse_deposit_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_PurseDepositHandle *pch = cls; - const json_t *j = response; - struct TALER_EXCHANGE_PurseDepositResponse dr = { - .hr.reply = j, - .hr.http_status = (unsigned int) response_code - }; - const struct TALER_EXCHANGE_Keys *keys = pch->keys; - - pch->job = NULL; - switch (response_code) - { - case 0: - dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - { - struct GNUNET_TIME_Timestamp etime; - struct TALER_ExchangeSignatureP exchange_sig; - struct TALER_ExchangePublicKeyP exchange_pub; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("exchange_sig", - &exchange_sig), - GNUNET_JSON_spec_fixed_auto ("exchange_pub", - &exchange_pub), - GNUNET_JSON_spec_fixed_auto ("h_contract_terms", - &dr.details.ok.h_contract_terms), - GNUNET_JSON_spec_timestamp ("exchange_timestamp", - &etime), - GNUNET_JSON_spec_timestamp ("purse_expiration", - &dr.details.ok.purse_expiration), - TALER_JSON_spec_amount ("total_deposited", - keys->currency, - &dr.details.ok.total_deposited), - TALER_JSON_spec_amount ("purse_value_after_fees", - keys->currency, - &dr.details.ok.purse_value_after_fees), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - if (GNUNET_OK != - TALER_EXCHANGE_test_signing_key (keys, - &exchange_pub)) - { - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_EXCHANGE_PURSE_DEPOSIT_EXCHANGE_SIGNATURE_INVALID; - break; - } - if (GNUNET_OK != - TALER_exchange_online_purse_created_verify ( - etime, - dr.details.ok.purse_expiration, - &dr.details.ok.purse_value_after_fees, - &dr.details.ok.total_deposited, - &pch->purse_pub, - &dr.details.ok.h_contract_terms, - &exchange_pub, - &exchange_sig)) - { - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_EXCHANGE_PURSE_DEPOSIT_EXCHANGE_SIGNATURE_INVALID; - break; - } - } - break; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - dr.hr.ec = TALER_JSON_get_error_code (j); - break; - case MHD_HTTP_FORBIDDEN: - dr.hr.ec = TALER_JSON_get_error_code (j); - /* Nothing really to verify, exchange says one of the signatures is - invalid; as we checked them, this should never happen, we - should pass the JSON reply to the application */ - break; - case MHD_HTTP_NOT_FOUND: - dr.hr.ec = TALER_JSON_get_error_code (j); - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - break; - case MHD_HTTP_CONFLICT: - dr.hr.ec = TALER_JSON_get_error_code (j); - switch (dr.hr.ec) - { - case TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA: - { - struct TALER_CoinSpendPublicKeyP coin_pub; - struct TALER_CoinSpendSignatureP coin_sig; - struct TALER_DenominationHashP h_denom_pub; - struct TALER_AgeCommitmentHashP phac; - bool found = false; - - if (GNUNET_OK != - TALER_EXCHANGE_check_purse_coin_conflict_ ( - &pch->purse_pub, - pch->base_url, - j, - &h_denom_pub, - &phac, - &coin_pub, - &coin_sig)) - { - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - for (unsigned int i = 0; i<pch->num_deposits; i++) - { - struct Coin *coin = &pch->coins[i]; - if (0 != GNUNET_memcmp (&coin_pub, - &coin->coin_pub)) - continue; - if (0 != - GNUNET_memcmp (&coin->h_denom_pub, - &h_denom_pub)) - { - found = true; - break; - } - if (0 != - GNUNET_memcmp (&coin->ahac, - &phac)) - { - found = true; - break; - } - if (0 == GNUNET_memcmp (&coin_sig, - &coin->coin_sig)) - { - /* identical signature => not a conflict */ - continue; - } - found = true; - break; - } - if (! found) - { - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - /* meta data conflict is real! */ - break; - } - case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS: - /* Nothing to check anymore here, proof needs to be - checked in the GET /coins/$COIN_PUB handler */ - break; - case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY: - break; - default: - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } /* ec switch */ - break; - case MHD_HTTP_GONE: - /* could happen if denomination was revoked or purse expired */ - /* Note: one might want to check /keys for revocation - signature here, alas tricky in case our /keys - is outdated => left to clients */ - dr.hr.ec = TALER_JSON_get_error_code (j); - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - dr.hr.ec = TALER_JSON_get_error_code (j); - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - dr.hr.ec = TALER_JSON_get_error_code (j); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange deposit\n", - (unsigned int) response_code, - dr.hr.ec); - GNUNET_break_op (0); - break; - } - if (TALER_EC_NONE == dr.hr.ec) - dr.hr.hint = NULL; - else - dr.hr.hint = TALER_ErrorCode_get_hint (dr.hr.ec); - pch->cb (pch->cb_cls, - &dr); - TALER_EXCHANGE_purse_deposit_cancel (pch); -} - - -struct TALER_EXCHANGE_PurseDepositHandle * -TALER_EXCHANGE_purse_deposit ( - struct GNUNET_CURL_Context *ctx, - const char *url, - struct TALER_EXCHANGE_Keys *keys, - const char *purse_exchange_url, - const struct TALER_PurseContractPublicKeyP *purse_pub, - uint8_t min_age, - unsigned int num_deposits, - const struct TALER_EXCHANGE_PurseDeposit deposits[static num_deposits], - TALER_EXCHANGE_PurseDepositCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_PurseDepositHandle *pch; - json_t *create_obj; - json_t *deposit_arr; - CURL *eh; - char arg_str[sizeof (pch->purse_pub) * 2 + 32]; - - // FIXME: use purse_exchange_url for wad transfers (#7271) - (void) purse_exchange_url; - if (0 == num_deposits) - { - GNUNET_break (0); - return NULL; - } - pch = GNUNET_new (struct TALER_EXCHANGE_PurseDepositHandle); - pch->purse_pub = *purse_pub; - pch->cb = cb; - pch->cb_cls = cb_cls; - { - char pub_str[sizeof (pch->purse_pub) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string ( - &pch->purse_pub, - sizeof (pch->purse_pub), - pub_str, - sizeof (pub_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "purses/%s/deposit", - pub_str); - } - pch->url = TALER_url_join (url, - arg_str, - NULL); - if (NULL == pch->url) - { - GNUNET_break (0); - GNUNET_free (pch); - return NULL; - } - deposit_arr = json_array (); - GNUNET_assert (NULL != deposit_arr); - pch->base_url = GNUNET_strdup (url); - pch->num_deposits = num_deposits; - pch->coins = GNUNET_new_array (num_deposits, - struct Coin); - for (unsigned int i = 0; i<num_deposits; i++) - { - const struct TALER_EXCHANGE_PurseDeposit *deposit = &deposits[i]; - const struct TALER_AgeCommitmentProof *acp = deposit->age_commitment_proof; - struct Coin *coin = &pch->coins[i]; - json_t *jdeposit; - struct TALER_AgeCommitmentHashP *achp = NULL; - struct TALER_AgeAttestationP attest; - struct TALER_AgeAttestationP *attestp = NULL; - - if (NULL != acp) - { - TALER_age_commitment_hash (&acp->commitment, - &coin->ahac); - achp = &coin->ahac; - if (GNUNET_OK != - TALER_age_commitment_attest (acp, - min_age, - &attest)) - { - GNUNET_break (0); - json_decref (deposit_arr); - GNUNET_free (pch->base_url); - GNUNET_free (pch->coins); - GNUNET_free (pch->url); - GNUNET_free (pch); - return NULL; - } - attestp = &attest; - } - GNUNET_CRYPTO_eddsa_key_get_public (&deposit->coin_priv.eddsa_priv, - &coin->coin_pub.eddsa_pub); - coin->h_denom_pub = deposit->h_denom_pub; - coin->contribution = deposit->amount; - TALER_wallet_purse_deposit_sign ( - pch->base_url, - &pch->purse_pub, - &deposit->amount, - &coin->h_denom_pub, - &coin->ahac, - &deposit->coin_priv, - &coin->coin_sig); - jdeposit = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_data_auto ("h_age_commitment", - achp)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_data_auto ("age_attestation", - attestp)), - TALER_JSON_pack_amount ("amount", - &deposit->amount), - GNUNET_JSON_pack_data_auto ("denom_pub_hash", - &deposit->h_denom_pub), - TALER_JSON_pack_denom_sig ("ub_sig", - &deposit->denom_sig), - GNUNET_JSON_pack_data_auto ("coin_pub", - &coin->coin_pub), - GNUNET_JSON_pack_data_auto ("coin_sig", - &coin->coin_sig)); - GNUNET_assert (0 == - json_array_append_new (deposit_arr, - jdeposit)); - } - create_obj = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_array_steal ("deposits", - deposit_arr)); - GNUNET_assert (NULL != create_obj); - eh = TALER_EXCHANGE_curl_easy_get_ (pch->url); - if ( (NULL == eh) || - (GNUNET_OK != - TALER_curl_easy_post (&pch->ctx, - eh, - create_obj)) ) - { - GNUNET_break (0); - if (NULL != eh) - curl_easy_cleanup (eh); - json_decref (create_obj); - GNUNET_free (pch->base_url); - GNUNET_free (pch->url); - GNUNET_free (pch->coins); - GNUNET_free (pch); - return NULL; - } - json_decref (create_obj); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "URL for purse deposit: `%s'\n", - pch->url); - pch->keys = TALER_EXCHANGE_keys_incref (keys); - pch->job = GNUNET_CURL_job_add2 (ctx, - eh, - pch->ctx.headers, - &handle_purse_deposit_finished, - pch); - return pch; -} - - -void -TALER_EXCHANGE_purse_deposit_cancel ( - struct TALER_EXCHANGE_PurseDepositHandle *pch) -{ - if (NULL != pch->job) - { - GNUNET_CURL_job_cancel (pch->job); - pch->job = NULL; - } - GNUNET_free (pch->base_url); - GNUNET_free (pch->url); - GNUNET_free (pch->coins); - TALER_EXCHANGE_keys_decref (pch->keys); - TALER_curl_easy_post_finished (&pch->ctx); - GNUNET_free (pch); -} - - -/* end of exchange_api_purse_deposit.c */ diff --git a/src/lib/exchange_api_purse_merge.c b/src/lib/exchange_api_purse_merge.c @@ -1,454 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2022-2023 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> - */ -/** - * @file lib/exchange_api_purse_merge.c - * @brief Implementation of the client to merge a purse - * into an account - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_json_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_json_lib.h" -#include "taler/taler_exchange_service.h" -#include "exchange_api_handle.h" -#include "exchange_api_common.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * @brief A purse merge with deposit handle - */ -struct TALER_EXCHANGE_AccountMergeHandle -{ - - /** - * The keys of the exchange this request handle will use - */ - struct TALER_EXCHANGE_Keys *keys; - - /** - * The url for this request. - */ - char *url; - - /** - * Context for #TEH_curl_easy_post(). Keeps the data that must - * persist for Curl to make the upload. - */ - struct TALER_CURL_PostContext ctx; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_AccountMergeCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Base URL of the provider hosting the @e reserve_pub. - */ - char *provider_url; - - /** - * Signature for our operation. - */ - struct TALER_PurseMergeSignatureP merge_sig; - - /** - * Expected value in the purse after fees. - */ - struct TALER_Amount purse_value_after_fees; - - /** - * Public key of the reserve public key. - */ - struct TALER_ReservePublicKeyP reserve_pub; - - /** - * Public key of the purse. - */ - struct TALER_PurseContractPublicKeyP purse_pub; - - /** - * Hash over the purse's contrac terms. - */ - struct TALER_PrivateContractHashP h_contract_terms; - - /** - * When does the purse expire. - */ - struct GNUNET_TIME_Timestamp purse_expiration; - - /** - * Our merge key. - */ - struct TALER_PurseMergePrivateKeyP merge_priv; - - /** - * Reserve signature affirming the merge. - */ - struct TALER_ReserveSignatureP reserve_sig; - -}; - - -/** - * Function called when we're done processing the - * HTTP /purse/$PID/merge request. - * - * @param cls the `struct TALER_EXCHANGE_AccountMergeHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_purse_merge_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_AccountMergeHandle *pch = cls; - const json_t *j = response; - struct TALER_EXCHANGE_AccountMergeResponse dr = { - .hr.reply = j, - .hr.http_status = (unsigned int) response_code, - .reserve_sig = &pch->reserve_sig - }; - - pch->job = NULL; - switch (response_code) - { - case 0: - dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - { - struct TALER_Amount total_deposited; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("exchange_sig", - &dr.details.ok.exchange_sig), - GNUNET_JSON_spec_fixed_auto ("exchange_pub", - &dr.details.ok.exchange_pub), - GNUNET_JSON_spec_timestamp ("exchange_timestamp", - &dr.details.ok.etime), - TALER_JSON_spec_amount ("merge_amount", - pch->purse_value_after_fees.currency, - &total_deposited), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - if (GNUNET_OK != - TALER_EXCHANGE_test_signing_key (pch->keys, - &dr.details.ok.exchange_pub)) - { - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_EXCHANGE_PURSE_MERGE_EXCHANGE_SIGNATURE_INVALID; - break; - } - if (GNUNET_OK != - TALER_exchange_online_purse_merged_verify ( - dr.details.ok.etime, - pch->purse_expiration, - &pch->purse_value_after_fees, - &pch->purse_pub, - &pch->h_contract_terms, - &pch->reserve_pub, - pch->provider_url, - &dr.details.ok.exchange_pub, - &dr.details.ok.exchange_sig)) - { - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_EXCHANGE_PURSE_MERGE_EXCHANGE_SIGNATURE_INVALID; - break; - } - } - break; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_PAYMENT_REQUIRED: - /* purse was not (yet) full */ - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_FORBIDDEN: - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - /* Nothing really to verify, exchange says one of the signatures is - invalid; as we checked them, this should never happen, we - should pass the JSON reply to the application */ - break; - case MHD_HTTP_NOT_FOUND: - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - break; - case MHD_HTTP_CONFLICT: - { - struct TALER_PurseMergePublicKeyP merge_pub; - - GNUNET_CRYPTO_eddsa_key_get_public (&pch->merge_priv.eddsa_priv, - &merge_pub.eddsa_pub); - if (GNUNET_OK != - TALER_EXCHANGE_check_purse_merge_conflict_ ( - &pch->merge_sig, - &merge_pub, - &pch->purse_pub, - pch->provider_url, - j)) - { - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - break; - } - break; - case MHD_HTTP_GONE: - /* could happen if denomination was revoked */ - /* Note: one might want to check /keys for revocation - signature here, alas tricky in case our /keys - is outdated => left to clients */ - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: - { - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_uint64 ( - "requirement_row", - &dr.details.unavailable_for_legal_reasons.requirement_row), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - } - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange deposit\n", - (unsigned int) response_code, - dr.hr.ec); - GNUNET_break_op (0); - break; - } - pch->cb (pch->cb_cls, - &dr); - TALER_EXCHANGE_account_merge_cancel (pch); -} - - -struct TALER_EXCHANGE_AccountMergeHandle * -TALER_EXCHANGE_account_merge ( - struct GNUNET_CURL_Context *ctx, - const char *url, - struct TALER_EXCHANGE_Keys *keys, - const char *reserve_exchange_url, - const struct TALER_ReservePrivateKeyP *reserve_priv, - const struct TALER_PurseContractPublicKeyP *purse_pub, - const struct TALER_PurseMergePrivateKeyP *merge_priv, - const struct TALER_PrivateContractHashP *h_contract_terms, - uint8_t min_age, - const struct TALER_Amount *purse_value_after_fees, - struct GNUNET_TIME_Timestamp purse_expiration, - struct GNUNET_TIME_Timestamp merge_timestamp, - TALER_EXCHANGE_AccountMergeCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_AccountMergeHandle *pch; - json_t *merge_obj; - CURL *eh; - char arg_str[sizeof (pch->purse_pub) * 2 + 32]; - struct TALER_NormalizedPayto reserve_url; - - pch = GNUNET_new (struct TALER_EXCHANGE_AccountMergeHandle); - pch->merge_priv = *merge_priv; - pch->cb = cb; - pch->cb_cls = cb_cls; - pch->purse_pub = *purse_pub; - pch->h_contract_terms = *h_contract_terms; - pch->purse_expiration = purse_expiration; - pch->purse_value_after_fees = *purse_value_after_fees; - if (NULL == reserve_exchange_url) - pch->provider_url = GNUNET_strdup (url); - else - pch->provider_url = GNUNET_strdup (reserve_exchange_url); - GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, - &pch->reserve_pub.eddsa_pub); - - { - char pub_str[sizeof (*purse_pub) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string ( - purse_pub, - sizeof (*purse_pub), - pub_str, - sizeof (pub_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "purses/%s/merge", - pub_str); - } - reserve_url = TALER_reserve_make_payto (pch->provider_url, - &pch->reserve_pub); - if (NULL == reserve_url.normalized_payto) - { - GNUNET_break (0); - GNUNET_free (pch->provider_url); - GNUNET_free (pch); - return NULL; - } - pch->url = TALER_url_join (url, - arg_str, - NULL); - if (NULL == pch->url) - { - GNUNET_break (0); - GNUNET_free (reserve_url.normalized_payto); - GNUNET_free (pch->provider_url); - GNUNET_free (pch); - return NULL; - } - TALER_wallet_purse_merge_sign (reserve_url, - merge_timestamp, - purse_pub, - merge_priv, - &pch->merge_sig); - { - struct TALER_Amount zero_purse_fee; - - GNUNET_assert (GNUNET_OK == - TALER_amount_set_zero (purse_value_after_fees->currency, - &zero_purse_fee)); - TALER_wallet_account_merge_sign (merge_timestamp, - purse_pub, - purse_expiration, - h_contract_terms, - purse_value_after_fees, - &zero_purse_fee, - min_age, - TALER_WAMF_MODE_MERGE_FULLY_PAID_PURSE, - reserve_priv, - &pch->reserve_sig); - } - merge_obj = GNUNET_JSON_PACK ( - TALER_JSON_pack_normalized_payto ("payto_uri", - reserve_url), - GNUNET_JSON_pack_data_auto ("merge_sig", - &pch->merge_sig), - GNUNET_JSON_pack_data_auto ("reserve_sig", - &pch->reserve_sig), - GNUNET_JSON_pack_timestamp ("merge_timestamp", - merge_timestamp)); - GNUNET_assert (NULL != merge_obj); - GNUNET_free (reserve_url.normalized_payto); - eh = TALER_EXCHANGE_curl_easy_get_ (pch->url); - if ( (NULL == eh) || - (GNUNET_OK != - TALER_curl_easy_post (&pch->ctx, - eh, - merge_obj)) ) - { - GNUNET_break (0); - if (NULL != eh) - curl_easy_cleanup (eh); - json_decref (merge_obj); - GNUNET_free (pch->provider_url); - GNUNET_free (pch->url); - GNUNET_free (pch); - return NULL; - } - json_decref (merge_obj); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "URL for purse merge: `%s'\n", - pch->url); - pch->keys = TALER_EXCHANGE_keys_incref (keys); - pch->job = GNUNET_CURL_job_add2 (ctx, - eh, - pch->ctx.headers, - &handle_purse_merge_finished, - pch); - return pch; -} - - -void -TALER_EXCHANGE_account_merge_cancel ( - struct TALER_EXCHANGE_AccountMergeHandle *pch) -{ - if (NULL != pch->job) - { - GNUNET_CURL_job_cancel (pch->job); - pch->job = NULL; - } - GNUNET_free (pch->url); - GNUNET_free (pch->provider_url); - TALER_curl_easy_post_finished (&pch->ctx); - TALER_EXCHANGE_keys_decref (pch->keys); - GNUNET_free (pch); -} - - -/* end of exchange_api_purse_merge.c */ diff --git a/src/lib/exchange_api_purses_get.c b/src/lib/exchange_api_purses_get.c @@ -1,300 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2022-2023 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_purses_get.c - * @brief Implementation of the /purses/ GET request - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_json_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_json_lib.h" -#include "taler/taler_exchange_service.h" -#include "exchange_api_handle.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * @brief A Contract Get Handle - */ -struct TALER_EXCHANGE_PurseGetHandle -{ - - /** - * The keys of the exchange this request handle will use - */ - struct TALER_EXCHANGE_Keys *keys; - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_PurseGetCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - -}; - - -/** - * Function called when we're done processing the - * HTTP /purses/$PID GET request. - * - * @param cls the `struct TALER_EXCHANGE_PurseGetHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_purse_get_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_PurseGetHandle *pgh = cls; - const json_t *j = response; - struct TALER_EXCHANGE_PurseGetResponse dr = { - .hr.reply = j, - .hr.http_status = (unsigned int) response_code - }; - - pgh->job = NULL; - switch (response_code) - { - case 0: - dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - { - bool no_merge = false; - bool no_deposit = false; - struct TALER_ExchangePublicKeyP exchange_pub; - struct TALER_ExchangeSignatureP exchange_sig; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_timestamp ("merge_timestamp", - &dr.details.ok.merge_timestamp), - &no_merge), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_timestamp ("deposit_timestamp", - &dr.details.ok.deposit_timestamp), - &no_deposit), - TALER_JSON_spec_amount_any ("balance", - &dr.details.ok.balance), - GNUNET_JSON_spec_timestamp ("purse_expiration", - &dr.details.ok.purse_expiration), - GNUNET_JSON_spec_fixed_auto ("exchange_pub", - &exchange_pub), - GNUNET_JSON_spec_fixed_auto ("exchange_sig", - &exchange_sig), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - - if (GNUNET_OK != - TALER_EXCHANGE_test_signing_key (pgh->keys, - &exchange_pub)) - { - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_EXCHANGE_PURSES_GET_INVALID_SIGNATURE_BY_EXCHANGE; - break; - } - if (GNUNET_OK != - TALER_exchange_online_purse_status_verify ( - dr.details.ok.merge_timestamp, - dr.details.ok.deposit_timestamp, - &dr.details.ok.balance, - &exchange_pub, - &exchange_sig)) - { - GNUNET_break_op (0); - dr.hr.http_status = 0; - dr.hr.ec = TALER_EC_EXCHANGE_PURSES_GET_INVALID_SIGNATURE_BY_EXCHANGE; - break; - } - pgh->cb (pgh->cb_cls, - &dr); - TALER_EXCHANGE_purse_get_cancel (pgh); - return; - } - case MHD_HTTP_BAD_REQUEST: - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - break; - case MHD_HTTP_FORBIDDEN: - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - /* Nothing really to verify, exchange says one of the signatures is - invalid; as we checked them, this should never happen, we - should pass the JSON reply to the application */ - break; - case MHD_HTTP_NOT_FOUND: - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - /* Exchange does not know about transaction; - we should pass the reply to the application */ - break; - case MHD_HTTP_GONE: - /* purse expired */ - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - break; - default: - /* unexpected response code */ - dr.hr.ec = TALER_JSON_get_error_code (j); - dr.hr.hint = TALER_JSON_get_error_hint (j); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange GET purses\n", - (unsigned int) response_code, - (int) dr.hr.ec); - GNUNET_break_op (0); - break; - } - pgh->cb (pgh->cb_cls, - &dr); - TALER_EXCHANGE_purse_get_cancel (pgh); -} - - -struct TALER_EXCHANGE_PurseGetHandle * -TALER_EXCHANGE_purse_get ( - struct GNUNET_CURL_Context *ctx, - const char *url, - struct TALER_EXCHANGE_Keys *keys, - const struct TALER_PurseContractPublicKeyP *purse_pub, - struct GNUNET_TIME_Relative timeout, - bool wait_for_merge, - TALER_EXCHANGE_PurseGetCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_PurseGetHandle *pgh; - CURL *eh; - char arg_str[sizeof (*purse_pub) * 2 + 64]; - unsigned int tms - = (unsigned int) timeout.rel_value_us - / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us; - - pgh = GNUNET_new (struct TALER_EXCHANGE_PurseGetHandle); - pgh->cb = cb; - pgh->cb_cls = cb_cls; - { - char cpub_str[sizeof (*purse_pub) * 2]; - char *end; - char timeout_str[32]; - - end = GNUNET_STRINGS_data_to_string (purse_pub, - sizeof (*purse_pub), - cpub_str, - sizeof (cpub_str)); - *end = '\0'; - GNUNET_snprintf (timeout_str, - sizeof (timeout_str), - "%u", - tms); - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "purses/%s/%s", - cpub_str, - wait_for_merge - ? "merge" - : "deposit"); - pgh->url = TALER_url_join (url, - arg_str, - "timeout_ms", - (0 == tms) - ? NULL - : timeout_str, - NULL); - } - if (NULL == pgh->url) - { - GNUNET_free (pgh); - return NULL; - } - eh = TALER_EXCHANGE_curl_easy_get_ (pgh->url); - if (NULL == eh) - { - GNUNET_break (0); - GNUNET_free (pgh->url); - GNUNET_free (pgh); - return NULL; - } - if (0 != tms) - { - GNUNET_break (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_TIMEOUT_MS, - (long) (tms + 100L))); - } - pgh->job = GNUNET_CURL_job_add (ctx, - eh, - &handle_purse_get_finished, - pgh); - pgh->keys = TALER_EXCHANGE_keys_incref (keys); - return pgh; -} - - -void -TALER_EXCHANGE_purse_get_cancel ( - struct TALER_EXCHANGE_PurseGetHandle *pgh) -{ - if (NULL != pgh->job) - { - GNUNET_CURL_job_cancel (pgh->job); - pgh->job = NULL; - } - GNUNET_free (pgh->url); - TALER_EXCHANGE_keys_decref (pgh->keys); - GNUNET_free (pgh); -} - - -/* end of exchange_api_purses_get.c */ diff --git a/src/lib/exchange_api_recoup.c b/src/lib/exchange_api_recoup.c @@ -1,382 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2017-2023 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_recoup.c - * @brief Implementation of the /recoup request of the exchange's HTTP API - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_json_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_json_lib.h" -#include "taler/taler_exchange_service.h" -#include "exchange_api_common.h" -#include "exchange_api_handle.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * @brief A Recoup Handle - */ -struct TALER_EXCHANGE_RecoupHandle -{ - - /** - * The keys of the exchange this request handle will use - */ - struct TALER_EXCHANGE_Keys *keys; - - /** - * The url for this request. - */ - char *url; - - /** - * Context for #TEH_curl_easy_post(). Keeps the data that must - * persist for Curl to make the upload. - */ - struct TALER_CURL_PostContext ctx; - - /** - * Denomination key of the coin. - */ - struct TALER_EXCHANGE_DenomPublicKey pk; - - /** - * Our signature requesting the recoup. - */ - struct TALER_CoinSpendSignatureP coin_sig; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_RecoupResultCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Public key of the coin we are trying to get paid back. - */ - struct TALER_CoinSpendPublicKeyP coin_pub; - -}; - - -/** - * Parse a recoup response. If it is valid, call the callback. - * - * @param ph recoup handle - * @param json json reply with the signature - * @return #GNUNET_OK if the signature is valid and we called the callback; - * #GNUNET_SYSERR if not (callback must still be called) - */ -static enum GNUNET_GenericReturnValue -process_recoup_response (const struct TALER_EXCHANGE_RecoupHandle *ph, - const json_t *json) -{ - struct TALER_EXCHANGE_RecoupResponse rr = { - .hr.reply = json, - .hr.http_status = MHD_HTTP_OK - }; - struct GNUNET_JSON_Specification spec_withdraw[] = { - GNUNET_JSON_spec_fixed_auto ("reserve_pub", - &rr.details.ok.reserve_pub), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec_withdraw, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - ph->cb (ph->cb_cls, - &rr); - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /recoup request. - * - * @param cls the `struct TALER_EXCHANGE_RecoupHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_recoup_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_RecoupHandle *ph = cls; - const json_t *j = response; - struct TALER_EXCHANGE_RecoupResponse rr = { - .hr.reply = j, - .hr.http_status = (unsigned int) response_code - }; - - ph->job = NULL; - switch (response_code) - { - case 0: - rr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - if (GNUNET_OK != - process_recoup_response (ph, - j)) - { - GNUNET_break_op (0); - rr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - rr.hr.http_status = 0; - break; - } - TALER_EXCHANGE_recoup_cancel (ph); - return; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - rr.hr.ec = TALER_JSON_get_error_code (j); - rr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_CONFLICT: - { - struct TALER_Amount min_key; - - rr.hr.ec = TALER_JSON_get_error_code (j); - rr.hr.hint = TALER_JSON_get_error_hint (j); - if (GNUNET_OK != - TALER_EXCHANGE_get_min_denomination_ (ph->keys, - &min_key)) - { - GNUNET_break (0); - rr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - rr.hr.http_status = 0; - break; - } - break; - } - case MHD_HTTP_FORBIDDEN: - /* Nothing really to verify, exchange says one of the signatures is - invalid; as we checked them, this should never happen, we - should pass the JSON reply to the application */ - rr.hr.ec = TALER_JSON_get_error_code (j); - rr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - rr.hr.ec = TALER_JSON_get_error_code (j); - rr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_GONE: - /* Kind of normal: the money was already sent to the merchant - (it was too late for the refund). */ - rr.hr.ec = TALER_JSON_get_error_code (j); - rr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - rr.hr.ec = TALER_JSON_get_error_code (j); - rr.hr.hint = TALER_JSON_get_error_hint (j); - break; - default: - /* unexpected response code */ - rr.hr.ec = TALER_JSON_get_error_code (j); - rr.hr.hint = TALER_JSON_get_error_hint (j); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange recoup\n", - (unsigned int) response_code, - (int) rr.hr.ec); - GNUNET_break (0); - break; - } - ph->cb (ph->cb_cls, - &rr); - TALER_EXCHANGE_recoup_cancel (ph); -} - - -struct TALER_EXCHANGE_RecoupHandle * -TALER_EXCHANGE_recoup ( - struct GNUNET_CURL_Context *ctx, - const char *url, - struct TALER_EXCHANGE_Keys *keys, - const struct TALER_EXCHANGE_DenomPublicKey *pk, - const struct TALER_DenominationSignature *denom_sig, - const struct TALER_ExchangeBlindingValues *blinding_values, - const struct TALER_PlanchetMasterSecretP *ps, - const struct TALER_HashBlindedPlanchetsP *h_planchets, - TALER_EXCHANGE_RecoupResultCallback recoup_cb, - void *recoup_cb_cls) -{ - struct TALER_EXCHANGE_RecoupHandle *ph; - struct TALER_DenominationHashP h_denom_pub; - json_t *recoup_obj; - CURL *eh; - char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32]; - struct TALER_CoinSpendPrivateKeyP coin_priv; - union GNUNET_CRYPTO_BlindingSecretP bks; - - ph = GNUNET_new (struct TALER_EXCHANGE_RecoupHandle); - TALER_planchet_setup_coin_priv (ps, - blinding_values, - &coin_priv); - TALER_planchet_blinding_secret_create (ps, - blinding_values, - &bks); - GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv, - &ph->coin_pub.eddsa_pub); - TALER_denom_pub_hash (&pk->key, - &h_denom_pub); - TALER_wallet_recoup_sign (&h_denom_pub, - &bks, - &coin_priv, - &ph->coin_sig); - recoup_obj = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_data_auto ("denom_pub_hash", - &h_denom_pub), - TALER_JSON_pack_denom_sig ("denom_sig", - denom_sig), - TALER_JSON_pack_exchange_blinding_values ("ewv", - blinding_values), - GNUNET_JSON_pack_data_auto ("coin_sig", - &ph->coin_sig), - GNUNET_JSON_pack_data_auto ("h_planchets", - h_planchets), - GNUNET_JSON_pack_data_auto ("coin_blind_key_secret", - &bks)); - switch (denom_sig->unblinded_sig->cipher) - { - case GNUNET_CRYPTO_BSA_INVALID: - json_decref (recoup_obj); - GNUNET_break (0); - GNUNET_free (ph); - return NULL; - case GNUNET_CRYPTO_BSA_RSA: - break; - case GNUNET_CRYPTO_BSA_CS: - { - union GNUNET_CRYPTO_BlindSessionNonce nonce; - - /* NOTE: this is not elegant, and as per the note in TALER_coin_ev_hash() - it is not strictly clear that the nonce is needed. Best case would be - to find a way to include it more 'naturally' somehow, for example with - the variant union version of bks! */ - TALER_cs_withdraw_nonce_derive (ps, - &nonce.cs_nonce); - GNUNET_assert ( - 0 == - json_object_set_new (recoup_obj, - "nonce", - GNUNET_JSON_from_data_auto ( - &nonce))); - } - } - - { - char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string ( - &ph->coin_pub, - sizeof (struct TALER_CoinSpendPublicKeyP), - pub_str, - sizeof (pub_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "coins/%s/recoup", - pub_str); - } - - ph->pk = *pk; - memset (&ph->pk.key, - 0, - sizeof (ph->pk.key)); /* zero out, as lifetime cannot be warranted */ - ph->cb = recoup_cb; - ph->cb_cls = recoup_cb_cls; - ph->url = TALER_url_join (url, - arg_str, - NULL); - if (NULL == ph->url) - { - json_decref (recoup_obj); - GNUNET_free (ph); - return NULL; - } - eh = TALER_EXCHANGE_curl_easy_get_ (ph->url); - if ( (NULL == eh) || - (GNUNET_OK != - TALER_curl_easy_post (&ph->ctx, - eh, - recoup_obj)) ) - { - GNUNET_break (0); - if (NULL != eh) - curl_easy_cleanup (eh); - json_decref (recoup_obj); - GNUNET_free (ph->url); - GNUNET_free (ph); - return NULL; - } - json_decref (recoup_obj); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "URL for recoup: `%s'\n", - ph->url); - ph->keys = TALER_EXCHANGE_keys_incref (keys); - ph->job = GNUNET_CURL_job_add2 (ctx, - eh, - ph->ctx.headers, - &handle_recoup_finished, - ph); - return ph; -} - - -void -TALER_EXCHANGE_recoup_cancel (struct TALER_EXCHANGE_RecoupHandle *ph) -{ - if (NULL != ph->job) - { - GNUNET_CURL_job_cancel (ph->job); - ph->job = NULL; - } - GNUNET_free (ph->url); - TALER_curl_easy_post_finished (&ph->ctx); - TALER_EXCHANGE_keys_decref (ph->keys); - GNUNET_free (ph); -} - - -/* end of exchange_api_recoup.c */ diff --git a/src/lib/exchange_api_recoup_refresh.c b/src/lib/exchange_api_recoup_refresh.c @@ -1,361 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2017-2023 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_recoup_refresh.c - * @brief Implementation of the /recoup-refresh request of the exchange's HTTP API - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_json_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_json_lib.h" -#include "taler/taler_exchange_service.h" -#include "exchange_api_common.h" -#include "exchange_api_handle.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * @brief A Recoup Handle - */ -struct TALER_EXCHANGE_RecoupRefreshHandle -{ - - /** - * The keys of the exchange this request handle will use - */ - struct TALER_EXCHANGE_Keys *keys; - - /** - * The url for this request. - */ - char *url; - - /** - * Context for #TEH_curl_easy_post(). Keeps the data that must - * persist for Curl to make the upload. - */ - struct TALER_CURL_PostContext ctx; - - /** - * Denomination key of the coin. - */ - struct TALER_EXCHANGE_DenomPublicKey pk; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_RecoupRefreshResultCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Public key of the coin we are trying to get paid back. - */ - struct TALER_CoinSpendPublicKeyP coin_pub; - - /** - * Signature affirming the recoup-refresh operation. - */ - struct TALER_CoinSpendSignatureP coin_sig; - -}; - - -/** - * Parse a recoup-refresh response. If it is valid, call the callback. - * - * @param ph recoup handle - * @param json json reply with the signature - * @return #GNUNET_OK if the signature is valid and we called the callback; - * #GNUNET_SYSERR if not (callback must still be called) - */ -static enum GNUNET_GenericReturnValue -process_recoup_response ( - const struct TALER_EXCHANGE_RecoupRefreshHandle *ph, - const json_t *json) -{ - struct TALER_EXCHANGE_RecoupRefreshResponse rrr = { - .hr.reply = json, - .hr.http_status = MHD_HTTP_OK - }; - struct GNUNET_JSON_Specification spec_refresh[] = { - GNUNET_JSON_spec_fixed_auto ("old_coin_pub", - &rrr.details.ok.old_coin_pub), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec_refresh, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - ph->cb (ph->cb_cls, - &rrr); - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /recoup-refresh request. - * - * @param cls the `struct TALER_EXCHANGE_RecoupRefreshHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_recoup_refresh_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_RecoupRefreshHandle *ph = cls; - const json_t *j = response; - struct TALER_EXCHANGE_RecoupRefreshResponse rrr = { - .hr.reply = j, - .hr.http_status = (unsigned int) response_code - }; - - ph->job = NULL; - switch (response_code) - { - case 0: - rrr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - if (GNUNET_OK != - process_recoup_response (ph, - j)) - { - GNUNET_break_op (0); - rrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - rrr.hr.http_status = 0; - break; - } - TALER_EXCHANGE_recoup_refresh_cancel (ph); - return; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - rrr.hr.ec = TALER_JSON_get_error_code (j); - rrr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_FORBIDDEN: - /* Nothing really to verify, exchange says one of the signatures is - invalid; as we checked them, this should never happen, we - should pass the JSON reply to the application */ - rrr.hr.ec = TALER_JSON_get_error_code (j); - rrr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - rrr.hr.ec = TALER_JSON_get_error_code (j); - rrr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_CONFLICT: - rrr.hr.ec = TALER_JSON_get_error_code (j); - rrr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_GONE: - /* Kind of normal: the money was already sent to the merchant - (it was too late for the refund). */ - rrr.hr.ec = TALER_JSON_get_error_code (j); - rrr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - rrr.hr.ec = TALER_JSON_get_error_code (j); - rrr.hr.hint = TALER_JSON_get_error_hint (j); - break; - default: - /* unexpected response code */ - rrr.hr.ec = TALER_JSON_get_error_code (j); - rrr.hr.hint = TALER_JSON_get_error_hint (j); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange recoup\n", - (unsigned int) response_code, - (int) rrr.hr.ec); - GNUNET_break (0); - break; - } - ph->cb (ph->cb_cls, - &rrr); - TALER_EXCHANGE_recoup_refresh_cancel (ph); -} - - -#pragma message "TODO[oec]: rewrite TALER_EXCHANGE_RecoupRefreshHandle" -struct TALER_EXCHANGE_RecoupRefreshHandle * -TALER_EXCHANGE_recoup_refresh ( - struct GNUNET_CURL_Context *ctx, - const char *url, - struct TALER_EXCHANGE_Keys *keys, - const struct TALER_EXCHANGE_DenomPublicKey *pk, - const struct TALER_DenominationSignature *denom_sig, - const struct TALER_ExchangeBlindingValues *blinding_values, - const struct TALER_PublicRefreshMasterSeedP *rms, - const struct TALER_PlanchetMasterSecretP *ps, - unsigned int idx, - TALER_EXCHANGE_RecoupRefreshResultCallback recoup_cb, - void *recoup_cb_cls) -{ - struct TALER_EXCHANGE_RecoupRefreshHandle *ph; - struct TALER_DenominationHashP h_denom_pub; - json_t *recoup_obj; - CURL *eh; - char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32]; - struct TALER_CoinSpendPrivateKeyP coin_priv; - union GNUNET_CRYPTO_BlindingSecretP bks; - - GNUNET_assert (NULL != recoup_cb); - ph = GNUNET_new (struct TALER_EXCHANGE_RecoupRefreshHandle); - ph->pk = *pk; - memset (&ph->pk.key, - 0, - sizeof (ph->pk.key)); /* zero out, as lifetime cannot be warranted */ - ph->cb = recoup_cb; - ph->cb_cls = recoup_cb_cls; - TALER_planchet_setup_coin_priv (ps, - blinding_values, - &coin_priv); - TALER_planchet_blinding_secret_create (ps, - blinding_values, - &bks); - GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv, - &ph->coin_pub.eddsa_pub); - TALER_denom_pub_hash (&pk->key, - &h_denom_pub); - TALER_wallet_recoup_refresh_sign (&h_denom_pub, - &bks, - &coin_priv, - &ph->coin_sig); - recoup_obj = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_data_auto ("denom_pub_hash", - &h_denom_pub), - TALER_JSON_pack_denom_sig ("denom_sig", - denom_sig), - TALER_JSON_pack_exchange_blinding_values ("ewv", - blinding_values), - GNUNET_JSON_pack_data_auto ("coin_sig", - &ph->coin_sig), - GNUNET_JSON_pack_data_auto ("coin_blind_key_secret", - &bks)); - - switch (denom_sig->unblinded_sig->cipher) - { - case GNUNET_CRYPTO_BSA_INVALID: - json_decref (recoup_obj); - GNUNET_break (0); - GNUNET_free (ph); - return NULL; - case GNUNET_CRYPTO_BSA_RSA: - break; - case GNUNET_CRYPTO_BSA_CS: - { - - } - break; - } - - { - char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string ( - &ph->coin_pub, - sizeof (struct TALER_CoinSpendPublicKeyP), - pub_str, - sizeof (pub_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "coins/%s/recoup-refresh", - pub_str); - } - - ph->url = TALER_url_join (url, - arg_str, - NULL); - if (NULL == ph->url) - { - json_decref (recoup_obj); - GNUNET_free (ph); - return NULL; - } - eh = TALER_EXCHANGE_curl_easy_get_ (ph->url); - if ( (NULL == eh) || - (GNUNET_OK != - TALER_curl_easy_post (&ph->ctx, - eh, - recoup_obj)) ) - { - GNUNET_break (0); - if (NULL != eh) - curl_easy_cleanup (eh); - json_decref (recoup_obj); - GNUNET_free (ph->url); - GNUNET_free (ph); - return NULL; - } - json_decref (recoup_obj); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "URL for recoup-refresh: `%s'\n", - ph->url); - ph->keys = TALER_EXCHANGE_keys_incref (keys); - ph->job = GNUNET_CURL_job_add2 (ctx, - eh, - ph->ctx.headers, - &handle_recoup_refresh_finished, - ph); - return ph; -} - - -void -TALER_EXCHANGE_recoup_refresh_cancel ( - struct TALER_EXCHANGE_RecoupRefreshHandle *ph) -{ - if (NULL != ph->job) - { - GNUNET_CURL_job_cancel (ph->job); - ph->job = NULL; - } - GNUNET_free (ph->url); - TALER_curl_easy_post_finished (&ph->ctx); - TALER_EXCHANGE_keys_decref (ph->keys); - GNUNET_free (ph); -} - - -/* end of exchange_api_recoup_refresh.c */ diff --git a/src/lib/exchange_api_refund.c b/src/lib/exchange_api_refund.c @@ -1,484 +0,0 @@ -/* - This file is part of TALER - 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 General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_refund.c - * @brief Implementation of the /refund request of the exchange's HTTP API - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_json_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_json_lib.h" -#include "taler/taler_exchange_service.h" -#include "exchange_api_handle.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * @brief A Refund Handle - */ -struct TALER_EXCHANGE_RefundHandle -{ - - /** - * The keys of the exchange this request handle will use - */ - struct TALER_EXCHANGE_Keys *keys; - - /** - * The url for this request. - */ - char *url; - - /** - * Context for #TEH_curl_easy_post(). Keeps the data that must - * persist for Curl to make the upload. - */ - struct TALER_CURL_PostContext ctx; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_RefundCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Hash over the proposal data to identify the contract - * which is being refunded. - */ - struct TALER_PrivateContractHashP h_contract_terms; - - /** - * The coin's public key. This is the value that must have been - * signed (blindly) by the Exchange. - */ - struct TALER_CoinSpendPublicKeyP coin_pub; - - /** - * The Merchant's public key. Allows the merchant to later refund - * the transaction or to inquire about the wire transfer identifier. - */ - struct TALER_MerchantPublicKeyP merchant; - - /** - * Merchant-generated transaction ID for the refund. - */ - uint64_t rtransaction_id; - - /** - * Amount to be refunded, including refund fee charged by the - * exchange to the customer. - */ - struct TALER_Amount refund_amount; - -}; - - -/** - * Verify that the signature on the "200 OK" response - * from the exchange is valid. - * - * @param[in,out] rh refund handle (refund fee added) - * @param json json reply with the signature - * @param[out] exchange_pub set to the exchange's public key - * @param[out] exchange_sig set to the exchange's signature - * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not - */ -static enum GNUNET_GenericReturnValue -verify_refund_signature_ok (struct TALER_EXCHANGE_RefundHandle *rh, - const json_t *json, - struct TALER_ExchangePublicKeyP *exchange_pub, - struct TALER_ExchangeSignatureP *exchange_sig) -{ - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("exchange_sig", - exchange_sig), - GNUNET_JSON_spec_fixed_auto ("exchange_pub", - exchange_pub), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_EXCHANGE_test_signing_key (rh->keys, - exchange_pub)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_exchange_online_refund_confirmation_verify ( - &rh->h_contract_terms, - &rh->coin_pub, - &rh->merchant, - rh->rtransaction_id, - &rh->refund_amount, - exchange_pub, - exchange_sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_OK; -} - - -/** - * Verify that the information on the "412 Dependency Failed" response - * from the exchange is valid and indeed shows that there is a refund - * transaction ID reuse going on. - * - * @param[in,out] rh refund handle (refund fee added) - * @param json json reply with the signature - * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not - */ -static enum GNUNET_GenericReturnValue -verify_failed_dependency_ok (struct TALER_EXCHANGE_RefundHandle *rh, - const json_t *json) -{ - const json_t *h; - json_t *e; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_array_const ("history", - &h), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (1 != json_array_size (h)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - e = json_array_get (h, 0); - { - struct TALER_Amount amount; - const char *type; - struct TALER_MerchantSignatureP sig; - struct TALER_Amount refund_fee; - struct TALER_PrivateContractHashP h_contract_terms; - uint64_t rtransaction_id; - struct TALER_MerchantPublicKeyP merchant_pub; - struct GNUNET_JSON_Specification ispec[] = { - TALER_JSON_spec_amount_any ("amount", - &amount), - GNUNET_JSON_spec_string ("type", - &type), - TALER_JSON_spec_amount_any ("refund_fee", - &refund_fee), - GNUNET_JSON_spec_fixed_auto ("merchant_sig", - &sig), - GNUNET_JSON_spec_fixed_auto ("h_contract_terms", - &h_contract_terms), - GNUNET_JSON_spec_fixed_auto ("merchant_pub", - &merchant_pub), - GNUNET_JSON_spec_uint64 ("rtransaction_id", - &rtransaction_id), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (e, - ispec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_merchant_refund_verify (&rh->coin_pub, - &h_contract_terms, - rtransaction_id, - &amount, - &merchant_pub, - &sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if ( (rtransaction_id != rh->rtransaction_id) || - (0 != GNUNET_memcmp (&rh->h_contract_terms, - &h_contract_terms)) || - (0 != GNUNET_memcmp (&rh->merchant, - &merchant_pub)) || - (0 == TALER_amount_cmp (&rh->refund_amount, - &amount)) ) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - } - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /refund request. - * - * @param cls the `struct TALER_EXCHANGE_RefundHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_refund_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_RefundHandle *rh = cls; - const json_t *j = response; - struct TALER_EXCHANGE_RefundResponse rr = { - .hr.reply = j, - .hr.http_status = (unsigned int) response_code - }; - - rh->job = NULL; - switch (response_code) - { - case 0: - rr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - if (GNUNET_OK != - verify_refund_signature_ok (rh, - j, - &rr.details.ok.exchange_pub, - &rr.details.ok.exchange_sig)) - { - GNUNET_break_op (0); - rr.hr.http_status = 0; - rr.hr.ec = TALER_EC_EXCHANGE_REFUND_INVALID_SIGNATURE_BY_EXCHANGE; - } - break; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); also can happen if the currency - differs (which we should obviously never support). - Just pass JSON reply to the application */ - rr.hr.ec = TALER_JSON_get_error_code (j); - rr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_FORBIDDEN: - /* Nothing really to verify, exchange says one of the signatures is - invalid; as we checked them, this should never happen, we - should pass the JSON reply to the application */ - rr.hr.ec = TALER_JSON_get_error_code (j); - rr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - rr.hr.ec = TALER_JSON_get_error_code (j); - rr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_CONFLICT: - /* Requested total refunds exceed deposited amount */ - rr.hr.ec = TALER_JSON_get_error_code (j); - rr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_GONE: - /* Kind of normal: the money was already sent to the merchant - (it was too late for the refund). */ - rr.hr.ec = TALER_JSON_get_error_code (j); - rr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_FAILED_DEPENDENCY: - rr.hr.ec = TALER_JSON_get_error_code (j); - rr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_PRECONDITION_FAILED: - if (GNUNET_OK != - verify_failed_dependency_ok (rh, - j)) - { - GNUNET_break (0); - rr.hr.http_status = 0; - rr.hr.ec = TALER_EC_EXCHANGE_REFUND_INVALID_FAILURE_PROOF_BY_EXCHANGE; - rr.hr.hint = "failed precondition proof returned by exchange is invalid"; - break; - } - /* Two different refund requests were made about the same deposit, but - carrying identical refund transaction ids. */ - rr.hr.ec = TALER_JSON_get_error_code (j); - rr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - rr.hr.ec = TALER_JSON_get_error_code (j); - rr.hr.hint = TALER_JSON_get_error_hint (j); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - rr.hr.ec = TALER_JSON_get_error_code (j); - rr.hr.hint = TALER_JSON_get_error_hint (j); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange refund\n", - (unsigned int) response_code, - rr.hr.ec); - break; - } - rh->cb (rh->cb_cls, - &rr); - TALER_EXCHANGE_refund_cancel (rh); -} - - -struct TALER_EXCHANGE_RefundHandle * -TALER_EXCHANGE_refund ( - struct GNUNET_CURL_Context *ctx, - const char *url, - struct TALER_EXCHANGE_Keys *keys, - const struct TALER_Amount *amount, - const struct TALER_PrivateContractHashP *h_contract_terms, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - uint64_t rtransaction_id, - const struct TALER_MerchantPrivateKeyP *merchant_priv, - TALER_EXCHANGE_RefundCallback cb, - void *cb_cls) -{ - struct TALER_MerchantPublicKeyP merchant_pub; - struct TALER_MerchantSignatureP merchant_sig; - struct TALER_EXCHANGE_RefundHandle *rh; - json_t *refund_obj; - CURL *eh; - char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32]; - - GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv, - &merchant_pub.eddsa_pub); - TALER_merchant_refund_sign (coin_pub, - h_contract_terms, - rtransaction_id, - amount, - merchant_priv, - &merchant_sig); - { - char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string ( - coin_pub, - sizeof (struct TALER_CoinSpendPublicKeyP), - pub_str, - sizeof (pub_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "coins/%s/refund", - pub_str); - } - refund_obj = GNUNET_JSON_PACK ( - TALER_JSON_pack_amount ("refund_amount", - amount), - GNUNET_JSON_pack_data_auto ("h_contract_terms", - h_contract_terms), - GNUNET_JSON_pack_uint64 ("rtransaction_id", - rtransaction_id), - GNUNET_JSON_pack_data_auto ("merchant_pub", - &merchant_pub), - GNUNET_JSON_pack_data_auto ("merchant_sig", - &merchant_sig)); - rh = GNUNET_new (struct TALER_EXCHANGE_RefundHandle); - rh->cb = cb; - rh->cb_cls = cb_cls; - rh->url = TALER_url_join (url, - arg_str, - NULL); - if (NULL == rh->url) - { - json_decref (refund_obj); - GNUNET_free (rh); - return NULL; - } - rh->h_contract_terms = *h_contract_terms; - rh->coin_pub = *coin_pub; - rh->merchant = merchant_pub; - rh->rtransaction_id = rtransaction_id; - rh->refund_amount = *amount; - eh = TALER_EXCHANGE_curl_easy_get_ (rh->url); - if ( (NULL == eh) || - (GNUNET_OK != - TALER_curl_easy_post (&rh->ctx, - eh, - refund_obj)) ) - { - GNUNET_break (0); - if (NULL != eh) - curl_easy_cleanup (eh); - json_decref (refund_obj); - GNUNET_free (rh->url); - GNUNET_free (rh); - return NULL; - } - json_decref (refund_obj); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "URL for refund: `%s'\n", - rh->url); - rh->keys = TALER_EXCHANGE_keys_incref (keys); - rh->job = GNUNET_CURL_job_add2 (ctx, - eh, - rh->ctx.headers, - &handle_refund_finished, - rh); - return rh; -} - - -void -TALER_EXCHANGE_refund_cancel (struct TALER_EXCHANGE_RefundHandle *refund) -{ - if (NULL != refund->job) - { - GNUNET_CURL_job_cancel (refund->job); - refund->job = NULL; - } - GNUNET_free (refund->url); - TALER_curl_easy_post_finished (&refund->ctx); - TALER_EXCHANGE_keys_decref (refund->keys); - GNUNET_free (refund); -} - - -/* end of exchange_api_refund.c */ diff --git a/src/lib/exchange_api_reserves_attest.c b/src/lib/exchange_api_reserves_attest.c @@ -1,365 +0,0 @@ -/* - This file is part of TALER - 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 General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_reserves_attest.c - * @brief Implementation of the POST /reserves-attest/$RESERVE_PUB requests - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP attest codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_json_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_exchange_service.h" -#include "taler/taler_json_lib.h" -#include "exchange_api_handle.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * @brief A /reserves-attest/$RID Handle - */ -struct TALER_EXCHANGE_ReservesAttestHandle -{ - - /** - * The keys of the this request handle will use - */ - struct TALER_EXCHANGE_Keys *keys; - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Context for #TEH_curl_easy_post(). Keeps the data that must - * persist for Curl to make the upload. - */ - struct TALER_CURL_PostContext post_ctx; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_ReservesPostAttestCallback cb; - - /** - * Public key of the reserve we are querying. - */ - struct TALER_ReservePublicKeyP reserve_pub; - - /** - * Closure for @a cb. - */ - void *cb_cls; - -}; - - -/** - * We received an #MHD_HTTP_OK attest code. Handle the JSON - * response. - * - * @param rsh handle of the request - * @param j JSON response - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -handle_reserves_attest_ok (struct TALER_EXCHANGE_ReservesAttestHandle *rsh, - const json_t *j) -{ - struct TALER_EXCHANGE_ReservePostAttestResult rs = { - .hr.reply = j, - .hr.http_status = MHD_HTTP_OK - }; - const json_t *attributes; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_timestamp ("exchange_timestamp", - &rs.details.ok.exchange_time), - GNUNET_JSON_spec_timestamp ("expiration_time", - &rs.details.ok.expiration_time), - GNUNET_JSON_spec_fixed_auto ("exchange_sig", - &rs.details.ok.exchange_sig), - GNUNET_JSON_spec_fixed_auto ("exchange_pub", - &rs.details.ok.exchange_pub), - GNUNET_JSON_spec_object_const ("attributes", - &attributes), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, - NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_EXCHANGE_test_signing_key (rsh->keys, - &rs.details.ok.exchange_pub)) - { - GNUNET_break_op (0); - rs.hr.http_status = 0; - rs.hr.ec = TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_SIGNATURE_BY_EXCHANGE; - rsh->cb (rsh->cb_cls, - &rs); - rsh->cb = NULL; - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; - } - rs.details.ok.attributes = attributes; - if (GNUNET_OK != - TALER_exchange_online_reserve_attest_details_verify ( - rs.details.ok.exchange_time, - rs.details.ok.expiration_time, - &rsh->reserve_pub, - attributes, - &rs.details.ok.exchange_pub, - &rs.details.ok.exchange_sig)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; - } - rsh->cb (rsh->cb_cls, - &rs); - rsh->cb = NULL; - GNUNET_JSON_parse_free (spec); - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /reserves-attest/$RID request. - * - * @param cls the `struct TALER_EXCHANGE_ReservesAttestHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_reserves_attest_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_ReservesAttestHandle *rsh = cls; - const json_t *j = response; - struct TALER_EXCHANGE_ReservePostAttestResult rs = { - .hr.reply = j, - .hr.http_status = (unsigned int) response_code - }; - - rsh->job = NULL; - switch (response_code) - { - case 0: - rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - if (GNUNET_OK != - handle_reserves_attest_ok (rsh, - j)) - { - rs.hr.http_status = 0; - rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - } - break; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - GNUNET_break (0); - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_FORBIDDEN: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - GNUNET_break (0); - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_CONFLICT: - /* Server doesn't have the requested attributes */ - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for reserves attest\n", - (unsigned int) response_code, - (int) rs.hr.ec); - break; - } - if (NULL != rsh->cb) - { - rsh->cb (rsh->cb_cls, - &rs); - rsh->cb = NULL; - } - TALER_EXCHANGE_reserves_attest_cancel (rsh); -} - - -struct TALER_EXCHANGE_ReservesAttestHandle * -TALER_EXCHANGE_reserves_attest ( - struct GNUNET_CURL_Context *ctx, - const char *url, - struct TALER_EXCHANGE_Keys *keys, - const struct TALER_ReservePrivateKeyP *reserve_priv, - unsigned int attributes_length, - const char *attributes[const static attributes_length], - TALER_EXCHANGE_ReservesPostAttestCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_ReservesAttestHandle *rsh; - CURL *eh; - char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32]; - struct TALER_ReserveSignatureP reserve_sig; - json_t *details; - struct GNUNET_TIME_Timestamp ts; - - if (0 == attributes_length) - { - GNUNET_break (0); - return NULL; - } - details = json_array (); - GNUNET_assert (NULL != details); - for (unsigned int i = 0; i<attributes_length; i++) - { - GNUNET_assert (0 == - json_array_append_new (details, - json_string (attributes[i]))); - } - rsh = GNUNET_new (struct TALER_EXCHANGE_ReservesAttestHandle); - rsh->cb = cb; - rsh->cb_cls = cb_cls; - GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, - &rsh->reserve_pub.eddsa_pub); - { - char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string ( - &rsh->reserve_pub, - sizeof (rsh->reserve_pub), - pub_str, - sizeof (pub_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "reserves-attest/%s", - pub_str); - } - rsh->url = TALER_url_join (url, - arg_str, - NULL); - if (NULL == rsh->url) - { - json_decref (details); - GNUNET_free (rsh); - return NULL; - } - eh = TALER_EXCHANGE_curl_easy_get_ (rsh->url); - if (NULL == eh) - { - GNUNET_break (0); - json_decref (details); - GNUNET_free (rsh->url); - GNUNET_free (rsh); - return NULL; - } - ts = GNUNET_TIME_timestamp_get (); - TALER_wallet_reserve_attest_request_sign (ts, - details, - reserve_priv, - &reserve_sig); - { - json_t *attest_obj = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_data_auto ("reserve_sig", - &reserve_sig), - GNUNET_JSON_pack_timestamp ("request_timestamp", - ts), - GNUNET_JSON_pack_array_steal ("details", - details)); - - if (GNUNET_OK != - TALER_curl_easy_post (&rsh->post_ctx, - eh, - attest_obj)) - { - GNUNET_break (0); - curl_easy_cleanup (eh); - json_decref (attest_obj); - GNUNET_free (rsh->url); - GNUNET_free (rsh); - return NULL; - } - json_decref (attest_obj); - } - rsh->job = GNUNET_CURL_job_add2 (ctx, - eh, - rsh->post_ctx.headers, - &handle_reserves_attest_finished, - rsh); - rsh->keys = TALER_EXCHANGE_keys_incref (keys); - return rsh; -} - - -void -TALER_EXCHANGE_reserves_attest_cancel ( - struct TALER_EXCHANGE_ReservesAttestHandle *rsh) -{ - if (NULL != rsh->job) - { - GNUNET_CURL_job_cancel (rsh->job); - rsh->job = NULL; - } - TALER_curl_easy_post_finished (&rsh->post_ctx); - TALER_EXCHANGE_keys_decref (rsh->keys); - GNUNET_free (rsh->url); - GNUNET_free (rsh); -} - - -/* end of exchange_api_reserves_attest.c */ diff --git a/src/lib/exchange_api_reserves_close.c b/src/lib/exchange_api_reserves_close.c @@ -1,373 +0,0 @@ -/* - This file is part of TALER - 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 General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_reserves_close.c - * @brief Implementation of the POST /reserves/$RESERVE_PUB/close requests - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP close codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_json_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_exchange_service.h" -#include "taler/taler_json_lib.h" -#include "exchange_api_handle.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * @brief A /reserves/$RID/close Handle - */ -struct TALER_EXCHANGE_ReservesCloseHandle -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Context for #TEH_curl_easy_post(). Keeps the data that must - * persist for Curl to make the upload. - */ - struct TALER_CURL_PostContext post_ctx; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_ReservesCloseCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Public key of the reserve we are querying. - */ - struct TALER_ReservePublicKeyP reserve_pub; - - /** - * Our signature. - */ - struct TALER_ReserveSignatureP reserve_sig; - - /** - * When did we make the request. - */ - struct GNUNET_TIME_Timestamp ts; - -}; - - -/** - * We received an #MHD_HTTP_OK close code. Handle the JSON - * response. - * - * @param rch handle of the request - * @param j JSON response - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -handle_reserves_close_ok (struct TALER_EXCHANGE_ReservesCloseHandle *rch, - const json_t *j) -{ - struct TALER_EXCHANGE_ReserveCloseResult rs = { - .hr.reply = j, - .hr.http_status = MHD_HTTP_OK, - }; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount_any ("wire_amount", - &rs.details.ok.wire_amount), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, - NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - rch->cb (rch->cb_cls, - &rs); - rch->cb = NULL; - GNUNET_JSON_parse_free (spec); - return GNUNET_OK; -} - - -/** - * We received an #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS close code. Handle the JSON - * response. - * - * @param rch handle of the request - * @param j JSON response - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -handle_reserves_close_kyc (struct TALER_EXCHANGE_ReservesCloseHandle *rch, - const json_t *j) -{ - struct TALER_EXCHANGE_ReserveCloseResult rs = { - .hr.reply = j, - .hr.http_status = MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS, - }; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ( - "h_payto", - &rs.details.unavailable_for_legal_reasons.h_payto), - GNUNET_JSON_spec_uint64 ( - "requirement_row", - &rs.details.unavailable_for_legal_reasons.requirement_row), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, - NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - rch->cb (rch->cb_cls, - &rs); - rch->cb = NULL; - GNUNET_JSON_parse_free (spec); - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /reserves/$RID/close request. - * - * @param cls the `struct TALER_EXCHANGE_ReservesCloseHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_reserves_close_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_ReservesCloseHandle *rch = cls; - const json_t *j = response; - struct TALER_EXCHANGE_ReserveCloseResult rs = { - .hr.reply = j, - .hr.http_status = (unsigned int) response_code - }; - - rch->job = NULL; - switch (response_code) - { - case 0: - rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - if (GNUNET_OK != - handle_reserves_close_ok (rch, - j)) - { - GNUNET_break_op (0); - rs.hr.http_status = 0; - rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - } - break; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - GNUNET_break (0); - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_FORBIDDEN: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - GNUNET_break (0); - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_CONFLICT: - /* Insufficient balance to inquire for reserve close */ - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: - if (GNUNET_OK != - handle_reserves_close_kyc (rch, - j)) - { - GNUNET_break_op (0); - rs.hr.http_status = 0; - rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - } - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for reserves close\n", - (unsigned int) response_code, - (int) rs.hr.ec); - break; - } - if (NULL != rch->cb) - { - rch->cb (rch->cb_cls, - &rs); - rch->cb = NULL; - } - TALER_EXCHANGE_reserves_close_cancel (rch); -} - - -struct TALER_EXCHANGE_ReservesCloseHandle * -TALER_EXCHANGE_reserves_close ( - struct GNUNET_CURL_Context *ctx, - const char *url, - const struct TALER_ReservePrivateKeyP *reserve_priv, - const struct TALER_FullPayto target_payto_uri, - TALER_EXCHANGE_ReservesCloseCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_ReservesCloseHandle *rch; - CURL *eh; - char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32]; - struct TALER_FullPaytoHashP h_payto; - - rch = GNUNET_new (struct TALER_EXCHANGE_ReservesCloseHandle); - rch->cb = cb; - rch->cb_cls = cb_cls; - rch->ts = GNUNET_TIME_timestamp_get (); - GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, - &rch->reserve_pub.eddsa_pub); - { - char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string ( - &rch->reserve_pub, - sizeof (rch->reserve_pub), - pub_str, - sizeof (pub_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "reserves/%s/close", - pub_str); - } - rch->url = TALER_url_join (url, - arg_str, - NULL); - if (NULL == rch->url) - { - GNUNET_free (rch); - return NULL; - } - eh = TALER_EXCHANGE_curl_easy_get_ (rch->url); - if (NULL == eh) - { - GNUNET_break (0); - GNUNET_free (rch->url); - GNUNET_free (rch); - return NULL; - } - if (NULL != target_payto_uri.full_payto) - TALER_full_payto_hash (target_payto_uri, - &h_payto); - TALER_wallet_reserve_close_sign (rch->ts, - (NULL != target_payto_uri.full_payto) - ? &h_payto - : NULL, - reserve_priv, - &rch->reserve_sig); - { - json_t *close_obj = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_allow_null ( - TALER_JSON_pack_full_payto ("payto_uri", - target_payto_uri)), - GNUNET_JSON_pack_timestamp ("request_timestamp", - rch->ts), - GNUNET_JSON_pack_data_auto ("reserve_sig", - &rch->reserve_sig)); - - if (GNUNET_OK != - TALER_curl_easy_post (&rch->post_ctx, - eh, - close_obj)) - { - GNUNET_break (0); - curl_easy_cleanup (eh); - json_decref (close_obj); - GNUNET_free (rch->url); - GNUNET_free (rch); - return NULL; - } - json_decref (close_obj); - } - rch->job = GNUNET_CURL_job_add2 (ctx, - eh, - rch->post_ctx.headers, - &handle_reserves_close_finished, - rch); - return rch; -} - - -void -TALER_EXCHANGE_reserves_close_cancel ( - struct TALER_EXCHANGE_ReservesCloseHandle *rch) -{ - if (NULL != rch->job) - { - GNUNET_CURL_job_cancel (rch->job); - rch->job = NULL; - } - TALER_curl_easy_post_finished (&rch->post_ctx); - GNUNET_free (rch->url); - GNUNET_free (rch); -} - - -/* end of exchange_api_reserves_close.c */ diff --git a/src/lib/exchange_api_reserves_get.c b/src/lib/exchange_api_reserves_get.c @@ -1,279 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2022 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_reserves_get.c - * @brief Implementation of the GET /reserves/$RESERVE_PUB requests - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_json_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_exchange_service.h" -#include "taler/taler_json_lib.h" -#include "exchange_api_handle.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * @brief A /reserves/ GET Handle - */ -struct TALER_EXCHANGE_ReservesGetHandle -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_ReservesGetCallback cb; - - /** - * Public key of the reserve we are querying. - */ - struct TALER_ReservePublicKeyP reserve_pub; - - /** - * Closure for @a cb. - */ - void *cb_cls; - -}; - - -/** - * We received an #MHD_HTTP_OK status code. Handle the JSON - * response. - * - * @param rgh handle of the request - * @param j JSON response - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -handle_reserves_get_ok (struct TALER_EXCHANGE_ReservesGetHandle *rgh, - const json_t *j) -{ - struct TALER_EXCHANGE_ReserveSummary rs = { - .hr.reply = j, - .hr.http_status = MHD_HTTP_OK - }; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount_any ("balance", - &rs.details.ok.balance), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ( - "last_origin", - (const char **) &rs.details.ok.last_origin.full_payto), - NULL), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, - NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - rgh->cb (rgh->cb_cls, - &rs); - rgh->cb = NULL; - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /reserves/ GET request. - * - * @param cls the `struct TALER_EXCHANGE_ReservesGetHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_reserves_get_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_ReservesGetHandle *rgh = cls; - const json_t *j = response; - struct TALER_EXCHANGE_ReserveSummary rs = { - .hr.reply = j, - .hr.http_status = (unsigned int) response_code - }; - - rgh->job = NULL; - switch (response_code) - { - case 0: - rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - if (GNUNET_OK != - handle_reserves_get_ok (rgh, - j)) - { - rs.hr.http_status = 0; - rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - } - break; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for GET %s\n", - (unsigned int) response_code, - (int) rs.hr.ec, - rgh->url); - break; - } - if (NULL != rgh->cb) - { - rgh->cb (rgh->cb_cls, - &rs); - rgh->cb = NULL; - } - TALER_EXCHANGE_reserves_get_cancel (rgh); -} - - -struct TALER_EXCHANGE_ReservesGetHandle * -TALER_EXCHANGE_reserves_get ( - struct GNUNET_CURL_Context *ctx, - const char *url, - const struct TALER_ReservePublicKeyP *reserve_pub, - struct GNUNET_TIME_Relative timeout, - TALER_EXCHANGE_ReservesGetCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_ReservesGetHandle *rgh; - CURL *eh; - char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 16 + 32]; - unsigned int tms - = (unsigned int) timeout.rel_value_us - / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us; - - { - char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; - char *end; - char timeout_str[32]; - - end = GNUNET_STRINGS_data_to_string ( - reserve_pub, - sizeof (*reserve_pub), - pub_str, - sizeof (pub_str)); - *end = '\0'; - GNUNET_snprintf (timeout_str, - sizeof (timeout_str), - "%u", - tms); - if (0 == tms) - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "reserves/%s", - pub_str); - else - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "reserves/%s?timeout_ms=%s", - pub_str, - timeout_str); - } - rgh = GNUNET_new (struct TALER_EXCHANGE_ReservesGetHandle); - rgh->cb = cb; - rgh->cb_cls = cb_cls; - rgh->reserve_pub = *reserve_pub; - rgh->url = TALER_url_join (url, - arg_str, - NULL); - if (NULL == rgh->url) - { - GNUNET_free (rgh); - return NULL; - } - eh = TALER_EXCHANGE_curl_easy_get_ (rgh->url); - if (NULL == eh) - { - GNUNET_break (0); - GNUNET_free (rgh->url); - GNUNET_free (rgh); - return NULL; - } - if (0 != tms) - { - GNUNET_break (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_TIMEOUT_MS, - (long) (tms + 100L))); - } - rgh->job = GNUNET_CURL_job_add (ctx, - eh, - &handle_reserves_get_finished, - rgh); - return rgh; -} - - -void -TALER_EXCHANGE_reserves_get_cancel ( - struct TALER_EXCHANGE_ReservesGetHandle *rgh) -{ - if (NULL != rgh->job) - { - GNUNET_CURL_job_cancel (rgh->job); - rgh->job = NULL; - } - GNUNET_free (rgh->url); - GNUNET_free (rgh); -} - - -/* end of exchange_api_reserves_get.c */ diff --git a/src/lib/exchange_api_reserves_get_attestable.c b/src/lib/exchange_api_reserves_get_attestable.c @@ -1,276 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2022 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_reserves_get_attestable.c - * @brief Implementation of the GET_ATTESTABLE /reserves/$RESERVE_PUB requests - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_json_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_exchange_service.h" -#include "taler/taler_json_lib.h" -#include "exchange_api_handle.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * @brief A /reserves/ GET_ATTESTABLE Handle - */ -struct TALER_EXCHANGE_ReservesGetAttestHandle -{ - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_ReservesGetAttestCallback cb; - - /** - * Public key of the reserve we are querying. - */ - struct TALER_ReservePublicKeyP reserve_pub; - - /** - * Closure for @a cb. - */ - void *cb_cls; - -}; - - -/** - * We received an #MHD_HTTP_OK status code. Handle the JSON - * response. - * - * @param rgah handle of the request - * @param j JSON response - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -handle_reserves_get_attestable_ok ( - struct TALER_EXCHANGE_ReservesGetAttestHandle *rgah, - const json_t *j) -{ - struct TALER_EXCHANGE_ReserveGetAttestResult rs = { - .hr.reply = j, - .hr.http_status = MHD_HTTP_OK - }; - const json_t *details; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_array_const ("details", - &details), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, - NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - { - unsigned int dlen = json_array_size (details); - const char *attributes[GNUNET_NZL (dlen)]; - - for (unsigned int i = 0; i<dlen; i++) - { - json_t *detail = json_array_get (details, - i); - attributes[i] = json_string_value (detail); - if (NULL == attributes[i]) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - } - rs.details.ok.attributes_length = dlen; - rs.details.ok.attributes = attributes; - rgah->cb (rgah->cb_cls, - &rs); - rgah->cb = NULL; - } - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP GET /reserves-attest/$RID request. - * - * @param cls the `struct TALER_EXCHANGE_ReservesGetAttestableHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_reserves_get_attestable_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_ReservesGetAttestHandle *rgah = cls; - const json_t *j = response; - struct TALER_EXCHANGE_ReserveGetAttestResult rs = { - .hr.reply = j, - .hr.http_status = (unsigned int) response_code - }; - - rgah->job = NULL; - switch (response_code) - { - case 0: - rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - if (GNUNET_OK != - handle_reserves_get_attestable_ok (rgah, - j)) - { - rs.hr.http_status = 0; - rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - } - break; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_CONFLICT: - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for reserves get_attestable\n", - (unsigned int) response_code, - (int) rs.hr.ec); - break; - } - if (NULL != rgah->cb) - { - rgah->cb (rgah->cb_cls, - &rs); - rgah->cb = NULL; - } - TALER_EXCHANGE_reserves_get_attestable_cancel (rgah); -} - - -struct TALER_EXCHANGE_ReservesGetAttestHandle * -TALER_EXCHANGE_reserves_get_attestable ( - struct GNUNET_CURL_Context *ctx, - const char *url, - const struct TALER_ReservePublicKeyP *reserve_pub, - TALER_EXCHANGE_ReservesGetAttestCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_ReservesGetAttestHandle *rgah; - CURL *eh; - char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32]; - - { - char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string ( - reserve_pub, - sizeof (*reserve_pub), - pub_str, - sizeof (pub_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "reserves-attest/%s", - pub_str); - } - rgah = GNUNET_new (struct TALER_EXCHANGE_ReservesGetAttestHandle); - rgah->cb = cb; - rgah->cb_cls = cb_cls; - rgah->reserve_pub = *reserve_pub; - rgah->url = TALER_url_join (url, - arg_str, - NULL); - if (NULL == rgah->url) - { - GNUNET_free (rgah); - return NULL; - } - eh = TALER_EXCHANGE_curl_easy_get_ (rgah->url); - if (NULL == eh) - { - GNUNET_break (0); - GNUNET_free (rgah->url); - GNUNET_free (rgah); - return NULL; - } - rgah->job = GNUNET_CURL_job_add (ctx, - eh, - &handle_reserves_get_attestable_finished, - rgah); - return rgah; -} - - -void -TALER_EXCHANGE_reserves_get_attestable_cancel ( - struct TALER_EXCHANGE_ReservesGetAttestHandle *rgah) -{ - if (NULL != rgah->job) - { - GNUNET_CURL_job_cancel (rgah->job); - rgah->job = NULL; - } - GNUNET_free (rgah->url); - GNUNET_free (rgah); -} - - -/* end of exchange_api_reserves_get_attestable.c */ diff --git a/src/lib/exchange_api_reserves_history.c b/src/lib/exchange_api_reserves_history.c @@ -1,1258 +0,0 @@ -/* - This file is part of TALER - 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 General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_reserves_history.c - * @brief Implementation of the POST /reserves/$RESERVE_PUB/history requests - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP history codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_json_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_exchange_service.h" -#include "taler/taler_json_lib.h" -#include "exchange_api_handle.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * @brief A /reserves/$RID/history Handle - */ -struct TALER_EXCHANGE_ReservesHistoryHandle -{ - - /** - * The keys of the exchange this request handle will use - */ - struct TALER_EXCHANGE_Keys *keys; - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Context for #TEH_curl_easy_post(). Keeps the data that must - * persist for Curl to make the upload. - */ - struct TALER_CURL_PostContext post_ctx; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_ReservesHistoryCallback cb; - - /** - * Public key of the reserve we are querying. - */ - struct TALER_ReservePublicKeyP reserve_pub; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Where to store the etag (if any). - */ - uint64_t etag; - -}; - - -/** - * Context for history entry helpers. - */ -struct HistoryParseContext -{ - - /** - * Keys of the exchange we use. - */ - const struct TALER_EXCHANGE_Keys *keys; - - /** - * Our reserve public key. - */ - const struct TALER_ReservePublicKeyP *reserve_pub; - - /** - * Array of UUIDs. - */ - struct GNUNET_HashCode *uuids; - - /** - * Where to sum up total inbound amounts. - */ - struct TALER_Amount *total_in; - - /** - * Where to sum up total outbound amounts. - */ - struct TALER_Amount *total_out; - - /** - * Number of entries already used in @e uuids. - */ - unsigned int uuid_off; -}; - - -/** - * Type of a function called to parse a reserve history - * entry @a rh. - * - * @param[in,out] rh where to write the result - * @param[in,out] uc UUID context for duplicate detection - * @param transaction the transaction to parse - * @return #GNUNET_OK on success - */ -typedef enum GNUNET_GenericReturnValue -(*ParseHelper)(struct TALER_EXCHANGE_ReserveHistoryEntry *rh, - struct HistoryParseContext *uc, - const json_t *transaction); - - -/** - * Parse "credit" reserve history entry. - * - * @param[in,out] rh entry to parse - * @param uc our context - * @param transaction the transaction to parse - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -parse_credit (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, - struct HistoryParseContext *uc, - const json_t *transaction) -{ - struct TALER_FullPayto wire_uri; - uint64_t wire_reference; - struct GNUNET_TIME_Timestamp timestamp; - struct GNUNET_JSON_Specification withdraw_spec[] = { - GNUNET_JSON_spec_uint64 ("wire_reference", - &wire_reference), - GNUNET_JSON_spec_timestamp ("timestamp", - &timestamp), - TALER_JSON_spec_full_payto_uri ("sender_account_url", - &wire_uri), - GNUNET_JSON_spec_end () - }; - - rh->type = TALER_EXCHANGE_RTT_CREDIT; - if (0 > - TALER_amount_add (uc->total_in, - uc->total_in, - &rh->amount)) - { - /* overflow in history already!? inconceivable! Bad exchange! */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - withdraw_spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - rh->details.in_details.sender_url.full_payto - = GNUNET_strdup (wire_uri.full_payto); - rh->details.in_details.wire_reference = wire_reference; - rh->details.in_details.timestamp = timestamp; - return GNUNET_OK; -} - - -/** - * Parse "withdraw" reserve history entry. - * - * @param[in,out] rh entry to parse - * @param uc our context - * @param transaction the transaction to parse - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -parse_withdraw (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, - struct HistoryParseContext *uc, - const json_t *transaction) -{ - uint16_t num_coins; - struct TALER_Amount withdraw_fee; - struct TALER_Amount withdraw_amount; - struct TALER_Amount amount_without_fee; - uint8_t max_age = 0; - uint8_t noreveal_index = 0; - struct TALER_HashBlindedPlanchetsP planchets_h; - struct TALER_HashBlindedPlanchetsP selected_h; - struct TALER_ReserveSignatureP reserve_sig; - struct TALER_BlindingMasterSeedP blinding_seed; - struct TALER_DenominationHashP *denom_pub_hashes; - size_t num_denom_pub_hashes; - bool no_max_age; - bool no_noreveal_index; - bool no_blinding_seed; - bool no_selected_h; - struct GNUNET_JSON_Specification withdraw_spec[] = { - GNUNET_JSON_spec_fixed_auto ("reserve_sig", - &reserve_sig), - GNUNET_JSON_spec_uint16 ("num_coins", - &num_coins), - GNUNET_JSON_spec_fixed_auto ("planchets_h", - &planchets_h), - TALER_JSON_spec_amount_any ("amount", - &withdraw_amount), - TALER_JSON_spec_amount_any ("withdraw_fee", - &withdraw_fee), - TALER_JSON_spec_array_of_denom_pub_h ("denom_pub_hashes", - &num_denom_pub_hashes, - &denom_pub_hashes), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_fixed_auto ("selected_h", - &selected_h), - &no_selected_h), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_uint8 ("max_age", - &max_age), - &no_max_age), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_fixed_auto ("blinding_seed", - &blinding_seed), - &no_blinding_seed), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_uint8 ("noreveal_index", - &noreveal_index), - &no_noreveal_index), - GNUNET_JSON_spec_end () - }; - - rh->type = TALER_EXCHANGE_RTT_WITHDRAWAL; - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - withdraw_spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - if ((no_max_age != no_noreveal_index) || - (no_max_age != no_selected_h)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (withdraw_spec); - return GNUNET_SYSERR; - } - rh->details.withdraw.age_restricted = ! no_max_age; - - if (num_coins != num_denom_pub_hashes) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (withdraw_spec); - return GNUNET_SYSERR; - } - - /* Check that the signature is a valid withdraw request */ - if (0>TALER_amount_subtract ( - &amount_without_fee, - &withdraw_amount, - &withdraw_fee)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (withdraw_spec); - return GNUNET_SYSERR; - } - - if (GNUNET_OK != - TALER_wallet_withdraw_verify ( - &amount_without_fee, - &withdraw_fee, - &planchets_h, - no_blinding_seed ? NULL : &blinding_seed, - no_max_age ? NULL : &uc->keys->age_mask, - no_max_age ? 0 : max_age, - uc->reserve_pub, - &reserve_sig)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (withdraw_spec); - return GNUNET_SYSERR; - } - - rh->details.withdraw.num_coins = num_coins; - rh->details.withdraw.age_restricted = ! no_max_age; - rh->details.withdraw.max_age = max_age; - rh->details.withdraw.planchets_h = planchets_h; - rh->details.withdraw.selected_h = selected_h; - rh->details.withdraw.noreveal_index = noreveal_index; - rh->details.withdraw.no_blinding_seed = no_blinding_seed; - if (! no_blinding_seed) - rh->details.withdraw.blinding_seed = blinding_seed; - - /* check that withdraw fee matches expectations! */ - { - const struct TALER_EXCHANGE_Keys *key_state; - struct TALER_Amount fee_acc; - struct TALER_Amount amount_acc; - - GNUNET_assert (GNUNET_OK == - TALER_amount_set_zero (withdraw_amount.currency, - &fee_acc)); - GNUNET_assert (GNUNET_OK == - TALER_amount_set_zero (withdraw_amount.currency, - &amount_acc)); - - key_state = uc->keys; - - /* accumulate the withdraw fees */ - for (size_t i=0; i < num_coins; i++) - { - const struct TALER_EXCHANGE_DenomPublicKey *dki; - - dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state, - &denom_pub_hashes[i]); - if (NULL == dki) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (withdraw_spec); - return GNUNET_SYSERR; - } - GNUNET_assert (0 <= - TALER_amount_add (&fee_acc, - &fee_acc, - &dki->fees.withdraw)); - GNUNET_assert (0 <= - TALER_amount_add (&amount_acc, - &amount_acc, - &dki->value)); - } - - if ( (GNUNET_YES != - TALER_amount_cmp_currency (&fee_acc, - &withdraw_fee)) || - (0 != - TALER_amount_cmp (&amount_acc, - &amount_without_fee)) ) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (withdraw_spec); - return GNUNET_SYSERR; - } - rh->details.withdraw.fee = withdraw_fee; - } - - #pragma message "is out_authorization_sig still needed? Not set anywhere" - rh->details.withdraw.out_authorization_sig - = json_object_get (transaction, - "signature"); - /* Check check that the same withdraw transaction - isn't listed twice by the exchange. We use the - "uuid" array to remember the hashes of all - signatures, and compare the hashes to find - duplicates. */ - GNUNET_CRYPTO_hash (&reserve_sig, - sizeof (reserve_sig), - &uc->uuids[uc->uuid_off]); - for (unsigned int i = 0; i<uc->uuid_off; i++) - { - if (0 == GNUNET_memcmp (&uc->uuids[uc->uuid_off], - &uc->uuids[i])) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (withdraw_spec); - return GNUNET_SYSERR; - } - } - uc->uuid_off++; - - if (0 > - TALER_amount_add (uc->total_out, - uc->total_out, - &rh->amount)) - { - /* overflow in history already!? inconceivable! Bad exchange! */ - GNUNET_break_op (0); - GNUNET_JSON_parse_free (withdraw_spec); - return GNUNET_SYSERR; - } - GNUNET_JSON_parse_free (withdraw_spec); - return GNUNET_OK; -} - - -/** - * Parse "recoup" reserve history entry. - * - * @param[in,out] rh entry to parse - * @param uc our context - * @param transaction the transaction to parse - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -parse_recoup (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, - struct HistoryParseContext *uc, - const json_t *transaction) -{ - const struct TALER_EXCHANGE_Keys *key_state; - struct GNUNET_JSON_Specification recoup_spec[] = { - GNUNET_JSON_spec_fixed_auto ("coin_pub", - &rh->details.recoup_details.coin_pub), - GNUNET_JSON_spec_fixed_auto ("exchange_sig", - &rh->details.recoup_details.exchange_sig), - GNUNET_JSON_spec_fixed_auto ("exchange_pub", - &rh->details.recoup_details.exchange_pub), - GNUNET_JSON_spec_timestamp ("timestamp", - &rh->details.recoup_details.timestamp), - GNUNET_JSON_spec_end () - }; - - rh->type = TALER_EXCHANGE_RTT_RECOUP; - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - recoup_spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - key_state = uc->keys; - if (GNUNET_OK != - TALER_EXCHANGE_test_signing_key (key_state, - &rh->details. - recoup_details.exchange_pub)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_exchange_online_confirm_recoup_verify ( - rh->details.recoup_details.timestamp, - &rh->amount, - &rh->details.recoup_details.coin_pub, - uc->reserve_pub, - &rh->details.recoup_details.exchange_pub, - &rh->details.recoup_details.exchange_sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (0 > - TALER_amount_add (uc->total_in, - uc->total_in, - &rh->amount)) - { - /* overflow in history already!? inconceivable! Bad exchange! */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_OK; -} - - -/** - * Parse "closing" reserve history entry. - * - * @param[in,out] rh entry to parse - * @param uc our context - * @param transaction the transaction to parse - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -parse_closing (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, - struct HistoryParseContext *uc, - const json_t *transaction) -{ - const struct TALER_EXCHANGE_Keys *key_state; - struct TALER_FullPayto receiver_uri; - struct GNUNET_JSON_Specification closing_spec[] = { - TALER_JSON_spec_full_payto_uri ( - "receiver_account_details", - &receiver_uri), - GNUNET_JSON_spec_fixed_auto ("wtid", - &rh->details.close_details.wtid), - GNUNET_JSON_spec_fixed_auto ("exchange_sig", - &rh->details.close_details.exchange_sig), - GNUNET_JSON_spec_fixed_auto ("exchange_pub", - &rh->details.close_details.exchange_pub), - TALER_JSON_spec_amount_any ("closing_fee", - &rh->details.close_details.fee), - GNUNET_JSON_spec_timestamp ("timestamp", - &rh->details.close_details.timestamp), - GNUNET_JSON_spec_end () - }; - - rh->type = TALER_EXCHANGE_RTT_CLOSING; - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - closing_spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - key_state = uc->keys; - if (GNUNET_OK != - TALER_EXCHANGE_test_signing_key ( - key_state, - &rh->details.close_details.exchange_pub)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_exchange_online_reserve_closed_verify ( - rh->details.close_details.timestamp, - &rh->amount, - &rh->details.close_details.fee, - receiver_uri, - &rh->details.close_details.wtid, - uc->reserve_pub, - &rh->details.close_details.exchange_pub, - &rh->details.close_details.exchange_sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (0 > - TALER_amount_add (uc->total_out, - uc->total_out, - &rh->amount)) - { - /* overflow in history already!? inconceivable! Bad exchange! */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - rh->details.close_details.receiver_account_details.full_payto - = GNUNET_strdup (receiver_uri.full_payto); - return GNUNET_OK; -} - - -/** - * Parse "merge" reserve history entry. - * - * @param[in,out] rh entry to parse - * @param uc our context - * @param transaction the transaction to parse - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -parse_merge (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, - struct HistoryParseContext *uc, - const json_t *transaction) -{ - uint32_t flags32; - struct GNUNET_JSON_Specification merge_spec[] = { - GNUNET_JSON_spec_fixed_auto ("h_contract_terms", - &rh->details.merge_details.h_contract_terms), - GNUNET_JSON_spec_fixed_auto ("merge_pub", - &rh->details.merge_details.merge_pub), - GNUNET_JSON_spec_fixed_auto ("purse_pub", - &rh->details.merge_details.purse_pub), - GNUNET_JSON_spec_uint32 ("min_age", - &rh->details.merge_details.min_age), - GNUNET_JSON_spec_uint32 ("flags", - &flags32), - GNUNET_JSON_spec_fixed_auto ("reserve_sig", - &rh->details.merge_details.reserve_sig), - TALER_JSON_spec_amount_any ("purse_fee", - &rh->details.merge_details.purse_fee), - GNUNET_JSON_spec_timestamp ("merge_timestamp", - &rh->details.merge_details.merge_timestamp), - GNUNET_JSON_spec_timestamp ("purse_expiration", - &rh->details.merge_details.purse_expiration), - GNUNET_JSON_spec_bool ("merged", - &rh->details.merge_details.merged), - GNUNET_JSON_spec_end () - }; - - rh->type = TALER_EXCHANGE_RTT_MERGE; - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - merge_spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - rh->details.merge_details.flags = - (enum TALER_WalletAccountMergeFlags) flags32; - if (GNUNET_OK != - TALER_wallet_account_merge_verify ( - rh->details.merge_details.merge_timestamp, - &rh->details.merge_details.purse_pub, - rh->details.merge_details.purse_expiration, - &rh->details.merge_details.h_contract_terms, - &rh->amount, - &rh->details.merge_details.purse_fee, - rh->details.merge_details.min_age, - rh->details.merge_details.flags, - uc->reserve_pub, - &rh->details.merge_details.reserve_sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (rh->details.merge_details.merged) - { - if (0 > - TALER_amount_add (uc->total_in, - uc->total_in, - &rh->amount)) - { - /* overflow in history already!? inconceivable! Bad exchange! */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - } - else - { - if (0 > - TALER_amount_add (uc->total_out, - uc->total_out, - &rh->details.merge_details.purse_fee)) - { - /* overflow in history already!? inconceivable! Bad exchange! */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - } - return GNUNET_OK; -} - - -/** - * Parse "open" reserve open entry. - * - * @param[in,out] rh entry to parse - * @param uc our context - * @param transaction the transaction to parse - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -parse_open (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, - struct HistoryParseContext *uc, - const json_t *transaction) -{ - struct GNUNET_JSON_Specification open_spec[] = { - GNUNET_JSON_spec_fixed_auto ("reserve_sig", - &rh->details.open_request.reserve_sig), - TALER_JSON_spec_amount_any ("open_payment", - &rh->details.open_request.reserve_payment), - GNUNET_JSON_spec_uint32 ("requested_min_purses", - &rh->details.open_request.purse_limit), - GNUNET_JSON_spec_timestamp ("request_timestamp", - &rh->details.open_request.request_timestamp), - GNUNET_JSON_spec_timestamp ("requested_expiration", - &rh->details.open_request.reserve_expiration), - GNUNET_JSON_spec_end () - }; - - rh->type = TALER_EXCHANGE_RTT_OPEN; - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - open_spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_wallet_reserve_open_verify ( - &rh->amount, - rh->details.open_request.request_timestamp, - rh->details.open_request.reserve_expiration, - rh->details.open_request.purse_limit, - uc->reserve_pub, - &rh->details.open_request.reserve_sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (0 > - TALER_amount_add (uc->total_out, - uc->total_out, - &rh->amount)) - { - /* overflow in history already!? inconceivable! Bad exchange! */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_OK; -} - - -/** - * Parse "close" reserve close entry. - * - * @param[in,out] rh entry to parse - * @param uc our context - * @param transaction the transaction to parse - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -parse_close (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, - struct HistoryParseContext *uc, - const json_t *transaction) -{ - struct GNUNET_JSON_Specification close_spec[] = { - GNUNET_JSON_spec_fixed_auto ("reserve_sig", - &rh->details.close_request.reserve_sig), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_fixed_auto ("h_payto", - &rh->details.close_request. - target_account_h_payto), - NULL), - GNUNET_JSON_spec_timestamp ("request_timestamp", - &rh->details.close_request.request_timestamp), - GNUNET_JSON_spec_end () - }; - - rh->type = TALER_EXCHANGE_RTT_CLOSE; - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - close_spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - /* force amount to invalid */ - memset (&rh->amount, - 0, - sizeof (rh->amount)); - if (GNUNET_OK != - TALER_wallet_reserve_close_verify ( - rh->details.close_request.request_timestamp, - &rh->details.close_request.target_account_h_payto, - uc->reserve_pub, - &rh->details.close_request.reserve_sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - return GNUNET_OK; -} - - -static void -free_reserve_history ( - unsigned int len, - struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static len]) -{ - for (unsigned int i = 0; i<len; i++) - { - struct TALER_EXCHANGE_ReserveHistoryEntry *rhi = &rhistory[i]; - - switch (rhi->type) - { - case TALER_EXCHANGE_RTT_CREDIT: - GNUNET_free (rhi->details.in_details.sender_url.full_payto); - break; - case TALER_EXCHANGE_RTT_WITHDRAWAL: - break; - case TALER_EXCHANGE_RTT_RECOUP: - break; - case TALER_EXCHANGE_RTT_CLOSING: - break; - case TALER_EXCHANGE_RTT_MERGE: - break; - case TALER_EXCHANGE_RTT_OPEN: - break; - case TALER_EXCHANGE_RTT_CLOSE: - GNUNET_free (rhi->details.close_details - .receiver_account_details.full_payto); - break; - } - } - GNUNET_free (rhistory); -} - - -/** - * Parse history given in JSON format and return it in binary - * format. - * - * @param keys exchange keys - * @param history JSON array with the history - * @param reserve_pub public key of the reserve to inspect - * @param currency currency we expect the balance to be in - * @param[out] total_in set to value of credits to reserve - * @param[out] total_out set to value of debits from reserve - * @param history_length number of entries in @a history - * @param[out] rhistory array of length @a history_length, set to the - * parsed history entries - * @return #GNUNET_OK if history was valid and @a rhistory and @a balance - * were set, - * #GNUNET_SYSERR if there was a protocol violation in @a history - */ -static enum GNUNET_GenericReturnValue -parse_reserve_history ( - const struct TALER_EXCHANGE_Keys *keys, - const json_t *history, - const struct TALER_ReservePublicKeyP *reserve_pub, - const char *currency, - struct TALER_Amount *total_in, - struct TALER_Amount *total_out, - unsigned int history_length, - struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static history_length]) -{ - const struct - { - const char *type; - ParseHelper helper; - } map[] = { - { "CREDIT", &parse_credit }, - { "WITHDRAW", &parse_withdraw }, - { "RECOUP", &parse_recoup }, - { "MERGE", &parse_merge }, - { "CLOSING", &parse_closing }, - { "OPEN", &parse_open }, - { "CLOSE", &parse_close }, - { NULL, NULL } - }; - struct GNUNET_HashCode uuid[history_length]; - struct HistoryParseContext uc = { - .keys = keys, - .reserve_pub = reserve_pub, - .uuids = uuid, - .total_in = total_in, - .total_out = total_out - }; - - GNUNET_assert (GNUNET_OK == - TALER_amount_set_zero (currency, - total_in)); - GNUNET_assert (GNUNET_OK == - TALER_amount_set_zero (currency, - total_out)); - for (unsigned int off = 0; off<history_length; off++) - { - struct TALER_EXCHANGE_ReserveHistoryEntry *rh = &rhistory[off]; - json_t *transaction; - struct TALER_Amount amount; - const char *type; - struct GNUNET_JSON_Specification hist_spec[] = { - GNUNET_JSON_spec_string ("type", - &type), - TALER_JSON_spec_amount_any ("amount", - &amount), - /* 'wire' and 'signature' are optional depending on 'type'! */ - GNUNET_JSON_spec_end () - }; - bool found = false; - - transaction = json_array_get (history, - off); - if (GNUNET_OK != - GNUNET_JSON_parse (transaction, - hist_spec, - NULL, NULL)) - { - GNUNET_break_op (0); - json_dumpf (transaction, - stderr, - JSON_INDENT (2)); - return GNUNET_SYSERR; - } - rh->amount = amount; - if (GNUNET_YES != - TALER_amount_cmp_currency (&amount, - total_in)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - for (unsigned int i = 0; NULL != map[i].type; i++) - { - if (0 == strcasecmp (map[i].type, - type)) - { - found = true; - if (GNUNET_OK != - map[i].helper (rh, - &uc, - transaction)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - break; - } - } - if (! found) - { - /* unexpected 'type', protocol incompatibility, complain! */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - } - return GNUNET_OK; -} - - -/** - * Handle HTTP header received by curl. - * - * @param buffer one line of HTTP header data - * @param size size of an item - * @param nitems number of items passed - * @param userdata our `struct TALER_EXCHANGE_ReservesHistoryHandle *` - * @return `size * nitems` - */ -static size_t -handle_header (char *buffer, - size_t size, - size_t nitems, - void *userdata) -{ - struct TALER_EXCHANGE_ReservesHistoryHandle *rhh = userdata; - size_t total = size * nitems; - char *ndup; - const char *hdr_type; - char *hdr_val; - char *sp; - - ndup = GNUNET_strndup (buffer, - total); - hdr_type = strtok_r (ndup, - ":", - &sp); - if (NULL == hdr_type) - { - GNUNET_free (ndup); - return total; - } - hdr_val = strtok_r (NULL, - "\n\r", - &sp); - if (NULL == hdr_val) - { - GNUNET_free (ndup); - return total; - } - if (' ' == *hdr_val) - hdr_val++; - if (0 == strcasecmp (hdr_type, - MHD_HTTP_HEADER_ETAG)) - { - unsigned long long tval; - char dummy; - - if (1 != - sscanf (hdr_val, - "\"%llu\"%c", - &tval, - &dummy)) - { - GNUNET_break_op (0); - GNUNET_free (ndup); - return 0; - } - rhh->etag = (uint64_t) tval; - } - GNUNET_free (ndup); - return total; -} - - -/** - * We received an #MHD_HTTP_OK history code. Handle the JSON - * response. - * - * @param rsh handle of the request - * @param j JSON response - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -handle_reserves_history_ok (struct TALER_EXCHANGE_ReservesHistoryHandle *rsh, - const json_t *j) -{ - const json_t *history; - unsigned int len; - struct TALER_EXCHANGE_ReserveHistory rs = { - .hr.reply = j, - .hr.http_status = MHD_HTTP_OK, - .details.ok.etag = rsh->etag - }; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount_any ("balance", - &rs.details.ok.balance), - GNUNET_JSON_spec_array_const ("history", - &history), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, - NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - len = json_array_size (history); - { - struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory; - - rhistory = GNUNET_new_array (len, - struct TALER_EXCHANGE_ReserveHistoryEntry); - if (GNUNET_OK != - parse_reserve_history (rsh->keys, - history, - &rsh->reserve_pub, - rs.details.ok.balance.currency, - &rs.details.ok.total_in, - &rs.details.ok.total_out, - len, - rhistory)) - { - GNUNET_break_op (0); - free_reserve_history (len, - rhistory); - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; - } - if (NULL != rsh->cb) - { - rs.details.ok.history = rhistory; - rs.details.ok.history_len = len; - rsh->cb (rsh->cb_cls, - &rs); - rsh->cb = NULL; - } - free_reserve_history (len, - rhistory); - } - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /reserves/$RID/history request. - * - * @param cls the `struct TALER_EXCHANGE_ReservesHistoryHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_reserves_history_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_ReservesHistoryHandle *rsh = cls; - const json_t *j = response; - struct TALER_EXCHANGE_ReserveHistory rs = { - .hr.reply = j, - .hr.http_status = (unsigned int) response_code - }; - - rsh->job = NULL; - switch (response_code) - { - case 0: - rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - if (GNUNET_OK != - handle_reserves_history_ok (rsh, - j)) - { - rs.hr.http_status = 0; - rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - } - break; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - GNUNET_break (0); - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_FORBIDDEN: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - GNUNET_break (0); - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for reserves history\n", - (unsigned int) response_code, - (int) rs.hr.ec); - break; - } - if (NULL != rsh->cb) - { - rsh->cb (rsh->cb_cls, - &rs); - rsh->cb = NULL; - } - TALER_EXCHANGE_reserves_history_cancel (rsh); -} - - -struct TALER_EXCHANGE_ReservesHistoryHandle * -TALER_EXCHANGE_reserves_history ( - struct GNUNET_CURL_Context *ctx, - const char *url, - struct TALER_EXCHANGE_Keys *keys, - const struct TALER_ReservePrivateKeyP *reserve_priv, - uint64_t start_off, - TALER_EXCHANGE_ReservesHistoryCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_ReservesHistoryHandle *rsh; - CURL *eh; - char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 64]; - struct curl_slist *job_headers; - - rsh = GNUNET_new (struct TALER_EXCHANGE_ReservesHistoryHandle); - rsh->cb = cb; - rsh->cb_cls = cb_cls; - GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, - &rsh->reserve_pub.eddsa_pub); - { - char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string ( - &rsh->reserve_pub, - sizeof (rsh->reserve_pub), - pub_str, - sizeof (pub_str)); - *end = '\0'; - if (0 != start_off) - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "reserves/%s/history?start=%llu", - pub_str, - (unsigned long long) start_off); - else - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "reserves/%s/history", - pub_str); - } - rsh->url = TALER_url_join (url, - arg_str, - NULL); - if (NULL == rsh->url) - { - GNUNET_free (rsh); - return NULL; - } - eh = TALER_EXCHANGE_curl_easy_get_ (rsh->url); - if (NULL == eh) - { - GNUNET_break (0); - GNUNET_free (rsh->url); - GNUNET_free (rsh); - return NULL; - } - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_HEADERFUNCTION, - &handle_header)); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_HEADERDATA, - rsh)); - { - struct TALER_ReserveSignatureP reserve_sig; - char *sig_hdr; - char *hdr; - - TALER_wallet_reserve_history_sign (start_off, - reserve_priv, - &reserve_sig); - - sig_hdr = GNUNET_STRINGS_data_to_string_alloc ( - &reserve_sig, - sizeof (reserve_sig)); - GNUNET_asprintf (&hdr, - "%s: %s", - TALER_RESERVE_HISTORY_SIGNATURE_HEADER, - sig_hdr); - GNUNET_free (sig_hdr); - job_headers = curl_slist_append (NULL, - hdr); - GNUNET_free (hdr); - if (NULL == job_headers) - { - GNUNET_break (0); - curl_easy_cleanup (eh); - return NULL; - } - } - - rsh->keys = TALER_EXCHANGE_keys_incref (keys); - rsh->job = GNUNET_CURL_job_add2 (ctx, - eh, - job_headers, - &handle_reserves_history_finished, - rsh); - curl_slist_free_all (job_headers); - return rsh; -} - - -void -TALER_EXCHANGE_reserves_history_cancel ( - struct TALER_EXCHANGE_ReservesHistoryHandle *rsh) -{ - if (NULL != rsh->job) - { - GNUNET_CURL_job_cancel (rsh->job); - rsh->job = NULL; - } - TALER_curl_easy_post_finished (&rsh->post_ctx); - GNUNET_free (rsh->url); - TALER_EXCHANGE_keys_decref (rsh->keys); - GNUNET_free (rsh); -} - - -/* end of exchange_api_reserves_history.c */ diff --git a/src/lib/exchange_api_reserves_open.c b/src/lib/exchange_api_reserves_open.c @@ -1,567 +0,0 @@ -/* - This file is part of TALER - 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 General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_reserves_open.c - * @brief Implementation of the POST /reserves/$RESERVE_PUB/open requests - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP open codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_json_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_exchange_service.h" -#include "taler/taler_json_lib.h" -#include "exchange_api_common.h" -#include "exchange_api_handle.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * Information we keep per coin to validate the reply. - */ -struct CoinData -{ - /** - * Public key of the coin. - */ - struct TALER_CoinSpendPublicKeyP coin_pub; - - /** - * Signature by the coin. - */ - struct TALER_CoinSpendSignatureP coin_sig; - - /** - * The hash of the denomination's public key - */ - struct TALER_DenominationHashP h_denom_pub; - - /** - * How much did this coin contribute. - */ - struct TALER_Amount contribution; -}; - - -/** - * @brief A /reserves/$RID/open Handle - */ -struct TALER_EXCHANGE_ReservesOpenHandle -{ - - /** - * The keys of the exchange this request handle will use - */ - struct TALER_EXCHANGE_Keys *keys; - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Context for #TEH_curl_easy_post(). Keeps the data that must - * persist for Curl to make the upload. - */ - struct TALER_CURL_PostContext post_ctx; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_ReservesOpenCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - - /** - * Information we keep per coin to validate the reply. - */ - struct CoinData *coins; - - /** - * Length of the @e coins array. - */ - unsigned int num_coins; - - /** - * Public key of the reserve we are querying. - */ - struct TALER_ReservePublicKeyP reserve_pub; - - /** - * Our signature. - */ - struct TALER_ReserveSignatureP reserve_sig; - - /** - * When did we make the request. - */ - struct GNUNET_TIME_Timestamp ts; - -}; - - -/** - * We received an #MHD_HTTP_OK open code. Handle the JSON - * response. - * - * @param roh handle of the request - * @param j JSON response - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -handle_reserves_open_ok (struct TALER_EXCHANGE_ReservesOpenHandle *roh, - const json_t *j) -{ - struct TALER_EXCHANGE_ReserveOpenResult rs = { - .hr.reply = j, - .hr.http_status = MHD_HTTP_OK, - }; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount_any ("open_cost", - &rs.details.ok.open_cost), - GNUNET_JSON_spec_timestamp ("reserve_expiration", - &rs.details.ok.expiration_time), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, - NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - roh->cb (roh->cb_cls, - &rs); - roh->cb = NULL; - GNUNET_JSON_parse_free (spec); - return GNUNET_OK; -} - - -/** - * We received an #MHD_HTTP_PAYMENT_REQUIRED open code. Handle the JSON - * response. - * - * @param roh handle of the request - * @param j JSON response - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -handle_reserves_open_pr (struct TALER_EXCHANGE_ReservesOpenHandle *roh, - const json_t *j) -{ - struct TALER_EXCHANGE_ReserveOpenResult rs = { - .hr.reply = j, - .hr.http_status = MHD_HTTP_PAYMENT_REQUIRED, - }; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount_any ("open_cost", - &rs.details.payment_required.open_cost), - GNUNET_JSON_spec_timestamp ("reserve_expiration", - &rs.details.payment_required.expiration_time), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, - NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - roh->cb (roh->cb_cls, - &rs); - roh->cb = NULL; - GNUNET_JSON_parse_free (spec); - return GNUNET_OK; -} - - -/** - * We received an #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS open code. Handle the JSON - * response. - * - * @param roh handle of the request - * @param j JSON response - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -handle_reserves_open_kyc (struct TALER_EXCHANGE_ReservesOpenHandle *roh, - const json_t *j) -{ - struct TALER_EXCHANGE_ReserveOpenResult rs = { - .hr.reply = j, - .hr.http_status = MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS, - }; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ( - "h_payto", - &rs.details.unavailable_for_legal_reasons.h_payto), - GNUNET_JSON_spec_uint64 ( - "requirement_row", - &rs.details.unavailable_for_legal_reasons.requirement_row), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, - NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - roh->cb (roh->cb_cls, - &rs); - roh->cb = NULL; - GNUNET_JSON_parse_free (spec); - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /reserves/$RID/open request. - * - * @param cls the `struct TALER_EXCHANGE_ReservesOpenHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_reserves_open_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_ReservesOpenHandle *roh = cls; - const json_t *j = response; - struct TALER_EXCHANGE_ReserveOpenResult rs = { - .hr.reply = j, - .hr.http_status = (unsigned int) response_code - }; - - roh->job = NULL; - switch (response_code) - { - case 0: - rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - if (GNUNET_OK != - handle_reserves_open_ok (roh, - j)) - { - GNUNET_break_op (0); - rs.hr.http_status = 0; - rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - } - break; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - GNUNET_break (0); - json_dumpf (j, - stderr, - JSON_INDENT (2)); - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_PAYMENT_REQUIRED: - if (GNUNET_OK != - handle_reserves_open_pr (roh, - j)) - { - GNUNET_break_op (0); - rs.hr.http_status = 0; - rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - } - break; - case MHD_HTTP_FORBIDDEN: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - GNUNET_break (0); - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_CONFLICT: - { - const struct CoinData *cd = NULL; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("coin_pub", - &rs.details.conflict.coin_pub), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, - NULL)) - { - GNUNET_break_op (0); - rs.hr.http_status = 0; - rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - for (unsigned int i = 0; i<roh->num_coins; i++) - { - const struct CoinData *cdi = &roh->coins[i]; - - if (0 == GNUNET_memcmp (&rs.details.conflict.coin_pub, - &cdi->coin_pub)) - { - cd = cdi; - break; - } - } - if (NULL == cd) - { - GNUNET_break_op (0); - rs.hr.http_status = 0; - rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - } - case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: - if (GNUNET_OK != - handle_reserves_open_kyc (roh, - j)) - { - GNUNET_break_op (0); - rs.hr.http_status = 0; - rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - } - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - rs.hr.ec = TALER_JSON_get_error_code (j); - rs.hr.hint = TALER_JSON_get_error_hint (j); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for reserves open\n", - (unsigned int) response_code, - (int) rs.hr.ec); - break; - } - if (NULL != roh->cb) - { - roh->cb (roh->cb_cls, - &rs); - roh->cb = NULL; - } - TALER_EXCHANGE_reserves_open_cancel (roh); -} - - -struct TALER_EXCHANGE_ReservesOpenHandle * -TALER_EXCHANGE_reserves_open ( - struct GNUNET_CURL_Context *ctx, - const char *url, - struct TALER_EXCHANGE_Keys *keys, - const struct TALER_ReservePrivateKeyP *reserve_priv, - const struct TALER_Amount *reserve_contribution, - unsigned int coin_payments_length, - const struct TALER_EXCHANGE_PurseDeposit coin_payments[ - static coin_payments_length], - struct GNUNET_TIME_Timestamp expiration_time, - uint32_t min_purses, - TALER_EXCHANGE_ReservesOpenCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_ReservesOpenHandle *roh; - CURL *eh; - char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32]; - json_t *cpa; - - roh = GNUNET_new (struct TALER_EXCHANGE_ReservesOpenHandle); - roh->cb = cb; - roh->cb_cls = cb_cls; - roh->ts = GNUNET_TIME_timestamp_get (); - GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, - &roh->reserve_pub.eddsa_pub); - { - char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string ( - &roh->reserve_pub, - sizeof (roh->reserve_pub), - pub_str, - sizeof (pub_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "reserves/%s/open", - pub_str); - } - roh->url = TALER_url_join (url, - arg_str, - NULL); - if (NULL == roh->url) - { - GNUNET_free (roh); - return NULL; - } - eh = TALER_EXCHANGE_curl_easy_get_ (roh->url); - if (NULL == eh) - { - GNUNET_break (0); - GNUNET_free (roh->url); - GNUNET_free (roh); - return NULL; - } - TALER_wallet_reserve_open_sign (reserve_contribution, - roh->ts, - expiration_time, - min_purses, - reserve_priv, - &roh->reserve_sig); - roh->coins = GNUNET_new_array (coin_payments_length, - struct CoinData); - cpa = json_array (); - GNUNET_assert (NULL != cpa); - for (unsigned int i = 0; i<coin_payments_length; i++) - { - const struct TALER_EXCHANGE_PurseDeposit *pd = &coin_payments[i]; - const struct TALER_AgeCommitmentProof *acp = pd->age_commitment_proof; - struct TALER_AgeCommitmentHashP ahac; - struct TALER_AgeCommitmentHashP *achp = NULL; - struct CoinData *cd = &roh->coins[i]; - json_t *cp; - - cd->contribution = pd->amount; - cd->h_denom_pub = pd->h_denom_pub; - if (NULL != acp) - { - TALER_age_commitment_hash (&acp->commitment, - &ahac); - achp = &ahac; - } - TALER_wallet_reserve_open_deposit_sign (&pd->amount, - &roh->reserve_sig, - &pd->coin_priv, - &cd->coin_sig); - GNUNET_CRYPTO_eddsa_key_get_public (&pd->coin_priv.eddsa_priv, - &cd->coin_pub.eddsa_pub); - - cp = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_data_auto ("h_age_commitment", - achp)), - TALER_JSON_pack_amount ("amount", - &pd->amount), - GNUNET_JSON_pack_data_auto ("denom_pub_hash", - &pd->h_denom_pub), - TALER_JSON_pack_denom_sig ("ub_sig", - &pd->denom_sig), - GNUNET_JSON_pack_data_auto ("coin_pub", - &cd->coin_pub), - GNUNET_JSON_pack_data_auto ("coin_sig", - &cd->coin_sig)); - GNUNET_assert (0 == - json_array_append_new (cpa, - cp)); - } - { - json_t *open_obj = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_timestamp ("request_timestamp", - roh->ts), - GNUNET_JSON_pack_timestamp ("reserve_expiration", - expiration_time), - GNUNET_JSON_pack_array_steal ("payments", - cpa), - TALER_JSON_pack_amount ("reserve_payment", - reserve_contribution), - GNUNET_JSON_pack_uint64 ("purse_limit", - min_purses), - GNUNET_JSON_pack_data_auto ("reserve_sig", - &roh->reserve_sig)); - - if (GNUNET_OK != - TALER_curl_easy_post (&roh->post_ctx, - eh, - open_obj)) - { - GNUNET_break (0); - curl_easy_cleanup (eh); - json_decref (open_obj); - GNUNET_free (roh->coins); - GNUNET_free (roh->url); - GNUNET_free (roh); - return NULL; - } - json_decref (open_obj); - } - roh->keys = TALER_EXCHANGE_keys_incref (keys); - roh->job = GNUNET_CURL_job_add2 (ctx, - eh, - roh->post_ctx.headers, - &handle_reserves_open_finished, - roh); - return roh; -} - - -void -TALER_EXCHANGE_reserves_open_cancel ( - struct TALER_EXCHANGE_ReservesOpenHandle *roh) -{ - if (NULL != roh->job) - { - GNUNET_CURL_job_cancel (roh->job); - roh->job = NULL; - } - TALER_curl_easy_post_finished (&roh->post_ctx); - GNUNET_free (roh->coins); - GNUNET_free (roh->url); - TALER_EXCHANGE_keys_decref (roh->keys); - GNUNET_free (roh); -} - - -/* end of exchange_api_reserves_open.c */ diff --git a/src/lib/exchange_api_reveal_melt.c b/src/lib/exchange_api_reveal_melt.c @@ -1,416 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2025 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_reveal_melt.c - * @brief Implementation of the /reveal-melt request - * @author Özgür Kesim - */ -#include "taler/platform.h" -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_json_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_json_lib.h" -#include "taler/taler_exchange_service.h" -#include "exchange_api_common.h" -#include "exchange_api_handle.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" -#include "exchange_api_refresh_common.h" - - -/** - * Handler for a running reveal-melt request - */ -struct TALER_EXCHANGE_RevealMeltHandle -{ - /** - * The url for the request - */ - char *request_url; - - /** - * CURL handle for the request job. - */ - struct GNUNET_CURL_Job *job; - - /** - * Post Context - */ - struct TALER_CURL_PostContext post_ctx; - - /** - * Number of coins to expect - */ - size_t num_expected_coins; - - /** - * The input provided - */ - const struct TALER_EXCHANGE_RevealMeltInput *reveal_input; - - /** - * The melt data - */ - struct MeltData md; - - /** - * Callback to pass the result onto - */ - TALER_EXCHANGE_RevealMeltCallback callback; - - /** - * Closure for @e callback - */ - void *callback_cls; - -}; - -/** - * We got a 200 OK response for the /reveal-melt operation. - * Extract the signed blinded coins and return it to the caller. - * - * @param mrh operation handle - * @param j_response reply from the exchange - * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors - */ -static enum GNUNET_GenericReturnValue -reveal_melt_ok ( - struct TALER_EXCHANGE_RevealMeltHandle *mrh, - const json_t *j_response) -{ - struct TALER_EXCHANGE_RevealMeltResponse response = { - .hr.reply = j_response, - .hr.http_status = MHD_HTTP_OK, - }; - struct TALER_BlindedDenominationSignature blind_sigs[mrh->num_expected_coins]; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_array_of_blinded_denom_sigs ("ev_sigs", - mrh->num_expected_coins, - blind_sigs), - GNUNET_JSON_spec_end () - }; - if (GNUNET_OK != - GNUNET_JSON_parse (j_response, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - { - struct TALER_EXCHANGE_RevealedCoinInfo coins[mrh->num_expected_coins]; - - /* Reconstruct the coins and unblind the signatures */ - for (unsigned int i = 0; i<mrh->num_expected_coins; i++) - { - struct TALER_EXCHANGE_RevealedCoinInfo *rci = &coins[i]; - const struct FreshCoinData *fcd = &mrh->md.fcds[i]; - const struct TALER_DenominationPublicKey *pk; - struct TALER_CoinSpendPublicKeyP coin_pub; - struct TALER_CoinPubHashP coin_hash; - struct TALER_FreshCoin coin; - union GNUNET_CRYPTO_BlindingSecretP bks; - const struct TALER_AgeCommitmentHashP *pah = NULL; - - rci->ps = fcd->ps[mrh->reveal_input->noreveal_index]; - rci->bks = fcd->bks[mrh->reveal_input->noreveal_index]; - rci->age_commitment_proof = NULL; - pk = &fcd->fresh_pk; - if (NULL != mrh->md.melted_coin.age_commitment_proof) - { - rci->age_commitment_proof - = fcd->age_commitment_proofs[mrh->reveal_input->noreveal_index]; - TALER_age_commitment_hash ( - &rci->age_commitment_proof->commitment, - &rci->h_age_commitment); - pah = &rci->h_age_commitment; - } - - TALER_planchet_setup_coin_priv (&rci->ps, - &mrh->reveal_input->blinding_values[i], - &rci->coin_priv); - TALER_planchet_blinding_secret_create (&rci->ps, - &mrh->reveal_input->blinding_values - [i], - &bks); - /* needed to verify the signature, and we didn't store it earlier, - hence recomputing it here... */ - GNUNET_CRYPTO_eddsa_key_get_public (&rci->coin_priv.eddsa_priv, - &coin_pub.eddsa_pub); - TALER_coin_pub_hash (&coin_pub, - pah, - &coin_hash); - if (GNUNET_OK != - TALER_planchet_to_coin (pk, - &blind_sigs[i], - &bks, - &rci->coin_priv, - pah, - &coin_hash, - &mrh->reveal_input->blinding_values[i], - &coin)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return GNUNET_SYSERR; - } - GNUNET_JSON_parse_free (spec); - rci->sig = coin.sig; - } - - response.details.ok.num_coins = mrh->num_expected_coins; - response.details.ok.coins = coins; - mrh->callback (mrh->callback_cls, - &response); - /* Make sure the callback isn't called again */ - mrh->callback = NULL; - /* Free resources */ - for (size_t i = 0; i < mrh->num_expected_coins; i++) - { - struct TALER_EXCHANGE_RevealedCoinInfo *rci = &coins[i]; - - TALER_denom_sig_free (&rci->sig); - TALER_blinded_denom_sig_free (&blind_sigs[i]); - } - } - - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /reveal-melt request. - * - * @param cls the `struct TALER_EXCHANGE_RevealMeltHandle` - * @param response_code The HTTP response code - * @param response response data - */ -static void -handle_reveal_melt_finished ( - void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_RevealMeltHandle *mrh = cls; - const json_t *j_response = response; - struct TALER_EXCHANGE_RevealMeltResponse awr = { - .hr.reply = j_response, - .hr.http_status = (unsigned int) response_code - }; - - mrh->job = NULL; - switch (response_code) - { - case 0: - awr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - { - enum GNUNET_GenericReturnValue ret; - - ret = reveal_melt_ok (mrh, - j_response); - if (GNUNET_OK != ret) - { - GNUNET_break_op (0); - awr.hr.http_status = 0; - awr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - GNUNET_assert (NULL == mrh->callback); - TALER_EXCHANGE_reveal_melt_cancel (mrh); - return; - } - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - awr.hr.ec = TALER_JSON_get_error_code (j_response); - awr.hr.hint = TALER_JSON_get_error_hint (j_response); - break; - case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, the exchange basically just says - that it doesn't know this age-melt commitment. */ - awr.hr.ec = TALER_JSON_get_error_code (j_response); - awr.hr.hint = TALER_JSON_get_error_hint (j_response); - break; - case MHD_HTTP_CONFLICT: - /* An age commitment for one of the coins did not fulfill - * the required maximum age requirement of the corresponding - * reserve. - * Error code: TALER_EC_EXCHANGE_GENERIC_COIN_AGE_REQUIREMENT_FAILURE - * or TALER_EC_EXCHANGE_AGE_WITHDRAW_REVEAL_INVALID_HASH. - */ - awr.hr.ec = TALER_JSON_get_error_code (j_response); - awr.hr.hint = TALER_JSON_get_error_hint (j_response); - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - awr.hr.ec = TALER_JSON_get_error_code (j_response); - awr.hr.hint = TALER_JSON_get_error_hint (j_response); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - awr.hr.ec = TALER_JSON_get_error_code (j_response); - awr.hr.hint = TALER_JSON_get_error_hint (j_response); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange melt\n", - (unsigned int) response_code, - (int) awr.hr.ec); - break; - } - mrh->callback (mrh->callback_cls, - &awr); - TALER_EXCHANGE_reveal_melt_cancel (mrh); -} - - -/** - * Call /reveal-melt - * - * @param curl_ctx The context for CURL - * @param mrh The handler - */ -static void -perform_protocol ( - struct GNUNET_CURL_Context *curl_ctx, - struct TALER_EXCHANGE_RevealMeltHandle *mrh) -{ - CURL *curlh; - json_t *j_batch_seeds; - - - j_batch_seeds = json_array (); - GNUNET_assert (NULL != j_batch_seeds); - - for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++) - { - if (mrh->reveal_input->noreveal_index == k) - continue; - - GNUNET_assert (0 == json_array_append_new ( - j_batch_seeds, - GNUNET_JSON_from_data_auto ( - &mrh->md.kappa_batch_seeds.tuple[k]))); - } - { - json_t *j_request_body; - - j_request_body = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_data_auto ("rc", - &mrh->md.rc), - GNUNET_JSON_pack_array_steal ("batch_seeds", - j_batch_seeds)); - GNUNET_assert (NULL != j_request_body); - - if (NULL != mrh->reveal_input->melt_input->melt_age_commitment_proof) - { - json_t *j_age = GNUNET_JSON_PACK ( - TALER_JSON_pack_age_commitment ( - "age_commitment", - &mrh->reveal_input->melt_input->melt_age_commitment_proof->commitment) - ); - GNUNET_assert (NULL != j_age); - GNUNET_assert (0 == - json_object_update_new (j_request_body, - j_age)); - } - curlh = TALER_EXCHANGE_curl_easy_get_ (mrh->request_url); - GNUNET_assert (NULL != curlh); - GNUNET_assert (GNUNET_OK == - TALER_curl_easy_post (&mrh->post_ctx, - curlh, - j_request_body)); - json_decref (j_request_body); - } - mrh->job = GNUNET_CURL_job_add2 ( - curl_ctx, - curlh, - mrh->post_ctx.headers, - &handle_reveal_melt_finished, - mrh); - if (NULL == mrh->job) - { - GNUNET_break (0); - if (NULL != curlh) - curl_easy_cleanup (curlh); - TALER_EXCHANGE_reveal_melt_cancel (mrh); - } -} - - -struct TALER_EXCHANGE_RevealMeltHandle * -TALER_EXCHANGE_reveal_melt ( - struct GNUNET_CURL_Context *curl_ctx, - const char *exchange_url, - const struct TALER_EXCHANGE_RevealMeltInput *reveal_melt_input, - TALER_EXCHANGE_RevealMeltCallback reveal_cb, - void *reveal_cb_cls) -{ - struct TALER_EXCHANGE_RevealMeltHandle *mrh = - GNUNET_new (struct TALER_EXCHANGE_RevealMeltHandle); - mrh->callback = reveal_cb; - mrh->callback_cls = reveal_cb_cls; - mrh->reveal_input = reveal_melt_input; - mrh->num_expected_coins = reveal_melt_input->melt_input->num_fresh_denom_pubs; - mrh->request_url = TALER_url_join (exchange_url, - "reveal-melt", - NULL); - if (NULL == mrh->request_url) - { - GNUNET_break (0); - GNUNET_free (mrh); - return NULL; - } - if (reveal_melt_input->num_blinding_values != - reveal_melt_input->melt_input->num_fresh_denom_pubs) - { - GNUNET_break (0); - GNUNET_free (mrh); - return NULL; - } - TALER_EXCHANGE_get_melt_data ( - reveal_melt_input->rms, - reveal_melt_input->melt_input, - reveal_melt_input->blinding_seed, - reveal_melt_input->blinding_values, - &mrh->md); - perform_protocol (curl_ctx, - mrh); - return mrh; -} - - -void -TALER_EXCHANGE_reveal_melt_cancel ( - struct TALER_EXCHANGE_RevealMeltHandle *mrh) -{ - if (NULL != mrh->job) - { - GNUNET_CURL_job_cancel (mrh->job); - mrh->job = NULL; - } - TALER_curl_easy_post_finished (&mrh->post_ctx); - TALER_EXCHANGE_free_melt_data (&mrh->md); - GNUNET_free (mrh->request_url); - GNUNET_free (mrh); -} diff --git a/src/lib/exchange_api_reveal_withdraw.c b/src/lib/exchange_api_reveal_withdraw.c @@ -1,366 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2023-2025 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_reveal_withdraw.c - * @brief Implementation of /reveal-withdraw requests - * @author Özgür Kesim - */ - -#include "taler/platform.h" -#include <gnunet/gnunet_common.h> -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_json_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_curl_lib.h" -#include "taler/taler_json_lib.h" -#include "taler/taler_exchange_service.h" -#include "exchange_api_common.h" -#include "exchange_api_handle.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" - -/** - * Handler for a running reveal-withdraw request - */ -struct TALER_EXCHANGE_RevealWithdrawHandle -{ - /** - * The commitment from the previous call withdraw - */ - const struct TALER_HashBlindedPlanchetsP *planchets_h; - - /** - * Number of coins for which to reveal tuples of seeds - */ - size_t num_coins; - - /** - * The TALER_CNC_KAPPA-1 tuple of seeds to reveal - */ - struct TALER_RevealWithdrawMasterSeedsP seeds; - - /** - * The url for the reveal request - */ - char *request_url; - - /** - * CURL handle for the request job. - */ - struct GNUNET_CURL_Job *job; - - /** - * Post Context - */ - struct TALER_CURL_PostContext post_ctx; - - /** - * Callback - */ - TALER_EXCHANGE_RevealWithdrawCallback callback; - - /** - * Reveal - */ - void *callback_cls; -}; - - -/** - * We got a 200 OK response for the /reveal-withdraw operation. - * Extract the signed blindedcoins and return it to the caller. - * - * @param wrh operation handle - * @param j_response reply from the exchange - * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors - */ -static enum GNUNET_GenericReturnValue -reveal_withdraw_ok ( - struct TALER_EXCHANGE_RevealWithdrawHandle *wrh, - const json_t *j_response) -{ - struct TALER_EXCHANGE_RevealWithdrawResponse response = { - .hr.reply = j_response, - .hr.http_status = MHD_HTTP_OK, - }; - const json_t *j_sigs; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_array_const ("ev_sigs", - &j_sigs), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j_response, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - if (wrh->num_coins != json_array_size (j_sigs)) - { - /* Number of coins generated does not match our expectation */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - { - struct TALER_BlindedDenominationSignature denom_sigs[wrh->num_coins]; - json_t *j_sig; - size_t n; - - /* Reconstruct the coins and unblind the signatures */ - json_array_foreach (j_sigs, n, j_sig) - { - struct GNUNET_JSON_Specification ispec[] = { - TALER_JSON_spec_blinded_denom_sig (NULL, - &denom_sigs[n]), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j_sig, - ispec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - } - - response.details.ok.num_sigs = wrh->num_coins; - response.details.ok.blinded_denom_sigs = denom_sigs; - wrh->callback (wrh->callback_cls, - &response); - /* Make sure the callback isn't called again */ - wrh->callback = NULL; - /* Free resources */ - for (size_t i = 0; i < wrh->num_coins; i++) - TALER_blinded_denom_sig_free (&denom_sigs[i]); - } - - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /reveal-withdraw request. - * - * @param cls the `struct TALER_EXCHANGE_RevealWithdrawHandle` - * @param response_code The HTTP response code - * @param response response data - */ -static void -handle_reveal_withdraw_finished ( - void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_RevealWithdrawHandle *wrh = cls; - const json_t *j_response = response; - struct TALER_EXCHANGE_RevealWithdrawResponse awr = { - .hr.reply = j_response, - .hr.http_status = (unsigned int) response_code - }; - - wrh->job = NULL; - switch (response_code) - { - case 0: - awr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - { - enum GNUNET_GenericReturnValue ret; - - ret = reveal_withdraw_ok (wrh, - j_response); - if (GNUNET_OK != ret) - { - GNUNET_break_op (0); - awr.hr.http_status = 0; - awr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - GNUNET_assert (NULL == wrh->callback); - TALER_EXCHANGE_reveal_withdraw_cancel (wrh); - return; - } - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - awr.hr.ec = TALER_JSON_get_error_code (j_response); - awr.hr.hint = TALER_JSON_get_error_hint (j_response); - break; - case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, the exchange basically just says - that it doesn't know this age-withdraw commitment. */ - awr.hr.ec = TALER_JSON_get_error_code (j_response); - awr.hr.hint = TALER_JSON_get_error_hint (j_response); - break; - case MHD_HTTP_CONFLICT: - /* An age commitment for one of the coins did not fulfill - * the required maximum age requirement of the corresponding - * reserve. - * Error code: TALER_EC_EXCHANGE_GENERIC_COIN_AGE_REQUIREMENT_FAILURE - * or TALER_EC_EXCHANGE_AGE_WITHDRAW_REVEAL_INVALID_HASH. - */ - awr.hr.ec = TALER_JSON_get_error_code (j_response); - awr.hr.hint = TALER_JSON_get_error_hint (j_response); - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - awr.hr.ec = TALER_JSON_get_error_code (j_response); - awr.hr.hint = TALER_JSON_get_error_hint (j_response); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - awr.hr.ec = TALER_JSON_get_error_code (j_response); - awr.hr.hint = TALER_JSON_get_error_hint (j_response); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange age-withdraw\n", - (unsigned int) response_code, - (int) awr.hr.ec); - break; - } - wrh->callback (wrh->callback_cls, - &awr); - TALER_EXCHANGE_reveal_withdraw_cancel (wrh); -} - - -/** - * Call /reveal-withdraw - * - * @param curl_ctx The context for CURL - * @param wrh The handler - */ -static void -perform_protocol ( - struct GNUNET_CURL_Context *curl_ctx, - struct TALER_EXCHANGE_RevealWithdrawHandle *wrh) -{ - CURL *curlh; - json_t *j_array_of_secrets; - - j_array_of_secrets = json_array (); - GNUNET_assert (NULL != j_array_of_secrets); - - for (uint8_t k = 0; k < TALER_CNC_KAPPA - 1; k++) - { - json_t *j_sec = GNUNET_JSON_from_data_auto (&wrh->seeds.tuple[k]); - GNUNET_assert (NULL != j_sec); - GNUNET_assert (0 == json_array_append_new (j_array_of_secrets, - j_sec)); - } - { - json_t *j_request_body; - - j_request_body = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_data_auto ("planchets_h", - wrh->planchets_h), - GNUNET_JSON_pack_array_steal ("disclosed_batch_seeds", - j_array_of_secrets)); - GNUNET_assert (NULL != j_request_body); - - curlh = TALER_EXCHANGE_curl_easy_get_ (wrh->request_url); - GNUNET_assert (NULL != curlh); - GNUNET_assert (GNUNET_OK == - TALER_curl_easy_post (&wrh->post_ctx, - curlh, - j_request_body)); - - json_decref (j_request_body); - } - - wrh->job = GNUNET_CURL_job_add2 ( - curl_ctx, - curlh, - wrh->post_ctx.headers, - &handle_reveal_withdraw_finished, - wrh); - if (NULL == wrh->job) - { - GNUNET_break (0); - if (NULL != curlh) - curl_easy_cleanup (curlh); - TALER_EXCHANGE_reveal_withdraw_cancel (wrh); - } - - return; -} - - -struct TALER_EXCHANGE_RevealWithdrawHandle * -TALER_EXCHANGE_reveal_withdraw ( - struct GNUNET_CURL_Context *curl_ctx, - const char *exchange_url, - size_t num_coins, - const struct TALER_HashBlindedPlanchetsP *planchets_h, - const struct TALER_RevealWithdrawMasterSeedsP *seeds, - TALER_EXCHANGE_RevealWithdrawCallback reveal_cb, - void *reveal_cb_cls) -{ - struct TALER_EXCHANGE_RevealWithdrawHandle *wrh = - GNUNET_new (struct TALER_EXCHANGE_RevealWithdrawHandle); - wrh->planchets_h = planchets_h; - wrh->num_coins = num_coins; - wrh->seeds = *seeds; - wrh->callback = reveal_cb; - wrh->callback_cls = reveal_cb_cls; - wrh->request_url = TALER_url_join (exchange_url, - "reveal-withdraw", - NULL); - if (NULL == wrh->request_url) - { - GNUNET_break (0); - GNUNET_free (wrh); - return NULL; - } - - perform_protocol (curl_ctx, wrh); - - return wrh; -} - - -void -TALER_EXCHANGE_reveal_withdraw_cancel ( - struct TALER_EXCHANGE_RevealWithdrawHandle *wrh) -{ - if (NULL != wrh->job) - { - GNUNET_CURL_job_cancel (wrh->job); - wrh->job = NULL; - } - TALER_curl_easy_post_finished (&wrh->post_ctx); - - if (NULL != wrh->request_url) - GNUNET_free (wrh->request_url); - - GNUNET_free (wrh); -} - - -/* exchange_api_reveal_withdraw.c */ diff --git a/src/lib/exchange_api_transfers_get.c b/src/lib/exchange_api_transfers_get.c @@ -1,400 +0,0 @@ -/* - This file is part of TALER - 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 General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_transfers_get.c - * @brief Implementation of the GET /transfers/ request - * @author Christian Grothoff - */ -#include "taler/platform.h" -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler/taler_exchange_service.h" -#include "taler/taler_json_lib.h" -#include "exchange_api_handle.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" - - -/** - * @brief A /transfers/ GET Handle - */ -struct TALER_EXCHANGE_TransfersGetHandle -{ - - /** - * The keys of the exchange this request handle will use - */ - struct TALER_EXCHANGE_Keys *keys; - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_TransfersGetCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - -}; - - -/** - * We got a #MHD_HTTP_OK response for the /transfers/ request. - * Check that the response is well-formed and if it is, call the - * callback. If not, return an error code. - * - * This code is very similar to - * merchant_api_track_transfer.c::check_transfers_get_response_ok. - * Any changes should likely be reflected there as well. - * - * @param wdh handle to the operation - * @param json response we got - * @return #GNUNET_OK if we are done and all is well, - * #GNUNET_SYSERR if the response was bogus - */ -static enum GNUNET_GenericReturnValue -check_transfers_get_response_ok ( - struct TALER_EXCHANGE_TransfersGetHandle *wdh, - const json_t *json) -{ - const json_t *details_j; - struct TALER_Amount total_expected; - struct TALER_MerchantPublicKeyP merchant_pub; - struct TALER_EXCHANGE_TransfersGetResponse tgr = { - .hr.reply = json, - .hr.http_status = MHD_HTTP_OK - }; - struct TALER_EXCHANGE_TransferData *td - = &tgr.details.ok.td; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_amount_any ("total", - &td->total_amount), - TALER_JSON_spec_amount_any ("wire_fee", - &td->wire_fee), - GNUNET_JSON_spec_fixed_auto ("merchant_pub", - &merchant_pub), - GNUNET_JSON_spec_fixed_auto ("h_payto", - &td->h_payto), - GNUNET_JSON_spec_timestamp ("execution_time", - &td->execution_time), - GNUNET_JSON_spec_array_const ("deposits", - &details_j), - GNUNET_JSON_spec_fixed_auto ("exchange_sig", - &td->exchange_sig), - GNUNET_JSON_spec_fixed_auto ("exchange_pub", - &td->exchange_pub), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_amount_set_zero (td->total_amount.currency, - &total_expected)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_EXCHANGE_test_signing_key ( - wdh->keys, - &td->exchange_pub)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - td->details_length = json_array_size (details_j); - { - struct GNUNET_HashContext *hash_context; - struct TALER_TrackTransferDetails *details; - - details = GNUNET_new_array (td->details_length, - struct TALER_TrackTransferDetails); - td->details = details; - hash_context = GNUNET_CRYPTO_hash_context_start (); - for (unsigned int i = 0; i<td->details_length; i++) - { - struct TALER_TrackTransferDetails *detail = &details[i]; - struct json_t *detail_j = json_array_get (details_j, i); - struct GNUNET_JSON_Specification spec_detail[] = { - GNUNET_JSON_spec_fixed_auto ("h_contract_terms", - &detail->h_contract_terms), - GNUNET_JSON_spec_fixed_auto ("coin_pub", &detail->coin_pub), - TALER_JSON_spec_amount ("deposit_value", - total_expected.currency, - &detail->coin_value), - TALER_JSON_spec_amount ("deposit_fee", - total_expected.currency, - &detail->coin_fee), - GNUNET_JSON_spec_mark_optional ( - TALER_JSON_spec_amount ("refund_total", - total_expected.currency, - &detail->refund_total), - NULL), - GNUNET_JSON_spec_end () - }; - - GNUNET_assert (GNUNET_OK == - TALER_amount_set_zero (td->total_amount.currency, - &detail->refund_total)); - if ( (GNUNET_OK != - GNUNET_JSON_parse (detail_j, - spec_detail, - NULL, NULL)) || - (0 > - TALER_amount_add (&total_expected, - &total_expected, - &detail->coin_value)) || - (0 > - TALER_amount_subtract (&total_expected, - &total_expected, - &detail->coin_fee)) ) - { - GNUNET_break_op (0); - GNUNET_CRYPTO_hash_context_abort (hash_context); - GNUNET_free (details); - return GNUNET_SYSERR; - } - /* build up big hash for signature checking later */ - TALER_exchange_online_wire_deposit_append ( - hash_context, - &detail->h_contract_terms, - td->execution_time, - &detail->coin_pub, - &detail->coin_value, - &detail->coin_fee); - } - /* Check signature */ - GNUNET_CRYPTO_hash_context_finish (hash_context, - &td->h_details); - if (GNUNET_OK != - TALER_exchange_online_wire_deposit_verify ( - &td->total_amount, - &td->wire_fee, - &merchant_pub, - &td->h_payto, - &td->h_details, - &td->exchange_pub, - &td->exchange_sig)) - { - GNUNET_break_op (0); - GNUNET_free (details); - return GNUNET_SYSERR; - } - if (0 > - TALER_amount_subtract (&total_expected, - &total_expected, - &td->wire_fee)) - { - GNUNET_break_op (0); - GNUNET_free (details); - return GNUNET_SYSERR; - } - if (0 != - TALER_amount_cmp (&total_expected, - &td->total_amount)) - { - GNUNET_break_op (0); - GNUNET_free (details); - return GNUNET_SYSERR; - } - wdh->cb (wdh->cb_cls, - &tgr); - GNUNET_free (details); - } - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /transfers/ request. - * - * @param cls the `struct TALER_EXCHANGE_TransfersGetHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_transfers_get_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_TransfersGetHandle *wdh = cls; - const json_t *j = response; - struct TALER_EXCHANGE_TransfersGetResponse tgr = { - .hr.reply = j, - .hr.http_status = (unsigned int) response_code - }; - - wdh->job = NULL; - switch (response_code) - { - case 0: - tgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - if (GNUNET_OK == - check_transfers_get_response_ok (wdh, - j)) - { - TALER_EXCHANGE_transfers_get_cancel (wdh); - return; - } - GNUNET_break_op (0); - tgr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - tgr.hr.http_status = 0; - break; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - tgr.hr.ec = TALER_JSON_get_error_code (j); - tgr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_FORBIDDEN: - /* Nothing really to verify, exchange says one of the signatures is - invalid; as we checked them, this should never happen, we - should pass the JSON reply to the application */ - tgr.hr.ec = TALER_JSON_get_error_code (j); - tgr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_NOT_FOUND: - /* Exchange does not know about transaction; - we should pass the reply to the application */ - tgr.hr.ec = TALER_JSON_get_error_code (j); - tgr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - tgr.hr.ec = TALER_JSON_get_error_code (j); - tgr.hr.hint = TALER_JSON_get_error_hint (j); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - tgr.hr.ec = TALER_JSON_get_error_code (j); - tgr.hr.hint = TALER_JSON_get_error_hint (j); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for transfers get\n", - (unsigned int) response_code, - (int) tgr.hr.ec); - break; - } - wdh->cb (wdh->cb_cls, - &tgr); - TALER_EXCHANGE_transfers_get_cancel (wdh); -} - - -struct TALER_EXCHANGE_TransfersGetHandle * -TALER_EXCHANGE_transfers_get ( - struct GNUNET_CURL_Context *ctx, - const char *url, - struct TALER_EXCHANGE_Keys *keys, - const struct TALER_WireTransferIdentifierRawP *wtid, - TALER_EXCHANGE_TransfersGetCallback cb, - void *cb_cls) -{ - struct TALER_EXCHANGE_TransfersGetHandle *wdh; - CURL *eh; - char arg_str[sizeof (struct TALER_WireTransferIdentifierRawP) * 2 + 32]; - - wdh = GNUNET_new (struct TALER_EXCHANGE_TransfersGetHandle); - wdh->cb = cb; - wdh->cb_cls = cb_cls; - - { - char wtid_str[sizeof (struct TALER_WireTransferIdentifierRawP) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string (wtid, - sizeof (struct - TALER_WireTransferIdentifierRawP), - wtid_str, - sizeof (wtid_str)); - *end = '\0'; - GNUNET_snprintf (arg_str, - sizeof (arg_str), - "transfers/%s", - wtid_str); - } - wdh->url = TALER_url_join (url, - arg_str, - NULL); - if (NULL == wdh->url) - { - GNUNET_free (wdh); - return NULL; - } - eh = TALER_EXCHANGE_curl_easy_get_ (wdh->url); - if (NULL == eh) - { - GNUNET_break (0); - GNUNET_free (wdh->url); - GNUNET_free (wdh); - return NULL; - } - wdh->keys = TALER_EXCHANGE_keys_incref (keys); - wdh->job = GNUNET_CURL_job_add_with_ct_json (ctx, - eh, - &handle_transfers_get_finished, - wdh); - return wdh; -} - - -/** - * Cancel wire deposits request. This function cannot be used on a request - * handle if a response is already served for it. - * - * @param wdh the wire deposits request handle - */ -void -TALER_EXCHANGE_transfers_get_cancel ( - struct TALER_EXCHANGE_TransfersGetHandle *wdh) -{ - if (NULL != wdh->job) - { - GNUNET_CURL_job_cancel (wdh->job); - wdh->job = NULL; - } - GNUNET_free (wdh->url); - TALER_EXCHANGE_keys_decref (wdh->keys); - GNUNET_free (wdh); -} - - -/* end of exchange_api_transfers_get.c */ diff --git a/src/lib/exchange_api_withdraw.c b/src/lib/exchange_api_withdraw.c @@ -1,1928 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2023-2025 Taler Systems SA - - TALER 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. - - TALER 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 - TALER; see the file COPYING. If not, see - <http://www.gnu.org/licenses/> -*/ -/** - * @file lib/exchange_api_withdraw.c - * @brief Implementation of /withdraw requests - * @author Özgür Kesim - */ -/** - * We want the "dangerous" exports here as these are OUR exports - * and we want to check that the prototypes match. - */ -#define TALER_TESTING_EXPORTS_DANGEROUS 1 -#include "taler/platform.h" -#include <gnunet/gnunet_common.h> -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_json_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include <sys/wait.h> -#include "taler/taler_curl_lib.h" -#include "taler/taler_error_codes.h" -#include "taler/taler_json_lib.h" -#include "taler/taler_exchange_service.h" -#include "exchange_api_common.h" -#include "exchange_api_handle.h" -#include "taler/taler_signatures.h" -#include "exchange_api_curl_defaults.h" -#include "taler/taler_util.h" - -/** - * A CoinCandidate is populated from a master secret. - * The data is copied from and generated out of the client's input. - */ -struct CoinCandidate -{ - /** - * The details derived form the master secrets - */ - struct TALER_EXCHANGE_WithdrawCoinPrivateDetails details; - - /** - * Blinded hash of the coin - **/ - struct TALER_BlindedCoinHashP blinded_coin_h; - -}; - - -/** - * Closure for a call to /blinding-prepare, contains data that is needed to process - * the result. - */ -struct BlindingPrepareClosure -{ - /** - * Number of coins in the blinding-prepare step. - * Not that this number might be smaller than the total number - * of coins in the withdraw, as the prepare is only necessary - * for CS denominations - */ - size_t num_prepare_coins; - - /** - * Array of @e num_prepare_coins of data per coin - */ - struct BlindingPrepareCoinData - { - /** - * Pointer to the candidate in CoinData.candidates, - * to continue to build its contents based on the results from /blinding-prepare - */ - struct CoinCandidate *candidate; - - /** - * Planchet to finally generate in the corresponding candidate - * in CoindData.planchet_details - */ - struct TALER_PlanchetDetail *planchet; - - /** - * Denomination information, needed for the - * step after /blinding-prepare - */ - const struct TALER_DenominationPublicKey *denom_pub; - - /** - * True, if denomination supports age restriction - */ - bool age_denom; - - /** - * The index into the array of returned values from the call to - * /blinding-prepare that are to be used for this coin. - */ - size_t cs_idx; - - } *coins; - - /** - * Number of seeds requested. This may differ from @e num_prepare_coins - * in case of a withdraw with required age proof, in which case - * @e num_prepare_coins = TALER_CNC_KAPPA * @e num_seeds - */ - size_t num_nonces; - - /** - * Array of @e num_nonces calculated nonces. - */ - union GNUNET_CRYPTO_BlindSessionNonce *nonces; - - /** - * Handler to the originating call to /withdraw, needed to either - * cancel the running withdraw request (on failure of the current call - * to /blinding-prepare), or to eventually perform the protocol, once all - * blinding-prepare requests have successfully finished. - */ - struct TALER_EXCHANGE_WithdrawHandle *withdraw_handle; - -}; - - -/** - * Data we keep per coin in the batch. - * This is copied from and generated out of the input provided - * by the client. - */ -struct CoinData -{ - /** - * The denomination of the coin. - */ - struct TALER_EXCHANGE_DenomPublicKey denom_pub; - - /** - * The Candidates for the coin. If the batch is not age-restricted, - * only index 0 is used. - */ - struct CoinCandidate candidates[TALER_CNC_KAPPA]; - - /** - * Details of the planchet(s). If the batch is not age-restricted, - * only index 0 is used. - */ - struct TALER_PlanchetDetail planchet_details[TALER_CNC_KAPPA]; -}; - - -/** - * A /withdraw request-handle for calls with pre-blinded planchets. - * Returned by TALER_EXCHANGE_withdraw_blinded. - */ -struct TALER_EXCHANGE_WithdrawBlindedHandle -{ - - /** - * Reserve private key. - */ - const struct TALER_ReservePrivateKeyP *reserve_priv; - - /** - * Reserve public key, calculated - */ - struct TALER_ReservePublicKeyP reserve_pub; - - /** - * Signature of the reserve for the request, calculated after all - * parameters for the coins are collected. - */ - struct TALER_ReserveSignatureP reserve_sig; - - /* - * The denomination keys of the exchange - */ - struct TALER_EXCHANGE_Keys *keys; - - /** - * The hash of all the planchets - */ - struct TALER_HashBlindedPlanchetsP planchets_h; - - /** - * Seed used for the derival of blinding factors for denominations - * with Clause-Schnorr cipher. We derive this from the master seed - * for the withdraw, but independent from the other planchet seeds. - */ - const struct TALER_BlindingMasterSeedP *blinding_seed; - - /** - * Total amount requested (without fee). - */ - struct TALER_Amount amount; - - /** - * Total withdraw fee - */ - struct TALER_Amount fee; - - /** - * Is this call for age-restriced coins, with age proof? - */ - bool with_age_proof; - - /** - * If @e with_age_proof is true or @max_age is > 0, - * the age mask to use, extracted from the denominations. - * MUST be the same for all denominations. - */ - struct TALER_AgeMask age_mask; - - /** - * The maximum age to commit to. If @e with_age_proof - * is true, the client will need to proof the correct setting - * of age-restriction on the coins via an additional call - * to /reveal-withdraw. - */ - uint8_t max_age; - - /** - * If @e with_age_proof is true, the hash of all the selected planchets - */ - struct TALER_HashBlindedPlanchetsP selected_h; - - /** - * Length of the either the @e blinded.input or - * the @e blinded.with_age_proof_input array, - * depending on @e with_age_proof. - */ - size_t num_input; - - union - { - /** - * The blinded planchet input candidates for age-restricted coins - * for the call to /withdraw - */ - const struct - TALER_EXCHANGE_WithdrawBlindedAgeRestrictedCoinInput *with_age_proof_input; - - /** - * The blinded planchet input for the call to /withdraw via - * TALER_EXCHANGE_withdraw_blinded, for age-unrestricted coins. - */ - const struct TALER_EXCHANGE_WithdrawBlindedCoinInput *input; - - } blinded; - - /** - * The url for this request. - */ - char *request_url; - - /** - * Context for curl. - */ - struct GNUNET_CURL_Context *curl_ctx; - - /** - * CURL handle for the request job. - */ - struct GNUNET_CURL_Job *job; - - /** - * Post Context - */ - struct TALER_CURL_PostContext post_ctx; - - /** - * Function to call with withdraw response results. - */ - TALER_EXCHANGE_WithdrawBlindedCallback callback; - - /** - * Closure for @e blinded_callback - */ - void *callback_cls; -}; - -/** - * A /withdraw request-handle for calls from - * a wallet, i. e. when blinding data is available. - */ -struct TALER_EXCHANGE_WithdrawHandle -{ - - /** - * The base-URL of the exchange. - */ - const char *exchange_url; - - /** - * Seed to derive of all seeds for the coins. - */ - struct TALER_WithdrawMasterSeedP seed; - - /** - * If @e with_age_proof is true, the derived TALER_CNC_KAPPA many - * seeds for candidate batches. - */ - struct TALER_KappaWithdrawMasterSeedP kappa_seed; - - /** - * True if @e blinding_seed is filled, that is, if - * any of the denominations is of cipher type CS - */ - bool has_blinding_seed; - - /** - * Seed used for the derivation of blinding factors for denominations - * with Clause-Schnorr cipher. We derive this from the master seed - * for the withdraw, but independent from the other planchet seeds. - * Only valid when @e has_blinding_seed is true; - */ - struct TALER_BlindingMasterSeedP blinding_seed; - - /** - * Reserve private key. - */ - const struct TALER_ReservePrivateKeyP *reserve_priv; - - /** - * Reserve public key, calculated - */ - struct TALER_ReservePublicKeyP reserve_pub; - - /** - * Signature of the reserve for the request, calculated after all - * parameters for the coins are collected. - */ - struct TALER_ReserveSignatureP reserve_sig; - - /* - * The denomination keys of the exchange - */ - struct TALER_EXCHANGE_Keys *keys; - - /** - * True, if the withdraw is for age-restricted coins, with age-proof. - * The denominations MUST support age restriction. - */ - bool with_age_proof; - - /** - * If @e with_age_proof is true, the age mask, extracted - * from the denominations. - * MUST be the same for all denominations. - * - */ - struct TALER_AgeMask age_mask; - - /** - * The maximum age to commit to. If @e with_age_proof - * is true, the client will need to proof the correct setting - * of age-restriction on the coins via an additional call - * to /reveal-withdraw. - */ - uint8_t max_age; - - /** - * Length of the @e coin_data Array - */ - size_t num_coins; - - /** - * Array of per-coin data - */ - struct CoinData *coin_data; - - /** - * Context for curl. - */ - struct GNUNET_CURL_Context *curl_ctx; - - /** - * Function to call with withdraw response results. - */ - TALER_EXCHANGE_WithdrawCallback callback; - - /** - * Closure for @e callback - */ - void *callback_cls; - - /* The handler for the call to /blinding-prepare, needed for CS denominations */ - struct TALER_EXCHANGE_BlindingPrepareHandle *blinding_prepare_handle; - - /* The Handler for the actual call to the exchange */ - struct TALER_EXCHANGE_WithdrawBlindedHandle *withdraw_blinded_handle; -}; - - -/** - * We got a 200 OK response for the /withdraw operation. - * Extract the signatures and return them to the caller. - * - * @param wbh operation handle - * @param j_response reply from the exchange - * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors - */ -static enum GNUNET_GenericReturnValue -withdraw_blinded_ok ( - struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh, - const json_t *j_response) -{ - struct TALER_EXCHANGE_WithdrawBlindedResponse response = { - .hr.reply = j_response, - .hr.http_status = MHD_HTTP_OK, - }; - const json_t *j_sigs; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_array_const ("ev_sigs", - &j_sigs), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j_response, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - if (wbh->num_input != json_array_size (j_sigs)) - { - /* Number of coins generated does not match our expectation */ - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - { - struct TALER_BlindedDenominationSignature denoms_sig[wbh->num_input]; - - memset (denoms_sig, - 0, - sizeof(denoms_sig)); - - /* Reconstruct the coins and unblind the signatures */ - { - json_t *j_sig; - size_t i; - - json_array_foreach (j_sigs, i, j_sig) - { - struct GNUNET_JSON_Specification ispec[] = { - TALER_JSON_spec_blinded_denom_sig (NULL, - &denoms_sig[i]), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j_sig, - ispec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - } - } - - response.details.ok.num_sigs = wbh->num_input; - response.details.ok.blinded_denom_sigs = denoms_sig; - response.details.ok.planchets_h = wbh->planchets_h; - wbh->callback ( - wbh->callback_cls, - &response); - /* Make sure the callback isn't called again */ - wbh->callback = NULL; - /* Free resources */ - for (size_t i = 0; i < wbh->num_input; i++) - TALER_blinded_denom_sig_free (&denoms_sig[i]); - } - - return GNUNET_OK; -} - - -/** - * We got a 201 CREATED response for the /withdraw operation. - * Extract the noreveal_index and return it to the caller. - * - * @param wbh operation handle - * @param j_response reply from the exchange - * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors - */ -static enum GNUNET_GenericReturnValue -withdraw_blinded_created ( - struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh, - const json_t *j_response) -{ - struct TALER_EXCHANGE_WithdrawBlindedResponse response = { - .hr.reply = j_response, - .hr.http_status = MHD_HTTP_CREATED, - .details.created.planchets_h = wbh->planchets_h, - .details.created.num_coins = wbh->num_input, - }; - struct TALER_ExchangeSignatureP exchange_sig; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_uint8 ("noreveal_index", - &response.details.created.noreveal_index), - GNUNET_JSON_spec_fixed_auto ("exchange_sig", - &exchange_sig), - GNUNET_JSON_spec_fixed_auto ("exchange_pub", - &response.details.created.exchange_pub), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK!= - GNUNET_JSON_parse (j_response, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - if (GNUNET_OK != - TALER_exchange_online_withdraw_age_confirmation_verify ( - &wbh->planchets_h, - response.details.created.noreveal_index, - &response.details.created.exchange_pub, - &exchange_sig)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - - } - - wbh->callback (wbh->callback_cls, - &response); - /* make sure the callback isn't called again */ - wbh->callback = NULL; - - return GNUNET_OK; -} - - -/** - * Function called when we're done processing the - * HTTP /withdraw request. - * - * @param cls the `struct TALER_EXCHANGE_WithdrawBlindedHandle` - * @param response_code The HTTP response code - * @param response response data - */ -static void -handle_withdraw_blinded_finished ( - void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh = cls; - const json_t *j_response = response; - struct TALER_EXCHANGE_WithdrawBlindedResponse wbr = { - .hr.reply = j_response, - .hr.http_status = (unsigned int) response_code - }; - - wbh->job = NULL; - switch (response_code) - { - case 0: - wbr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - { - if (GNUNET_OK != - withdraw_blinded_ok ( - wbh, - j_response)) - { - GNUNET_break_op (0); - wbr.hr.http_status = 0; - wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - GNUNET_assert (NULL == wbh->callback); - TALER_EXCHANGE_withdraw_blinded_cancel (wbh); - return; - } - case MHD_HTTP_CREATED: - if (GNUNET_OK != - withdraw_blinded_created ( - wbh, - j_response)) - { - GNUNET_break_op (0); - wbr.hr.http_status = 0; - wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - GNUNET_assert (NULL == wbh->callback); - TALER_EXCHANGE_withdraw_blinded_cancel (wbh); - return; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - wbr.hr.ec = TALER_JSON_get_error_code (j_response); - wbr.hr.hint = TALER_JSON_get_error_hint (j_response); - break; - case MHD_HTTP_FORBIDDEN: - GNUNET_break_op (0); - /* Nothing really to verify, exchange says one of the signatures is - invalid; as we checked them, this should never happen, we - should pass the JSON reply to the application */ - wbr.hr.ec = TALER_JSON_get_error_code (j_response); - wbr.hr.hint = TALER_JSON_get_error_hint (j_response); - break; - case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, the exchange basically just says - that it doesn't know this reserve. Can happen if we - query before the wire transfer went through. - We should simply pass the JSON reply to the application. */ - wbr.hr.ec = TALER_JSON_get_error_code (j_response); - wbr.hr.hint = TALER_JSON_get_error_hint (j_response); - break; - case MHD_HTTP_CONFLICT: - /* The age requirements might not have been met */ - wbr.hr.ec = TALER_JSON_get_error_code (j_response); - wbr.hr.hint = TALER_JSON_get_error_hint (j_response); - break; - case MHD_HTTP_GONE: - /* could happen if denomination was revoked */ - /* Note: one might want to check /keys for revocation - signature here, alas tricky in case our /keys - is outdated => left to clients */ - wbr.hr.ec = TALER_JSON_get_error_code (j_response); - wbr.hr.hint = TALER_JSON_get_error_hint (j_response); - break; - case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: - /* only validate reply is well-formed */ - { - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ( - "h_payto", - &wbr.details.unavailable_for_legal_reasons.h_payto), - GNUNET_JSON_spec_uint64 ( - "requirement_row", - &wbr.details.unavailable_for_legal_reasons.requirement_row), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j_response, - spec, - NULL, NULL)) - { - GNUNET_break_op (0); - wbr.hr.http_status = 0; - wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - break; - } - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - wbr.hr.ec = TALER_JSON_get_error_code (j_response); - wbr.hr.hint = TALER_JSON_get_error_hint (j_response); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - wbr.hr.ec = TALER_JSON_get_error_code (j_response); - wbr.hr.hint = TALER_JSON_get_error_hint (j_response); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange withdraw\n", - (unsigned int) response_code, - (int) wbr.hr.ec); - break; - } - wbh->callback (wbh->callback_cls, - &wbr); - TALER_EXCHANGE_withdraw_blinded_cancel (wbh); -} - - -/** - * Runs the actual withdraw operation with the blinded planchets. - * - * @param[in,out] wbh withdraw blinded handle - */ -static void -perform_withdraw_protocol ( - struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh) -{ -#define FAIL_IF(cond) \ - do { \ - if ((cond)) \ - { \ - GNUNET_break (! (cond)); \ - goto ERROR; \ - } \ - } while (0) - - json_t *j_denoms = NULL; - json_t *j_planchets = NULL; - json_t *j_request_body = NULL; - CURL *curlh = NULL; - struct GNUNET_HashContext *coins_hctx = NULL; - struct TALER_BlindedCoinHashP bch; - - GNUNET_assert (0 < wbh->num_input); - - FAIL_IF (GNUNET_OK != - TALER_amount_set_zero (wbh->keys->currency, - &wbh->amount)); - FAIL_IF (GNUNET_OK != - TALER_amount_set_zero (wbh->keys->currency, - &wbh->fee)); - - /* Accumulate total value with fees */ - for (size_t i = 0; i < wbh->num_input; i++) - { - const struct TALER_EXCHANGE_DenomPublicKey *dpub = - wbh->with_age_proof ? - wbh->blinded.with_age_proof_input[i].denom_pub : - wbh->blinded.input[i].denom_pub; - - FAIL_IF (0 > - TALER_amount_add (&wbh->amount, - &wbh->amount, - &dpub->value)); - FAIL_IF (0 > - TALER_amount_add (&wbh->fee, - &wbh->fee, - &dpub->fees.withdraw)); - - if (GNUNET_CRYPTO_BSA_CS == - dpub->key.bsign_pub_key->cipher) - GNUNET_assert (NULL != wbh->blinding_seed); - - } - - if (wbh->with_age_proof || wbh->max_age > 0) - { - wbh->age_mask = - wbh->blinded.with_age_proof_input[0].denom_pub->key.age_mask; - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Attempting to withdraw from reserve %s with maximum age %d to proof\n", - TALER_B2S (&wbh->reserve_pub), - wbh->max_age); - } - else - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Attempting to withdraw from reserve %s\n", - TALER_B2S (&wbh->reserve_pub)); - } - - coins_hctx = GNUNET_CRYPTO_hash_context_start (); - FAIL_IF (NULL == coins_hctx); - - j_denoms = json_array (); - j_planchets = json_array (); - FAIL_IF ((NULL == j_denoms) || - (NULL == j_planchets)); - - for (size_t i = 0; i< wbh->num_input; i++) - { - /* Build the denomination array */ - const struct TALER_EXCHANGE_DenomPublicKey *denom_pub = - wbh->with_age_proof ? - wbh->blinded.with_age_proof_input[i].denom_pub : - wbh->blinded.input[i].denom_pub; - const struct TALER_DenominationHashP *denom_h = &denom_pub->h_key; - json_t *jdenom; - - /* The mask must be the same for all coins */ - FAIL_IF (wbh->with_age_proof && - (wbh->age_mask.bits != denom_pub->key.age_mask.bits)); - - jdenom = GNUNET_JSON_from_data_auto (denom_h); - FAIL_IF (NULL == jdenom); - FAIL_IF (0 > json_array_append_new (j_denoms, - jdenom)); - } - - - /* Build the planchet array and calculate the hash over all planchets. */ - if (! wbh->with_age_proof) - { - for (size_t i = 0; i< wbh->num_input; i++) - { - const struct TALER_PlanchetDetail *planchet = - &wbh->blinded.input[i].planchet_details; - json_t *jc = GNUNET_JSON_PACK ( - TALER_JSON_pack_blinded_planchet ( - NULL, - &planchet->blinded_planchet)); - FAIL_IF (NULL == jc); - FAIL_IF (0 > json_array_append_new (j_planchets, - jc)); - - TALER_coin_ev_hash (&planchet->blinded_planchet, - &planchet->denom_pub_hash, - &bch); - - GNUNET_CRYPTO_hash_context_read (coins_hctx, - &bch, - sizeof(bch)); - } - } - else - { /* Age restricted case with required age-proof. */ - - /** - * We collect the run of all coin candidates for the same γ index - * first, then γ+1 etc. - */ - for (size_t k = 0; k < TALER_CNC_KAPPA; k++) - { - struct GNUNET_HashContext *batch_ctx; - struct TALER_BlindedCoinHashP batch_h; - - batch_ctx = GNUNET_CRYPTO_hash_context_start (); - FAIL_IF (NULL == batch_ctx); - - for (size_t i = 0; i< wbh->num_input; i++) - { - const struct TALER_PlanchetDetail *planchet = - &wbh->blinded.with_age_proof_input[i].planchet_details[k]; - json_t *jc = GNUNET_JSON_PACK ( - TALER_JSON_pack_blinded_planchet ( - NULL, - &planchet->blinded_planchet)); - - FAIL_IF (NULL == jc); - FAIL_IF (0 > json_array_append_new ( - j_planchets, - jc)); - - TALER_coin_ev_hash ( - &planchet->blinded_planchet, - &planchet->denom_pub_hash, - &bch); - - GNUNET_CRYPTO_hash_context_read ( - batch_ctx, - &bch, - sizeof(bch)); - } - - GNUNET_CRYPTO_hash_context_finish ( - batch_ctx, - &batch_h.hash); - GNUNET_CRYPTO_hash_context_read ( - coins_hctx, - &batch_h, - sizeof(batch_h)); - } - } - - /* Build the hash of the planchets */ - GNUNET_CRYPTO_hash_context_finish ( - coins_hctx, - &wbh->planchets_h.hash); - coins_hctx = NULL; - - /* Sign the request */ - TALER_wallet_withdraw_sign ( - &wbh->amount, - &wbh->fee, - &wbh->planchets_h, - wbh->blinding_seed, - wbh->with_age_proof ? &wbh->age_mask: NULL, - wbh->with_age_proof ? wbh->max_age : 0, - wbh->reserve_priv, - &wbh->reserve_sig); - - /* Initiate the POST-request */ - j_request_body = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("cipher", - "ED25519"), - GNUNET_JSON_pack_data_auto ("reserve_pub", - &wbh->reserve_pub), - GNUNET_JSON_pack_array_steal ("denoms_h", - j_denoms), - GNUNET_JSON_pack_array_steal ("coin_evs", - j_planchets), - GNUNET_JSON_pack_allow_null ( - wbh->with_age_proof - ? GNUNET_JSON_pack_int64 ("max_age", - wbh->max_age) - : GNUNET_JSON_pack_string ("max_age", - NULL) ), - GNUNET_JSON_pack_data_auto ("reserve_sig", - &wbh->reserve_sig)); - FAIL_IF (NULL == j_request_body); - - if (NULL != wbh->blinding_seed) - { - json_t *j_seed = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_data_auto ("blinding_seed", - wbh->blinding_seed)); - GNUNET_assert (NULL != j_seed); - GNUNET_assert (0 == - json_object_update_new ( - j_request_body, - j_seed)); - } - - curlh = TALER_EXCHANGE_curl_easy_get_ (wbh->request_url); - FAIL_IF (NULL == curlh); - FAIL_IF (GNUNET_OK != - TALER_curl_easy_post ( - &wbh->post_ctx, - curlh, - j_request_body)); - json_decref (j_request_body); - j_request_body = NULL; - - wbh->job = GNUNET_CURL_job_add2 ( - wbh->curl_ctx, - curlh, - wbh->post_ctx.headers, - &handle_withdraw_blinded_finished, - wbh); - FAIL_IF (NULL == wbh->job); - - /* No errors, return */ - return; - -ERROR: - if (NULL != coins_hctx) - GNUNET_CRYPTO_hash_context_abort (coins_hctx); - if (NULL != j_denoms) - json_decref (j_denoms); - if (NULL != j_planchets) - json_decref (j_planchets); - if (NULL != j_request_body) - json_decref (j_request_body); - if (NULL != curlh) - curl_easy_cleanup (curlh); - TALER_EXCHANGE_withdraw_blinded_cancel (wbh); - return; -#undef FAIL_IF -} - - -/** - * @brief Callback to copy the results from the call to TALER_withdraw_blinded - * in the non-age-restricted case to the result for the originating call from TALER_withdraw. - * - * @param cls struct TALER_WithdrawHandle - * @param wbr The response - */ -static void -copy_results ( - void *cls, - const struct TALER_EXCHANGE_WithdrawBlindedResponse *wbr) -{ - /* The original handle from the top-level call to withdraw */ - struct TALER_EXCHANGE_WithdrawHandle *wh = cls; - struct TALER_EXCHANGE_WithdrawResponse resp = { - .hr = wbr->hr, - }; - - wh->withdraw_blinded_handle = NULL; - - /** - * The withdraw protocol has been performed with blinded data. - * Now the response can be copied as is, except for the MHD_HTTP_OK case, - * in which we now need to perform the unblinding. - */ - switch (wbr->hr.http_status) - { - case MHD_HTTP_OK: - { - struct TALER_EXCHANGE_WithdrawCoinPrivateDetails - details[GNUNET_NZL (wh->num_coins)]; - bool ok = true; - - GNUNET_assert (wh->num_coins == wbr->details.ok.num_sigs); - memset (details, - 0, - sizeof(details)); - resp.details.ok.num_sigs = wbr->details.ok.num_sigs; - resp.details.ok.coin_details = details; - resp.details.ok.planchets_h = wbr->details.ok.planchets_h; - for (size_t n = 0; n<wh->num_coins; n++) - { - const struct TALER_BlindedDenominationSignature *bsig = - &wbr->details.ok.blinded_denom_sigs[n]; - struct CoinData *cd = &wh->coin_data[n]; - struct TALER_EXCHANGE_WithdrawCoinPrivateDetails *coin = &details[n]; - struct TALER_FreshCoin fresh_coin; - - *coin = wh->coin_data[n].candidates[0].details; - coin->planchet = wh->coin_data[n].planchet_details[0]; - GNUNET_CRYPTO_eddsa_key_get_public ( - &coin->coin_priv.eddsa_priv, - &coin->coin_pub.eddsa_pub); - - if (GNUNET_OK != - TALER_planchet_to_coin (&cd->denom_pub.key, - bsig, - &coin->blinding_key, - &coin->coin_priv, - &coin->h_age_commitment, - &coin->h_coin_pub, - &coin->blinding_values, - &fresh_coin)) - { - resp.hr.http_status = 0; - resp.hr.ec = TALER_EC_EXCHANGE_WITHDRAW_UNBLIND_FAILURE; - GNUNET_break_op (0); - ok = false; - break; - } - coin->denom_sig = fresh_coin.sig; - } - if (ok) - { - wh->callback ( - wh->callback_cls, - &resp); - wh->callback = NULL; - } - for (size_t n = 0; n<wh->num_coins; n++) - { - struct TALER_EXCHANGE_WithdrawCoinPrivateDetails *coin = &details[n]; - - TALER_denom_sig_free (&coin->denom_sig); - } - break; - } - case MHD_HTTP_CREATED: - resp.details.created = wbr->details.created; - break; - - case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: - resp.details.unavailable_for_legal_reasons = - wbr->details.unavailable_for_legal_reasons; - break; - - default: - /* nothing to do here, .hr.ec and .hr.hint are all set already from previous response */ - break; - } - if (NULL != wh->callback) - { - wh->callback ( - wh->callback_cls, - &resp); - wh->callback = NULL; - } - TALER_EXCHANGE_withdraw_cancel (wh); -} - - -/** - * @brief Callback to copy the results from the call to TALER_withdraw_blinded - * in the age-restricted case to the result for the originating call from TALER_withdraw. - * - * @param cls struct TALER_WithdrawHandle - * @param wbr The response - */ -static void -copy_results_with_age_proof ( - void *cls, - const struct TALER_EXCHANGE_WithdrawBlindedResponse *wbr) -{ - /* The original handle from the top-level call to withdraw */ - struct TALER_EXCHANGE_WithdrawHandle *wh = cls; - uint8_t k = wbr->details.created.noreveal_index; - struct TALER_EXCHANGE_WithdrawCoinPrivateDetails details[wh->num_coins]; - struct TALER_EXCHANGE_WithdrawResponse resp = { - .hr = wbr->hr, - }; - - wh->withdraw_blinded_handle = NULL; - - switch (wbr->hr.http_status) - { - case MHD_HTTP_OK: - /* in the age-restricted case, this should not happen */ - GNUNET_break_op (0); - break; - - case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: - resp.details.unavailable_for_legal_reasons = - wbr->details.unavailable_for_legal_reasons; - break; - - case MHD_HTTP_CREATED: - { - GNUNET_assert (wh->num_coins == wbr->details.created.num_coins); - resp.details.created = wbr->details.created; - resp.details.created.coin_details = details; - resp.details.created.kappa_seed = wh->kappa_seed; - memset (details, - 0, - sizeof(details)); - for (size_t n = 0; n< wh->num_coins; n++) - { - details[n] = wh->coin_data[n].candidates[k].details; - details[n].planchet = wh->coin_data[n].planchet_details[k]; - } - break; - } - - default: - break; - } - - wh->callback ( - wh->callback_cls, - &resp); - wh->callback = NULL; - TALER_EXCHANGE_withdraw_cancel (wh); -} - - -/** - * @brief Prepares and executes TALER_EXCHANGE_withdraw_blinded. - * If there were CS-denominations involved, started once the all calls - * to /blinding-prepare are done. - */ -static void -call_withdraw_blinded ( - struct TALER_EXCHANGE_WithdrawHandle *wh) -{ - - GNUNET_assert (NULL == wh->blinding_prepare_handle); - - if (! wh->with_age_proof) - { - struct TALER_EXCHANGE_WithdrawBlindedCoinInput input[wh->num_coins]; - - memset (input, - 0, - sizeof(input)); - - /* Prepare the blinded planchets as input */ - for (size_t n = 0; n < wh->num_coins; n++) - { - input[n].denom_pub = - &wh->coin_data[n].denom_pub; - input[n].planchet_details = - *wh->coin_data[n].planchet_details; - } - - wh->withdraw_blinded_handle = - TALER_EXCHANGE_withdraw_blinded ( - wh->curl_ctx, - wh->keys, - wh->exchange_url, - wh->reserve_priv, - wh->has_blinding_seed ? &wh->blinding_seed : NULL, - wh->num_coins, - input, - &copy_results, - wh); - } - else - { /* age restricted case */ - struct TALER_EXCHANGE_WithdrawBlindedAgeRestrictedCoinInput - ari[wh->num_coins]; - - memset (ari, - 0, - sizeof(ari)); - - /* Prepare the blinded planchets as input */ - for (size_t n = 0; n < wh->num_coins; n++) - { - ari[n].denom_pub = &wh->coin_data[n].denom_pub; - for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++) - ari[n].planchet_details[k] = - wh->coin_data[n].planchet_details[k]; - } - - wh->withdraw_blinded_handle = - TALER_EXCHANGE_withdraw_blinded_with_age_proof ( - wh->curl_ctx, - wh->keys, - wh->exchange_url, - wh->reserve_priv, - wh->has_blinding_seed ? &wh->blinding_seed : NULL, - wh->max_age, - wh->num_coins, - ari, - &copy_results_with_age_proof, - wh); - } -} - - -/** - * @brief Function called when /blinding-prepare is finished - * - * @param cls the `struct BlindingPrepareClosure *` - * @param bpr replies from the /blinding-prepare request - */ -static void -blinding_prepare_done ( - void *cls, - const struct TALER_EXCHANGE_BlindingPrepareResponse *bpr) -{ - struct BlindingPrepareClosure *bpcls = cls; - struct TALER_EXCHANGE_WithdrawHandle *wh = bpcls->withdraw_handle; - - wh->blinding_prepare_handle = NULL; - switch (bpr->hr.http_status) - { - case MHD_HTTP_OK: - { - bool success = false; - size_t num = bpr->details.ok.num_blinding_values; - - GNUNET_assert (0 != num); - GNUNET_assert (num == bpcls->num_nonces); - for (size_t i = 0; i < bpcls->num_prepare_coins; i++) - { - struct TALER_PlanchetDetail *planchet = bpcls->coins[i].planchet; - struct CoinCandidate *can = bpcls->coins[i].candidate; - size_t cs_idx = bpcls->coins[i].cs_idx; - - GNUNET_assert (NULL != can); - GNUNET_assert (NULL != planchet); - success = false; - - /* Complete the initialization of the coin with CS denomination */ - TALER_denom_ewv_copy ( - &can->details.blinding_values, - &bpr->details.ok.blinding_values[cs_idx]); - - GNUNET_assert (GNUNET_CRYPTO_BSA_CS == - can->details.blinding_values.blinding_inputs->cipher); - - TALER_planchet_setup_coin_priv ( - &can->details.secret, - &can->details.blinding_values, - &can->details.coin_priv); - - TALER_planchet_blinding_secret_create ( - &can->details.secret, - &can->details.blinding_values, - &can->details.blinding_key); - - /* This initializes the 2nd half of the - can->planchet_detail.blinded_planchet */ - if (GNUNET_OK != - TALER_planchet_prepare ( - bpcls->coins[i].denom_pub, - &can->details.blinding_values, - &can->details.blinding_key, - &bpcls->nonces[cs_idx], - &can->details.coin_priv, - &can->details.h_age_commitment, - &can->details.h_coin_pub, - planchet)) - { - GNUNET_break (0); - break; - } - - TALER_coin_ev_hash (&planchet->blinded_planchet, - &planchet->denom_pub_hash, - &can->blinded_coin_h); - success = true; - } - - /* /blinding-prepare is done, we can now perform the - * actual withdraw operation */ - if (success) - call_withdraw_blinded (wh); - goto cleanup; - } - default: - { - /* We got an error condition during blinding prepare that we need to report */ - struct TALER_EXCHANGE_WithdrawResponse resp = { - .hr = bpr->hr - }; - - wh->callback ( - wh->callback_cls, - &resp); - - wh->callback = NULL; - break; - } - } - TALER_EXCHANGE_withdraw_cancel (wh); -cleanup: - GNUNET_free (bpcls->coins); - GNUNET_free (bpcls->nonces); - GNUNET_free (bpcls); -} - - -/** - * @brief Prepares non age-restricted coins for the call to withdraw and - * calculates the total amount with fees. - * For denomination with CS as cipher, initiates the preflight to retrieve the - * bpcls-parameter via /blinding-prepare. - * Note that only one of the three parameters seed, tuples or secrets must not be NULL - * - * @param wh The handler to the withdraw - * @param num_coins Number of coins to withdraw - * @param max_age The maximum age to commit to - * @param denoms_pub Array @e num_coins of denominations - * @param seed master seed from which to derive @e num_coins secrets and blinding, if @e blinding_seed is NULL - * @param blinding_seed master seed for the blinding. Might be NULL, in which case the blinding_seed is derived from @e seed - * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure - */ -static enum GNUNET_GenericReturnValue -prepare_coins ( - struct TALER_EXCHANGE_WithdrawHandle *wh, - size_t num_coins, - uint8_t max_age, - const struct TALER_EXCHANGE_DenomPublicKey *denoms_pub, - const struct TALER_WithdrawMasterSeedP *seed, - const struct TALER_BlindingMasterSeedP *blinding_seed) -{ - size_t cs_num = 0; - struct BlindingPrepareClosure *cs_closure; - uint8_t kappa; - -#define FAIL_IF(cond) \ - do \ - { \ - if ((cond)) \ - { \ - GNUNET_break (! (cond)); \ - goto ERROR; \ - } \ - } while (0) - - GNUNET_assert (0 < num_coins); - - wh->num_coins = num_coins; - wh->max_age = max_age; - wh->age_mask = denoms_pub[0].key.age_mask; - wh->coin_data = GNUNET_new_array ( - wh->num_coins, - struct CoinData); - - /* First, figure out how many Clause-Schnorr denominations we have */ - for (size_t i =0; i< wh->num_coins; i++) - { - if (GNUNET_CRYPTO_BSA_CS == - denoms_pub[i].key.bsign_pub_key->cipher) - cs_num++; - } - - if (wh->with_age_proof) - { - kappa = TALER_CNC_KAPPA; - } - else - { - kappa = 1; - } - - { - struct TALER_PlanchetMasterSecretP secrets[kappa][num_coins]; - struct TALER_EXCHANGE_NonceKey cs_nonce_keys[GNUNET_NZL (cs_num)]; - uint32_t cs_indices[GNUNET_NZL (cs_num)]; - - size_t cs_denom_idx = 0; - size_t cs_coin_idx = 0; - - if (wh->with_age_proof) - { - TALER_withdraw_expand_kappa_seed (seed, - &wh->kappa_seed); - for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++) - { - TALER_withdraw_expand_secrets ( - num_coins, - &wh->kappa_seed.tuple[k], - secrets[k]); - } - } - else - { - TALER_withdraw_expand_secrets ( - num_coins, - seed, - secrets[0]); - } - - if (0 < cs_num) - { - memset (cs_nonce_keys, - 0, - sizeof(cs_nonce_keys)); - cs_closure = GNUNET_new (struct BlindingPrepareClosure); - cs_closure->withdraw_handle = wh; - cs_closure->num_prepare_coins = cs_num * kappa; - GNUNET_assert ((1 == kappa) || (cs_num * kappa > cs_num)); - cs_closure->coins = - GNUNET_new_array (cs_closure->num_prepare_coins, - struct BlindingPrepareCoinData); - cs_closure->num_nonces = cs_num; - cs_closure->nonces = - GNUNET_new_array (cs_closure->num_nonces, - union GNUNET_CRYPTO_BlindSessionNonce); - } - else - { - cs_closure = NULL; - } - - for (uint32_t i = 0; i < wh->num_coins; i++) - { - struct CoinData *cd = &wh->coin_data[i]; - bool age_denom = (0 != denoms_pub[i].key.age_mask.bits); - - cd->denom_pub = denoms_pub[i]; - /* The age mask must be the same for all coins */ - FAIL_IF (wh->with_age_proof && - (0 == denoms_pub[i].key.age_mask.bits)); - FAIL_IF (wh->age_mask.bits != - denoms_pub[i].key.age_mask.bits); - TALER_denom_pub_copy (&cd->denom_pub.key, - &denoms_pub[i].key); - - /* Mark the indices of the coins which are of type Clause-Schnorr - * and add their denomination public key hash to the list. - */ - if (GNUNET_CRYPTO_BSA_CS == - cd->denom_pub.key.bsign_pub_key->cipher) - { - GNUNET_assert (cs_denom_idx<cs_num); - cs_indices[cs_denom_idx] = i; - cs_nonce_keys[cs_denom_idx].cnc_num = i; - cs_nonce_keys[cs_denom_idx].pk = &cd->denom_pub; - cs_denom_idx++; - } - - /* - * Note that we "loop" here either only once (if with_age_proof is false), - * or TALER_CNC_KAPPA times. - */ - for (uint8_t k = 0; k < kappa; k++) - { - struct CoinCandidate *can = &cd->candidates[k]; - struct TALER_PlanchetDetail *planchet = &cd->planchet_details[k]; - - can->details.secret = secrets[k][i]; - /* - * The age restriction needs to be set on a coin if the denomination - * support age restriction. Note that his is regardless of weither - * with_age_proof is set or not. - */ - if (age_denom) - { - /* Derive the age restriction from the given secret and - * the maximum age */ - TALER_age_restriction_from_secret ( - &can->details.secret, - &wh->age_mask, - wh->max_age, - &can->details.age_commitment_proof); - - TALER_age_commitment_hash ( - &can->details.age_commitment_proof.commitment, - &can->details.h_age_commitment); - } - - switch (cd->denom_pub.key.bsign_pub_key->cipher) - { - case GNUNET_CRYPTO_BSA_RSA: - TALER_denom_ewv_copy (&can->details.blinding_values, - TALER_denom_ewv_rsa_singleton ()); - TALER_planchet_setup_coin_priv (&can->details.secret, - &can->details.blinding_values, - &can->details.coin_priv); - TALER_planchet_blinding_secret_create (&can->details.secret, - &can->details.blinding_values, - &can->details.blinding_key); - FAIL_IF (GNUNET_OK != - TALER_planchet_prepare (&cd->denom_pub.key, - &can->details.blinding_values, - &can->details.blinding_key, - NULL, - &can->details.coin_priv, - (age_denom) - ? &can->details.h_age_commitment - : NULL, - &can->details.h_coin_pub, - planchet)); - TALER_coin_ev_hash (&planchet->blinded_planchet, - &planchet->denom_pub_hash, - &can->blinded_coin_h); - - break; - - case GNUNET_CRYPTO_BSA_CS: - { - /** - * Prepare the nonce and save the index and the denomination for the callback - * after the call to blinding-prepare - */ - cs_closure->coins[cs_coin_idx].candidate = can; - cs_closure->coins[cs_coin_idx].planchet = planchet; - cs_closure->coins[cs_coin_idx].denom_pub = &cd->denom_pub.key; - cs_closure->coins[cs_coin_idx].cs_idx = i; - cs_closure->coins[cs_coin_idx].age_denom = age_denom; - cs_coin_idx++; - break; - } - default: - FAIL_IF (1); - } - } - } - - if (0 < cs_num) - { - if (NULL != blinding_seed) - { - wh->blinding_seed = *blinding_seed; - } - else - { - TALER_cs_withdraw_seed_to_blinding_seed ( - seed, - &wh->blinding_seed); - } - wh->has_blinding_seed = true; - - TALER_cs_derive_only_cs_blind_nonces_from_seed ( - &wh->blinding_seed, - false, /* not for melt */ - cs_num, - cs_indices, - cs_closure->nonces); - - wh->blinding_prepare_handle = - TALER_EXCHANGE_blinding_prepare_for_withdraw ( - wh->curl_ctx, - wh->exchange_url, - &wh->blinding_seed, - cs_num, - cs_nonce_keys, - &blinding_prepare_done, - cs_closure); - FAIL_IF (NULL == wh->blinding_prepare_handle); - } - } - return GNUNET_OK; - -ERROR: - if (0<cs_num) - { - GNUNET_free (cs_closure->nonces); - GNUNET_free (cs_closure); - } - TALER_EXCHANGE_withdraw_cancel (wh); - return GNUNET_SYSERR; -#undef FAIL_IF - -} - - -/** - * Prepare a withdraw handle for both, the non-restricted - * and age-restricted case. - * - * @param curl_ctx The curl context to use - * @param keys The keys from the exchange - * @param exchange_url The base url to the exchange - * @param reserve_priv The private key of the exchange - * @param res_cb The callback to call on response - * @param res_cb_cls The closure to pass to the callback - */ -static struct TALER_EXCHANGE_WithdrawHandle * -setup_withdraw_handle ( - struct GNUNET_CURL_Context *curl_ctx, - struct TALER_EXCHANGE_Keys *keys, - const char *exchange_url, - const struct TALER_ReservePrivateKeyP *reserve_priv, - TALER_EXCHANGE_WithdrawCallback res_cb, - void *res_cb_cls) -{ - struct TALER_EXCHANGE_WithdrawHandle *wh; - - wh = GNUNET_new (struct TALER_EXCHANGE_WithdrawHandle); - wh->exchange_url = exchange_url; - wh->keys = TALER_EXCHANGE_keys_incref (keys); - wh->curl_ctx = curl_ctx; - wh->reserve_priv = reserve_priv; - wh->callback = res_cb; - wh->callback_cls = res_cb_cls; - - return wh; -} - - -struct TALER_EXCHANGE_WithdrawHandle * -TALER_EXCHANGE_withdraw_extra_blinding_seed ( - struct GNUNET_CURL_Context *curl_ctx, - struct TALER_EXCHANGE_Keys *keys, - const char *exchange_url, - const struct TALER_ReservePrivateKeyP *reserve_priv, - size_t num_coins, - const struct TALER_EXCHANGE_DenomPublicKey denoms_pub[static num_coins], - const struct TALER_WithdrawMasterSeedP *seed, - const struct TALER_BlindingMasterSeedP *blinding_seed, - uint8_t opaque_max_age, - TALER_EXCHANGE_WithdrawCallback res_cb, - void *res_cb_cls) -{ - struct TALER_EXCHANGE_WithdrawHandle *wh; - - wh = setup_withdraw_handle (curl_ctx, - keys, - exchange_url, - reserve_priv, - res_cb, - res_cb_cls); - GNUNET_assert (NULL != wh); - wh->with_age_proof = false; - - if (GNUNET_OK != - prepare_coins (wh, - num_coins, - opaque_max_age, - denoms_pub, - seed, - blinding_seed)) - { - GNUNET_free (wh); - return NULL; - } - - /* If there were no CS denominations, we can now perform the actual - * withdraw protocol. Otherwise, there are calls to /blinding-prepare - * in flight and once they finish, the withdraw-protocol will be - * called from within the blinding_prepare_done-function. - */ - if (NULL == wh->blinding_prepare_handle) - call_withdraw_blinded (wh); - - return wh; -} - - -struct TALER_EXCHANGE_WithdrawHandle * -TALER_EXCHANGE_withdraw ( - struct GNUNET_CURL_Context *curl_ctx, - struct TALER_EXCHANGE_Keys *keys, - const char *exchange_url, - const struct TALER_ReservePrivateKeyP *reserve_priv, - size_t num_coins, - const struct TALER_EXCHANGE_DenomPublicKey denoms_pub[static num_coins], - const struct TALER_WithdrawMasterSeedP *seed, - uint8_t opaque_max_age, - TALER_EXCHANGE_WithdrawCallback res_cb, - void *res_cb_cls) -{ - return TALER_EXCHANGE_withdraw_extra_blinding_seed ( - curl_ctx, - keys, - exchange_url, - reserve_priv, - num_coins, - denoms_pub, - seed, - NULL, - opaque_max_age, - res_cb, - res_cb_cls - ); -} - - -struct TALER_EXCHANGE_WithdrawHandle * -TALER_EXCHANGE_withdraw_with_age_proof_extra_blinding_seed ( - struct GNUNET_CURL_Context *curl_ctx, - struct TALER_EXCHANGE_Keys *keys, - const char *exchange_url, - const struct TALER_ReservePrivateKeyP *reserve_priv, - size_t num_coins, - const struct TALER_EXCHANGE_DenomPublicKey denoms_pub[static num_coins], - const struct TALER_WithdrawMasterSeedP *seed, - const struct TALER_BlindingMasterSeedP *blinding_seed, - uint8_t max_age, - TALER_EXCHANGE_WithdrawCallback res_cb, - void *res_cb_cls) -{ - struct TALER_EXCHANGE_WithdrawHandle *wh; - - wh = setup_withdraw_handle (curl_ctx, - keys, - exchange_url, - reserve_priv, - res_cb, - res_cb_cls); - GNUNET_assert (NULL != wh); - - wh->with_age_proof = true; - - if (GNUNET_OK != - prepare_coins (wh, - num_coins, - max_age, - denoms_pub, - seed, - blinding_seed)) - { - GNUNET_free (wh); - return NULL; - } - - /* If there were no CS denominations, we can now perform the actual - * withdraw protocol. Otherwise, there are calls to /blinding-prepare - * in flight and once they finish, the withdraw-protocol will be - * called from within the blinding_prepare_done-function. - */ - if (NULL == wh->blinding_prepare_handle) - call_withdraw_blinded (wh); - - return wh; -} - - -struct TALER_EXCHANGE_WithdrawHandle * -TALER_EXCHANGE_withdraw_with_age_proof ( - struct GNUNET_CURL_Context *curl_ctx, - struct TALER_EXCHANGE_Keys *keys, - const char *exchange_url, - const struct TALER_ReservePrivateKeyP *reserve_priv, - size_t num_coins, - const struct TALER_EXCHANGE_DenomPublicKey denoms_pub[static num_coins], - const struct TALER_WithdrawMasterSeedP *seed, - uint8_t max_age, - TALER_EXCHANGE_WithdrawCallback res_cb, - void *res_cb_cls) -{ - return TALER_EXCHANGE_withdraw_with_age_proof_extra_blinding_seed ( - curl_ctx, - keys, - exchange_url, - reserve_priv, - num_coins, - denoms_pub, - seed, - NULL, - max_age, - res_cb, - res_cb_cls); -} - - -void -TALER_EXCHANGE_withdraw_cancel ( - struct TALER_EXCHANGE_WithdrawHandle *wh) -{ - uint8_t kappa = wh->with_age_proof ? TALER_CNC_KAPPA : 1; - - /* Cleanup coin data */ - for (unsigned int i = 0; i<wh->num_coins; i++) - { - struct CoinData *cd = &wh->coin_data[i]; - - for (uint8_t k = 0; k < kappa; k++) - { - struct TALER_PlanchetDetail *planchet = &cd->planchet_details[k]; - struct CoinCandidate *can = &cd->candidates[k]; - - TALER_blinded_planchet_free (&planchet->blinded_planchet); - TALER_denom_ewv_free (&can->details.blinding_values); - TALER_age_commitment_proof_free (&can->details.age_commitment_proof); - } - TALER_denom_pub_free (&cd->denom_pub.key); - } - - TALER_EXCHANGE_blinding_prepare_cancel (wh->blinding_prepare_handle); - TALER_EXCHANGE_withdraw_blinded_cancel (wh->withdraw_blinded_handle); - wh->blinding_prepare_handle = NULL; - wh->withdraw_blinded_handle = NULL; - - GNUNET_free (wh->coin_data); - TALER_EXCHANGE_keys_decref (wh->keys); - GNUNET_free (wh); -} - - -/** - * @brief Prepare the handler for blinded withdraw - * - * Allocates the handler struct and prepares all fields of the handler - * except the blinded planchets, - * which depend on them being age-restricted or not. - * - * @param curl_ctx the context for curl - * @param keys the exchange keys - * @param exchange_url the url to the exchange - * @param reserve_priv the reserve's private key - * @param res_cb the callback on result - * @param res_cb_cls the closure to pass on to the callback - * @return the handler - */ -static struct TALER_EXCHANGE_WithdrawBlindedHandle * -setup_handler_common ( - struct GNUNET_CURL_Context *curl_ctx, - struct TALER_EXCHANGE_Keys *keys, - const char *exchange_url, - const struct TALER_ReservePrivateKeyP *reserve_priv, - TALER_EXCHANGE_WithdrawBlindedCallback res_cb, - void *res_cb_cls) -{ - - struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh = - GNUNET_new (struct TALER_EXCHANGE_WithdrawBlindedHandle); - - wbh->keys = TALER_EXCHANGE_keys_incref (keys); - wbh->curl_ctx = curl_ctx; - wbh->reserve_priv = reserve_priv; - wbh->callback = res_cb; - wbh->callback_cls = res_cb_cls; - wbh->request_url = TALER_url_join (exchange_url, - "withdraw", - NULL); - GNUNET_CRYPTO_eddsa_key_get_public ( - &wbh->reserve_priv->eddsa_priv, - &wbh->reserve_pub.eddsa_pub); - - return wbh; -} - - -struct TALER_EXCHANGE_WithdrawBlindedHandle * -TALER_EXCHANGE_withdraw_blinded ( - struct GNUNET_CURL_Context *curl_ctx, - struct TALER_EXCHANGE_Keys *keys, - const char *exchange_url, - const struct TALER_ReservePrivateKeyP *reserve_priv, - const struct TALER_BlindingMasterSeedP *blinding_seed, - size_t num_input, - const struct TALER_EXCHANGE_WithdrawBlindedCoinInput - blinded_input[static num_input], - TALER_EXCHANGE_WithdrawBlindedCallback res_cb, - void *res_cb_cls) -{ - struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh = - setup_handler_common (curl_ctx, - keys, - exchange_url, - reserve_priv, - res_cb, - res_cb_cls); - - wbh->with_age_proof = false; - wbh->num_input = num_input; - wbh->blinded.input = blinded_input; - wbh->blinding_seed = blinding_seed; - - perform_withdraw_protocol (wbh); - return wbh; -} - - -struct TALER_EXCHANGE_WithdrawBlindedHandle * -TALER_EXCHANGE_withdraw_blinded_with_age_proof ( - struct GNUNET_CURL_Context *curl_ctx, - struct TALER_EXCHANGE_Keys *keys, - const char *exchange_url, - const struct TALER_ReservePrivateKeyP *reserve_priv, - const struct TALER_BlindingMasterSeedP *blinding_seed, - uint8_t max_age, - unsigned int num_input, - const struct TALER_EXCHANGE_WithdrawBlindedAgeRestrictedCoinInput - blinded_input[static num_input], - TALER_EXCHANGE_WithdrawBlindedCallback res_cb, - void *res_cb_cls) -{ - struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh = - setup_handler_common (curl_ctx, - keys, - exchange_url, - reserve_priv, - res_cb, - res_cb_cls); - - wbh->with_age_proof = true; - wbh->max_age = max_age; - wbh->num_input = num_input; - wbh->blinded.with_age_proof_input = blinded_input; - wbh->blinding_seed = blinding_seed; - - perform_withdraw_protocol (wbh); - return wbh; -} - - -void -TALER_EXCHANGE_withdraw_blinded_cancel ( - struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh) -{ - if (NULL == wbh) - return; - if (NULL != wbh->job) - { - GNUNET_CURL_job_cancel (wbh->job); - wbh->job = NULL; - } - GNUNET_free (wbh->request_url); - TALER_EXCHANGE_keys_decref (wbh->keys); - TALER_curl_easy_post_finished (&wbh->post_ctx); - GNUNET_free (wbh); -} - - -/* exchange_api_withdraw.c */