diff options
author | Christian Grothoff <christian@grothoff.org> | 2023-10-24 09:22:43 +0200 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2023-10-24 09:22:43 +0200 |
commit | aee7774d2c99739fe4029e930ecfe9be368876f4 (patch) | |
tree | 32e85f40a3621835a604a4b5db41b9ce3aee5d29 | |
parent | deabb1e506ae678eeec608649388a082ada7c11a (diff) |
Revert "[lib] delete some files"last-gnunet-0.20-compatible
This reverts commit deabb1e506ae678eeec608649388a082ada7c11a.
36 files changed, 10992 insertions, 0 deletions
diff --git a/src/lib/auditor_api_curl_defaults.c b/src/lib/auditor_api_curl_defaults.c new file mode 100644 index 000000000..81fcd7bac --- /dev/null +++ b/src/lib/auditor_api_curl_defaults.c @@ -0,0 +1,61 @@ +/* + This file is part of TALER + Copyright (C) 2014-2018 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/auditor_api_curl_defaults.c + * @brief curl easy handle defaults + * @author Florian Dold + */ +#include "auditor_api_curl_defaults.h" + + +CURL * +TALER_AUDITOR_curl_easy_get_ (const char *url) +{ + CURL *eh; + struct GNUNET_AsyncScopeSave scope; + + GNUNET_async_scope_get (&scope); + + eh = curl_easy_init (); + if (NULL == eh) + return NULL; + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_URL, + url)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_FOLLOWLOCATION, + 1L)); + /* Enable compression (using whatever curl likes), see + https://curl.se/libcurl/c/CURLOPT_ACCEPT_ENCODING.html */ + GNUNET_break (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_ACCEPT_ENCODING, + "")); + /* limit MAXREDIRS to 5 as a simple security measure against + a potential infinite loop caused by a malicious target */ + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_MAXREDIRS, + 5L)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_TCP_FASTOPEN, + 1L)); + return eh; +} diff --git a/src/lib/auditor_api_curl_defaults.h b/src/lib/auditor_api_curl_defaults.h new file mode 100644 index 000000000..99e1e07e6 --- /dev/null +++ b/src/lib/auditor_api_curl_defaults.h @@ -0,0 +1,38 @@ +/* + This file is part of TALER + Copyright (C) 2014-2018 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/auditor_api_curl_defaults.h + * @brief curl easy handle defaults + * @author Florian Dold + */ +#ifndef _TALER_CURL_DEFAULTS_H +#define _TALER_CURL_DEFAULTS_H + +#include "platform.h" +#include <gnunet/gnunet_curl_lib.h> + + +/** + * Get a curl handle with the right defaults + * for the auditor lib. In the future, we might manage a pool of connections here. + * + * @param url URL to query + */ +CURL * +TALER_AUDITOR_curl_easy_get_ (const char *url); + +#endif /* _TALER_CURL_DEFAULTS_H */ diff --git a/src/lib/auditor_api_deposit_confirmation.c b/src/lib/auditor_api_deposit_confirmation.c new file mode 100644 index 000000000..1e2ecc6cc --- /dev/null +++ b/src/lib/auditor_api_deposit_confirmation.c @@ -0,0 +1,418 @@ +/* + 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/auditor_api_deposit_confirmation.c + * @brief Implementation of the /deposit request of the auditor's HTTP API + * @author Christian Grothoff + */ +#include "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_util.h" +#include "taler_curl_lib.h" +#include "taler_json_lib.h" +#include "taler_auditor_service.h" +#include "taler_signatures.h" +#include "auditor_api_curl_defaults.h" + + +/** + * @brief A DepositConfirmation Handle + */ +struct TALER_AUDITOR_DepositConfirmationHandle +{ + + /** + * 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_AUDITOR_DepositConfirmationResultCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + +}; + + +/** + * Function called when we're done processing the + * HTTP /deposit-confirmation request. + * + * @param cls the `struct TALER_AUDITOR_DepositConfirmationHandle` + * @param response_code HTTP response code, 0 on error + * @param djson parsed JSON result, NULL on error + */ +static void +handle_deposit_confirmation_finished (void *cls, + long response_code, + const void *djson) +{ + const json_t *json = djson; + struct TALER_AUDITOR_DepositConfirmationHandle *dh = cls; + struct TALER_AUDITOR_DepositConfirmationResponse dcr = { + .hr.reply = json, + .hr.http_status = (unsigned int) response_code + }; + + dh->job = NULL; + switch (response_code) + { + case 0: + dcr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + dcr.hr.ec = TALER_EC_NONE; + break; + case MHD_HTTP_BAD_REQUEST: + dcr.hr.ec = TALER_JSON_get_error_code (json); + dcr.hr.hint = TALER_JSON_get_error_hint (json); + /* This should never happen, either us or the auditor is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_FORBIDDEN: + dcr.hr.ec = TALER_JSON_get_error_code (json); + dcr.hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, auditor 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: + dcr.hr.ec = TALER_JSON_get_error_code (json); + dcr.hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + break; + case MHD_HTTP_GONE: + dcr.hr.ec = TALER_JSON_get_error_code (json); + dcr.hr.hint = TALER_JSON_get_error_hint (json); + /* Nothing really to verify, auditor 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_INTERNAL_SERVER_ERROR: + dcr.hr.ec = TALER_JSON_get_error_code (json); + dcr.hr.hint = TALER_JSON_get_error_hint (json); + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + dcr.hr.ec = TALER_JSON_get_error_code (json); + dcr.hr.hint = TALER_JSON_get_error_hint (json); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for auditor deposit confirmation\n", + (unsigned int) response_code, + dcr.hr.ec); + break; + } + dh->cb (dh->cb_cls, + &dcr); + TALER_AUDITOR_deposit_confirmation_cancel (dh); +} + + +/** + * Verify signature information about the deposit-confirmation. + * + * @param h_wire hash of merchant wire details + * @param h_policy hash over the policy extension, if any + * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the auditor) + * @param exchange_timestamp timestamp when the deposit was received by the wallet + * @param wire_deadline by what time must the amount be wired to the merchant + * @param refund_deadline date until which the merchant can issue a refund to the customer via the auditor (can be zero if refunds are not allowed); must not be after the @a wire_deadline + * @param amount_without_fee the amount confirmed to be wired by the exchange to the merchant + * @param coin_pub coin’s public key + * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests) + * @param exchange_sig the signature made with purpose #TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT + * @param exchange_pub the public key of the exchange that matches @a exchange_sig + * @param master_pub master public key of the exchange + * @param ep_start when does @a exchange_pub validity start + * @param ep_expire when does @a exchange_pub usage end + * @param ep_end when does @a exchange_pub legal validity end + * @param master_sig master signature affirming validity of @a exchange_pub + * @return #GNUNET_OK if signatures are OK, #GNUNET_SYSERR if not + */ +static enum GNUNET_GenericReturnValue +verify_signatures ( + const struct TALER_MerchantWireHashP *h_wire, + const struct TALER_ExtensionPolicyHashP *h_policy, + const struct TALER_PrivateContractHashP *h_contract_terms, + struct GNUNET_TIME_Timestamp exchange_timestamp, + struct GNUNET_TIME_Timestamp wire_deadline, + struct GNUNET_TIME_Timestamp refund_deadline, + const struct TALER_Amount *amount_without_fee, + unsigned int num_coins, + const struct TALER_CoinSpendSignatureP *coin_sigs[ + static num_coins], + const struct TALER_MerchantPublicKeyP *merchant_pub, + const struct TALER_ExchangePublicKeyP *exchange_pub, + const struct TALER_ExchangeSignatureP *exchange_sig, + const struct TALER_MasterPublicKeyP *master_pub, + struct GNUNET_TIME_Timestamp ep_start, + struct GNUNET_TIME_Timestamp ep_expire, + struct GNUNET_TIME_Timestamp ep_end, + const struct TALER_MasterSignatureP *master_sig) +{ + if (GNUNET_OK != + TALER_exchange_online_deposit_confirmation_verify ( + h_contract_terms, + h_wire, + h_policy, + exchange_timestamp, + wire_deadline, + refund_deadline, + amount_without_fee, + num_coins, + coin_sigs, + merchant_pub, + exchange_pub, + exchange_sig)) + { + GNUNET_break_op (0); + TALER_LOG_WARNING ( + "Invalid signature on /deposit-confirmation request!\n"); + { + TALER_LOG_DEBUG ("... amount_without_fee was %s\n", + TALER_amount2s (amount_without_fee)); + } + return GNUNET_SYSERR; + } + + if (GNUNET_OK != + TALER_exchange_offline_signkey_validity_verify ( + exchange_pub, + ep_start, + ep_expire, + ep_end, + master_pub, + master_sig)) + { + GNUNET_break (0); + TALER_LOG_WARNING ("Invalid signature on exchange signing key!\n"); + return GNUNET_SYSERR; + } + if (GNUNET_TIME_absolute_is_past (ep_end.abs_time)) + { + GNUNET_break (0); + TALER_LOG_WARNING ("Exchange signing key is no longer valid!\n"); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +struct TALER_AUDITOR_DepositConfirmationHandle * +TALER_AUDITOR_deposit_confirmation ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const struct TALER_MerchantWireHashP *h_wire, + const struct TALER_ExtensionPolicyHashP *h_policy, + const struct TALER_PrivateContractHashP *h_contract_terms, + struct GNUNET_TIME_Timestamp exchange_timestamp, + struct GNUNET_TIME_Timestamp wire_deadline, + struct GNUNET_TIME_Timestamp refund_deadline, + const struct TALER_Amount *total_without_fee, + unsigned int num_coins, + const struct TALER_CoinSpendPublicKeyP *coin_pubs[ + static num_coins], + const struct TALER_CoinSpendSignatureP *coin_sigs[ + static num_coins], + const struct TALER_MerchantPublicKeyP *merchant_pub, + const struct TALER_ExchangePublicKeyP *exchange_pub, + const struct TALER_ExchangeSignatureP *exchange_sig, + const struct TALER_MasterPublicKeyP *master_pub, + struct GNUNET_TIME_Timestamp ep_start, + struct GNUNET_TIME_Timestamp ep_expire, + struct GNUNET_TIME_Timestamp ep_end, + const struct TALER_MasterSignatureP *master_sig, + TALER_AUDITOR_DepositConfirmationResultCallback cb, + void *cb_cls) +{ + struct TALER_AUDITOR_DepositConfirmationHandle *dh; + json_t *deposit_confirmation_obj; + CURL *eh; + json_t *jcoin_sigs; + json_t *jcoin_pubs; + + if (0 == num_coins) + { + GNUNET_break (0); + return NULL; + } + if (GNUNET_OK != + verify_signatures (h_wire, + h_policy, + h_contract_terms, + exchange_timestamp, + wire_deadline, + refund_deadline, + total_without_fee, + num_coins, + coin_sigs, + merchant_pub, + exchange_pub, + exchange_sig, + master_pub, + ep_start, + ep_expire, + ep_end, + master_sig)) + { + GNUNET_break_op (0); + return NULL; + } + jcoin_sigs = json_array (); + GNUNET_assert (NULL != jcoin_sigs); + jcoin_pubs = json_array (); + GNUNET_assert (NULL != jcoin_pubs); + for (unsigned int i = 0; i<num_coins; i++) + { + GNUNET_assert (0 == + json_array_append_new (jcoin_sigs, + GNUNET_JSON_from_data_auto ( + coin_sigs[i]))); + GNUNET_assert (0 == + json_array_append_new (jcoin_pubs, + GNUNET_JSON_from_data_auto ( + coin_pubs[i]))); + } + deposit_confirmation_obj + = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("h_wire", + h_wire), + GNUNET_JSON_pack_data_auto ("h_policy", + h_policy), + GNUNET_JSON_pack_data_auto ("h_contract_terms", + h_contract_terms), + GNUNET_JSON_pack_timestamp ("exchange_timestamp", + exchange_timestamp), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_timestamp ("refund_deadline", + refund_deadline)), + GNUNET_JSON_pack_timestamp ("wire_deadline", + wire_deadline), + TALER_JSON_pack_amount ("total_without_fee", + total_without_fee), + GNUNET_JSON_pack_array_steal ("coin_pubs", + jcoin_pubs), + GNUNET_JSON_pack_array_steal ("coin_sigs", + jcoin_sigs), + GNUNET_JSON_pack_data_auto ("merchant_pub", + merchant_pub), + GNUNET_JSON_pack_data_auto ("exchange_sig", + exchange_sig), + GNUNET_JSON_pack_data_auto ("master_pub", + master_pub), + GNUNET_JSON_pack_timestamp ("ep_start", + ep_start), + GNUNET_JSON_pack_timestamp ("ep_expire", + ep_expire), + GNUNET_JSON_pack_timestamp ("ep_end", + ep_end), + GNUNET_JSON_pack_data_auto ("master_sig", + master_sig), + GNUNET_JSON_pack_data_auto ("exchange_pub", + exchange_pub)); + dh = GNUNET_new (struct TALER_AUDITOR_DepositConfirmationHandle); + dh->cb = cb; + dh->cb_cls = cb_cls; + dh->url = TALER_url_join (url, + "deposit-confirmation", + NULL); + if (NULL == dh->url) + { + GNUNET_free (dh); + return NULL; + } + eh = TALER_AUDITOR_curl_easy_get_ (dh->url); + if ( (NULL == eh) || + (CURLE_OK != + curl_easy_setopt (eh, + CURLOPT_CUSTOMREQUEST, + "PUT")) || + (GNUNET_OK != + TALER_curl_easy_post (&dh->ctx, + eh, + deposit_confirmation_obj)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (deposit_confirmation_obj); + GNUNET_free (dh->url); + GNUNET_free (dh); + return NULL; + } + json_decref (deposit_confirmation_obj); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "URL for deposit-confirmation: `%s'\n", + dh->url); + dh->job = GNUNET_CURL_job_add2 (ctx, + eh, + dh->ctx.headers, + &handle_deposit_confirmation_finished, + dh); + { + /* Disable 100 continue processing */ + struct curl_slist *x_headers; + + x_headers = curl_slist_append (NULL, + "Expect:"); + GNUNET_CURL_extend_headers (dh->job, + x_headers); + curl_slist_free_all (x_headers); + } + return dh; +} + + +void +TALER_AUDITOR_deposit_confirmation_cancel ( + struct TALER_AUDITOR_DepositConfirmationHandle *deposit_confirmation) +{ + if (NULL != deposit_confirmation->job) + { + GNUNET_CURL_job_cancel (deposit_confirmation->job); + deposit_confirmation->job = NULL; + } + GNUNET_free (deposit_confirmation->url); + TALER_curl_easy_post_finished (&deposit_confirmation->ctx); + GNUNET_free (deposit_confirmation); +} + + +/* end of auditor_api_deposit_confirmation.c */ diff --git a/src/lib/auditor_api_exchanges.c b/src/lib/auditor_api_exchanges.c new file mode 100644 index 000000000..897dfe60f --- /dev/null +++ b/src/lib/auditor_api_exchanges.c @@ -0,0 +1,244 @@ +/* + This file is part of TALER + Copyright (C) 2014-2018 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/auditor_api_exchanges.c + * @brief Implementation of the /exchanges request of the auditor's HTTP API + * @author Christian Grothoff + */ +#include "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_json_lib.h" +#include "taler_auditor_service.h" +#include "taler_util.h" +#include "taler_curl_lib.h" +#include "taler_signatures.h" +#include "auditor_api_curl_defaults.h" + +/** + * How many exchanges do we allow a single auditor to + * audit at most? + */ +#define MAX_EXCHANGES 1024 + + +/** + * @brief A ListExchanges Handle + */ +struct TALER_AUDITOR_ListExchangesHandle +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_AUDITOR_ListExchangesResultCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + +}; + + +/** + * Function called when we're done processing the + * HTTP /exchanges request. + * + * @param cls the `struct TALER_AUDITOR_ListExchangesHandle` + * @param response_code HTTP response code, 0 on error + * @param djson parsed JSON result, NULL on error + */ +static void +handle_exchanges_finished (void *cls, + long response_code, + const void *djson) +{ + const json_t *json = djson; + const json_t *ja; + unsigned int ja_len; + struct TALER_AUDITOR_ListExchangesHandle *leh = cls; + struct TALER_AUDITOR_ListExchangesResponse ler = { + .hr.reply = json, + .hr.http_status = (unsigned int) response_code + }; + + leh->job = NULL; + switch (response_code) + { + case 0: + ler.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + ja = json_object_get (json, + "exchanges"); + if ( (NULL == ja) || + (! json_is_array (ja)) ) + { + GNUNET_break (0); + ler.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + ler.hr.http_status = 0; + break; + } + + ja_len = json_array_size (ja); + if (ja_len > MAX_EXCHANGES) + { + GNUNET_break (0); + ler.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + ler.hr.http_status = 0; + break; + } + { + struct TALER_AUDITOR_ExchangeInfo ei[GNUNET_NZL (ja_len)]; + bool ok = true; + + for (unsigned int i = 0; i<ja_len; i++) + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("master_pub", + &ei[i].master_pub), + GNUNET_JSON_spec_string ("exchange_url", + &ei[i].exchange_url), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json_array_get (ja, + i), + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + ok = false; + ler.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + ler.hr.http_status = 0; + break; + } + } + if (! ok) + break; + ler.details.ok.ei = ei; + ler.details.ok.num_exchanges = ja_len; + leh->cb (leh->cb_cls, + &ler); + TALER_AUDITOR_list_exchanges_cancel (leh); + return; + } + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the auditor is buggy + (or API version conflict); just pass JSON reply to the application */ + ler.hr.ec = TALER_JSON_get_error_code (json); + ler.hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + ler.hr.ec = TALER_JSON_get_error_code (json); + ler.hr.hint = TALER_JSON_get_error_hint (json); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + ler.hr.ec = TALER_JSON_get_error_code (json); + ler.hr.hint = TALER_JSON_get_error_hint (json); + break; + default: + /* unexpected response code */ + ler.hr.ec = TALER_JSON_get_error_code (json); + ler.hr.hint = TALER_JSON_get_error_hint (json); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for auditor list-exchanges request\n", + (unsigned int) response_code, + (int) ler.hr.ec); + GNUNET_break_op (0); + break; + } + if (NULL != leh->cb) + leh->cb (leh->cb_cls, + &ler); + TALER_AUDITOR_list_exchanges_cancel (leh); +} + + +struct TALER_AUDITOR_ListExchangesHandle * +TALER_AUDITOR_list_exchanges (struct GNUNET_CURL_Context *ctx, + const char *url, + TALER_AUDITOR_ListExchangesResultCallback cb, + void *cb_cls) +{ + struct TALER_AUDITOR_ListExchangesHandle *leh; + CURL *eh; + + leh = GNUNET_new (struct TALER_AUDITOR_ListExchangesHandle); + leh->cb = cb; + leh->cb_cls = cb_cls; + leh->url = TALER_url_join (url, + "exchanges", + NULL); + if (NULL == leh->url) + { + GNUNET_free (leh); + return NULL; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "URL for list-exchanges: `%s'\n", + leh->url); + eh = TALER_AUDITOR_curl_easy_get_ (leh->url); + if (NULL == eh) + { + GNUNET_break (0); + GNUNET_free (leh->url); + GNUNET_free (leh); + return NULL; + } + leh->job = GNUNET_CURL_job_add (ctx, + eh, + &handle_exchanges_finished, + leh); + return leh; +} + + +void +TALER_AUDITOR_list_exchanges_cancel ( + struct TALER_AUDITOR_ListExchangesHandle *leh) +{ + if (NULL != leh->job) + { + GNUNET_CURL_job_cancel (leh->job); + leh->job = NULL; + } + GNUNET_free (leh->url); + GNUNET_free (leh); +} + + +/* end of auditor_api_exchanges.c */ diff --git a/src/lib/auditor_api_get_config.c b/src/lib/auditor_api_get_config.c new file mode 100644 index 000000000..c9f366568 --- /dev/null +++ b/src/lib/auditor_api_get_config.c @@ -0,0 +1,288 @@ +/* + 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/auditor_api_get_config.c + * @brief Implementation of /config for the auditor's HTTP API + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @author Christian Grothoff + */ +#include "platform.h" +#include <microhttpd.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_json_lib.h" +#include "taler_auditor_service.h" +#include "taler_signatures.h" +#include "auditor_api_curl_defaults.h" + + +/** + * Which revision of the Taler auditor protocol is implemented + * by this library? Used to determine compatibility. + */ +#define TALER_PROTOCOL_CURRENT 0 + +/** + * How many revisions back are we compatible to? + */ +#define TALER_PROTOCOL_AGE 0 + + +/** + * Log error related to CURL operations. + * + * @param type log level + * @param function which function failed to run + * @param code what was the curl error code + */ +#define CURL_STRERROR(type, function, code) \ + GNUNET_log (type, "Curl function `%s' has failed at `%s:%d' with error: %s", \ + function, __FILE__, __LINE__, curl_easy_strerror (code)); + + +/** + * Handle for the get config request. + */ +struct TALER_AUDITOR_GetConfigHandle +{ + /** + * The context of this handle + */ + struct GNUNET_CURL_Context *ctx; + + /** + * Function to call with the auditor's certification data, + * NULL if this has already been done. + */ + TALER_AUDITOR_ConfigCallback config_cb; + + /** + * Closure to pass to @e config_cb. + */ + void *config_cb_cls; + + /** + * Data for the request to get the /config of a auditor, + * NULL once we are past stage #MHS_INIT. + */ + struct GNUNET_CURL_Job *vr; + + /** + * The url for the @e vr job. + */ + char *vr_url; + +}; + + +/* ***************** Internal /config fetching ************* */ + +/** + * Decode the JSON in @a resp_obj from the /config response and store the data + * in the @a key_data. + * + * @param[in] resp_obj JSON object to parse + * @param[in,out] vi where to store the results we decoded + * @param[out] vc where to store config compatibility data + * @return #TALER_EC_NONE on success + */ +static enum TALER_ErrorCode +decode_config_json (const json_t *resp_obj, + struct TALER_AUDITOR_ConfigInformation *vi, + enum TALER_AUDITOR_VersionCompatibility *vc) +{ + unsigned int age; + unsigned int revision; + unsigned int current; + char dummy; + const char *ver; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("version", + &ver), + GNUNET_JSON_spec_fixed_auto ("auditor_public_key", + &vi->auditor_pub), + GNUNET_JSON_spec_end () + }; + + if (JSON_OBJECT != json_typeof (resp_obj)) + { + GNUNET_break_op (0); + return TALER_EC_GENERIC_JSON_INVALID; + } + /* check the config */ + if (GNUNET_OK != + GNUNET_JSON_parse (resp_obj, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return TALER_EC_GENERIC_JSON_INVALID; + } + if (3 != sscanf (ver, + "%u:%u:%u%c", + ¤t, + &revision, + &age, + &dummy)) + { + GNUNET_break_op (0); + return TALER_EC_GENERIC_VERSION_MALFORMED; + } + vi->version = ver; + *vc = TALER_AUDITOR_VC_MATCH; + if (TALER_PROTOCOL_CURRENT < current) + { + *vc |= TALER_AUDITOR_VC_NEWER; + if (TALER_PROTOCOL_CURRENT < current - age) + *vc |= TALER_AUDITOR_VC_INCOMPATIBLE; + } + if (TALER_PROTOCOL_CURRENT > current) + { + *vc |= TALER_AUDITOR_VC_OLDER; + if (TALER_PROTOCOL_CURRENT - TALER_PROTOCOL_AGE > current) + *vc |= TALER_AUDITOR_VC_INCOMPATIBLE; + } + return TALER_EC_NONE; +} + + +/** + * Callback used when downloading the reply to a /config request + * is complete. + * + * @param cls the `struct TALER_AUDITOR_GetConfigHandle` + * @param response_code HTTP response code, 0 on error + * @param gresp_obj parsed JSON result, NULL on error, must be a `const json_t *` + */ +static void +config_completed_cb (void *cls, + long response_code, + const void *gresp_obj) +{ + struct TALER_AUDITOR_GetConfigHandle *auditor = cls; + const json_t *resp_obj = gresp_obj; + struct TALER_AUDITOR_ConfigResponse vr = { + .hr.reply = resp_obj, + .hr.http_status = (unsigned int) response_code + }; + + auditor->vr = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received config from URL `%s' with status %ld.\n", + auditor->vr_url, + response_code); + switch (response_code) + { + case 0: + GNUNET_break_op (0); + vr.hr.ec = TALER_EC_INVALID; + break; + case MHD_HTTP_OK: + if (NULL == resp_obj) + { + GNUNET_break_op (0); + vr.hr.http_status = 0; + vr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + vr.hr.ec = decode_config_json (resp_obj, + &vr.details.ok.vi, + &vr.details.ok.compat); + if (TALER_EC_NONE != vr.hr.ec) + { + GNUNET_break_op (0); + vr.hr.http_status = 0; + break; + } + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + vr.hr.ec = TALER_JSON_get_error_code (resp_obj); + vr.hr.hint = TALER_JSON_get_error_hint (resp_obj); + break; + default: + vr.hr.ec = TALER_JSON_get_error_code (resp_obj); + vr.hr.hint = TALER_JSON_get_error_hint (resp_obj); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d\n", + (unsigned int) response_code, + (int) vr.hr.ec); + break; + } + auditor->config_cb (auditor->config_cb_cls, + &vr); + TALER_AUDITOR_get_config_cancel (auditor); +} + + +struct TALER_AUDITOR_GetConfigHandle * +TALER_AUDITOR_get_config (struct GNUNET_CURL_Context *ctx, + const char *url, + TALER_AUDITOR_ConfigCallback config_cb, + void *config_cb_cls) +{ + struct TALER_AUDITOR_GetConfigHandle *auditor; + CURL *eh; + + auditor = GNUNET_new (struct TALER_AUDITOR_GetConfigHandle); + auditor->config_cb = config_cb; + auditor->config_cb_cls = config_cb_cls; + auditor->ctx = ctx; + auditor->vr_url = TALER_url_join (url, + "config", + NULL); + if (NULL == auditor->vr_url) + { + GNUNET_break (0); + GNUNET_free (auditor->vr_url); + GNUNET_free (auditor); + return NULL; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Requesting auditor config with URL `%s'.\n", + auditor->vr_url); + eh = TALER_AUDITOR_curl_easy_get_ (auditor->vr_url); + if (NULL == eh) + { + GNUNET_break (0); + TALER_AUDITOR_get_config_cancel (auditor); + return NULL; + } + GNUNET_break (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_TIMEOUT, + (long) 300)); + auditor->vr = GNUNET_CURL_job_add (auditor->ctx, + eh, + &config_completed_cb, + auditor); + return auditor; +} + + +void +TALER_AUDITOR_get_config_cancel (struct TALER_AUDITOR_GetConfigHandle *auditor) +{ + if (NULL != auditor->vr) + { + GNUNET_CURL_job_cancel (auditor->vr); + auditor->vr = NULL; + } + GNUNET_free (auditor->vr_url); + GNUNET_free (auditor); +} + + +/* end of auditor_api_get_config.c */ diff --git a/src/lib/exchange_api_age_withdraw.c b/src/lib/exchange_api_age_withdraw.c new file mode 100644 index 000000000..ea9c0371e --- /dev/null +++ b/src/lib/exchange_api_age_withdraw.c @@ -0,0 +1,1117 @@ +/* + 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_age_withdraw.c + * @brief Implementation of /reserves/$RESERVE_PUB/age-withdraw requests + * @author Ă–zgĂĽr Kesim + */ + +#include "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_curl_lib.h" +#include "taler_error_codes.h" +#include "taler_json_lib.h" +#include "taler_exchange_service.h" +#include "exchange_api_common.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "exchange_api_curl_defaults.h" +#include "taler_util.h" + +/** + * A CoinCandidate is populated from a master secret + */ +struct CoinCandidate +{ + /** + * Master key material for the coin candidates. + */ + struct TALER_PlanchetMasterSecretP secret; + + /** + * The details derived form the master secrets + */ + struct TALER_EXCHANGE_AgeWithdrawCoinPrivateDetails details; + + /** + * Blinded hash of the coin + **/ + struct TALER_BlindedCoinHashP blinded_coin_h; + +}; + + +/** + * Closure for a call to /csr-withdraw, contains data that is needed to process + * the result. + */ +struct CSRClosure +{ + /* Points to the actual candidate in CoinData.coin_candidates, to continue + * to build its contents based on the results from /csr-withdraw */ + struct CoinCandidate *candidate; + + /* The planchet to finally generate. Points to the corresponding candidate + * in CoindData.planchet_details */ + struct TALER_PlanchetDetail *planchet; + + /* Handler to the originating call to /age-withdraw, needed to either + * cancel the running age-withdraw request (on failure of the current call + * to /csr-withdraw), or to eventually perform the protocol, once all + * csr-withdraw requests have successfully finished. */ + struct TALER_EXCHANGE_AgeWithdrawHandle *age_withdraw_handle; + + /* Denomination information, needed for CS coins for the + * step after /csr-withdraw */ + const struct TALER_EXCHANGE_DenomPublicKey *denom_pub; + + /* Handler for the CS R request */ + struct TALER_EXCHANGE_CsRWithdrawHandle *csr_withdraw_handle; +}; + +/** + * Data we keep per coin in the batch. + */ +struct CoinData +{ + /** + * The denomination of the coin. Must support age restriction, i.e + * its .keys.age_mask MUST not be 0 + */ + struct TALER_EXCHANGE_DenomPublicKey denom_pub; + + /** + * The Candidates for the coin + */ + struct CoinCandidate coin_candidates[TALER_CNC_KAPPA]; + + /** + * Details of the planchet(s). + */ + struct TALER_PlanchetDetail planchet_details[TALER_CNC_KAPPA]; + + /** + * Closure for each candidate of type CS for the preflight request to + * /csr-withdraw + */ + struct CSRClosure csr_cls[TALER_CNC_KAPPA]; +}; + +/** + * A /reserves/$RESERVE_PUB/age-withdraw request-handle for calls with + * pre-blinded planchets. Returned by TALER_EXCHANGE_age_withdraw_blinded. + */ +struct TALER_EXCHANGE_AgeWithdrawBlindedHandle +{ + + /** + * 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 age mask, extacted from the denominations. + * MUST be the same for all denominations + * + */ + struct TALER_AgeMask age_mask; + + /** + * Maximum age to commit to. + */ + uint8_t max_age; + + /** + * The commitment calculated as SHA512 hash over all blinded_coin_h + */ + struct TALER_AgeWithdrawCommitmentHashP h_commitment; + + /** + * Total amount requested (value plus withdraw fee). + */ + struct TALER_Amount amount_with_fee; + + /** + * Length of the @e blinded_input Array + */ + size_t num_input; + + /** + * The blinded planchet input for the call to /age-withdraw via + * TALER_EXCHANGE_age_withdraw_blinded + */ + const struct TALER_EXCHANGE_AgeWithdrawBlindedInput *blinded_input; + + /** + * 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 age-withdraw response results. + */ + TALER_EXCHANGE_AgeWithdrawBlindedCallback callback; + + /** + * Closure for @e blinded_callback + */ + void *callback_cls; +}; + +/** + * A /reserves/$RESERVE_PUB/age-withdraw request-handle for calls from + * a wallet, i. e. when blinding data is available. + */ +struct TALER_EXCHANGE_AgeWithdrawHandle +{ + + /** + * Length of the @e coin_data Array + */ + size_t num_coins; + + /** + * The base-URL of the exchange. + */ + const char *exchange_url; + + /** + * 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 age mask, extacted from the denominations. + * MUST be the same for all denominations + * + */ + struct TALER_AgeMask age_mask; + + /** + * Maximum age to commit to. + */ + uint8_t max_age; + + /** + * Array of per-coin data + */ + struct CoinData *coin_data; + + /** + * Context for curl. + */ + struct GNUNET_CURL_Context *curl_ctx; + + struct + { + /** + * Number of /csr-withdraw requests still pending. + */ + unsigned int pending; + + /** + * CURL handle for the request job. + */ + struct GNUNET_CURL_Job *job; + } csr; + + + /** + * Function to call with age-withdraw response results. + */ + TALER_EXCHANGE_AgeWithdrawCallback callback; + + /** + * Closure for @e age_withdraw_cb + */ + void *callback_cls; + + /* The Handler for the actual call to the exchange */ + struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *procotol_handle; +}; + +/** + * We got a 200 OK response for the /reserves/$RESERVE_PUB/age-withdraw operation. + * Extract the noreveal_index and return it to the caller. + * + * @param awbh operation handle + * @param j_response reply from the exchange + * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors + */ +static enum GNUNET_GenericReturnValue +reserve_age_withdraw_ok ( + struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh, + const json_t *j_response) +{ + struct TALER_EXCHANGE_AgeWithdrawBlindedResponse response = { + .hr.reply = j_response, + .hr.http_status = MHD_HTTP_OK, + .details.ok.h_commitment = awbh->h_commitment + }; + struct TALER_ExchangeSignatureP exchange_sig; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_uint8 ("noreveal_index", + &response.details.ok.noreveal_index), + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &response.details.ok.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_age_withdraw_confirmation_verify ( + &awbh->h_commitment, + response.details.ok.noreveal_index, + &response.details.ok.exchange_pub, + &exchange_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + + } + + awbh->callback (awbh->callback_cls, + &response); + /* make sure the callback isn't called again */ + awbh->callback = NULL; + + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /reserves/$RESERVE_PUB/age-withdraw request. + * + * @param cls the `struct TALER_EXCHANGE_AgeWithdrawHandle` + * @param response_code The HTTP response code + * @param response response data + */ +static void +handle_reserve_age_withdraw_blinded_finished ( + void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh = cls; + const json_t *j_response = response; + struct TALER_EXCHANGE_AgeWithdrawBlindedResponse awbr = { + .hr.reply = j_response, + .hr.http_status = (unsigned int) response_code + }; + + awbh->job = NULL; + switch (response_code) + { + case 0: + awbr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + reserve_age_withdraw_ok (awbh, + j_response)) + { + GNUNET_break_op (0); + awbr.hr.http_status = 0; + awbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + GNUNET_assert (NULL == awbh->callback); + TALER_EXCHANGE_age_withdraw_blinded_cancel (awbh); + 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 */ + awbr.hr.ec = TALER_JSON_get_error_code (j_response); + awbr.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 */ + awbr.hr.ec = TALER_JSON_get_error_code (j_response); + awbr.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. */ + awbr.hr.ec = TALER_JSON_get_error_code (j_response); + awbr.hr.hint = TALER_JSON_get_error_hint (j_response); + break; + case MHD_HTTP_CONFLICT: + /* The age requirements might not have been met */ + awbr.hr.ec = TALER_JSON_get_error_code (j_response); + awbr.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 */ + awbr.hr.ec = TALER_JSON_get_error_code (j_response); + awbr.hr.hint = TALER_JSON_get_error_hint (j_response); + break; + case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: + /* only validate reply is well-formed */ + { + uint64_t ptu; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_uint64 ("requirement_row", + &ptu), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j_response, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + awbr.hr.http_status = 0; + awbr.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 */ + awbr.hr.ec = TALER_JSON_get_error_code (j_response); + awbr.hr.hint = TALER_JSON_get_error_hint (j_response); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + awbr.hr.ec = TALER_JSON_get_error_code (j_response); + awbr.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) awbr.hr.ec); + break; + } + awbh->callback (awbh->callback_cls, + &awbr); + TALER_EXCHANGE_age_withdraw_blinded_cancel (awbh); +} + + +/** + * Runs the actual age-withdraw operation with the blinded planchets. + * + * @param[in,out] awbh age withdraw handler + */ +static void +perform_protocol ( + struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh) +{ +#define FAIL_IF(cond) \ + do { \ + if ((cond)) \ + { \ + GNUNET_break (! (cond)); \ + goto ERROR; \ + } \ + } while(0) + + struct GNUNET_HashContext *coins_hctx; + json_t *j_denoms = NULL; + json_t *j_array_candidates = NULL; + json_t *j_request_body = NULL; + CURL *curlh = NULL; + + GNUNET_assert (0 < awbh->num_input); + awbh->age_mask = awbh->blinded_input[0].denom_pub->key.age_mask; + + FAIL_IF (GNUNET_OK != + TALER_amount_set_zero (awbh->keys->currency, + &awbh->amount_with_fee)); + /* Accumulate total value with fees */ + for (size_t i = 0; i < awbh->num_input; i++) + { + struct TALER_Amount coin_total; + const struct TALER_EXCHANGE_DenomPublicKey *dpub = + awbh->blinded_input[i].denom_pub; + + FAIL_IF (0 > + TALER_amount_add (&coin_total, + &dpub->fees.withdraw, + &dpub->value)); + FAIL_IF (0 > + TALER_amount_add (&awbh->amount_with_fee, + &awbh->amount_with_fee, + &coin_total)); + } + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Attempting to age-withdraw from reserve %s with maximum age %d\n", + TALER_B2S (&awbh->reserve_pub), + awbh->max_age); + + coins_hctx = GNUNET_CRYPTO_hash_context_start (); + FAIL_IF (NULL == coins_hctx); + + + j_denoms = json_array (); + j_array_candidates = json_array (); + FAIL_IF ((NULL == j_denoms) || + (NULL == j_array_candidates)); + + for (size_t i = 0; i< awbh->num_input; i++) + { + /* Build the denomination array */ + { + const struct TALER_EXCHANGE_DenomPublicKey *denom_pub = + awbh->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 (awbh->age_mask.bits != denom_pub->key.age_mask.bits); + + jdenom = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto (NULL, + denom_h)); + FAIL_IF (NULL == jdenom); + FAIL_IF (0 < json_array_append_new (j_denoms, + jdenom)); + + /* Build the candidate array */ + { + json_t *j_can = json_array (); + FAIL_IF (NULL == j_can); + + for (size_t k = 0; k < TALER_CNC_KAPPA; k++) + { + struct TALER_BlindedCoinHashP bch; + const struct TALER_PlanchetDetail *planchet = + &awbh->blinded_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_can, + jc)); + + TALER_coin_ev_hash (&planchet->blinded_planchet, + &planchet->denom_pub_hash, + &bch); + + GNUNET_CRYPTO_hash_context_read (coins_hctx, + &bch, + sizeof(bch)); + } + + FAIL_IF (0 < json_array_append_new (j_array_candidates, + j_can)); + } + } + } + + /* Build the hash of the commitment */ + GNUNET_CRYPTO_hash_context_finish (coins_hctx, + &awbh->h_commitment.hash); + + /* Sign the request */ + TALER_wallet_age_withdraw_sign (&awbh->h_commitment, + &awbh->amount_with_fee, + &awbh->age_mask, + awbh->max_age, + awbh->reserve_priv, + &awbh->reserve_sig); + + /* Initiate the POST-request */ + j_request_body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_array_steal ("denom_hs", j_denoms), + GNUNET_JSON_pack_array_steal ("blinded_coin_evs", j_array_candidates), + GNUNET_JSON_pack_uint64 ("max_age", awbh->max_age), + GNUNET_JSON_pack_data_auto ("reserve_sig", &awbh->reserve_sig)); + FAIL_IF (NULL == j_request_body); + + curlh = TALER_EXCHANGE_curl_easy_get_ (awbh->request_url); + FAIL_IF (NULL == curlh); + FAIL_IF (GNUNET_OK != + TALER_curl_easy_post (&awbh->post_ctx, + curlh, + j_request_body)); + json_decref (j_request_body); + j_request_body = NULL; + + awbh->job = GNUNET_CURL_job_add2 ( + awbh->curl_ctx, + curlh, + awbh->post_ctx.headers, + &handle_reserve_age_withdraw_blinded_finished, + awbh); + FAIL_IF (NULL == awbh->job); + + /* No errors, return */ + return; + +ERROR: + if (NULL != j_denoms) + json_decref (j_denoms); + if (NULL != j_array_candidates) + json_decref (j_array_candidates); + if (NULL != j_request_body) + json_decref (j_request_body); + if (NULL != curlh) + curl_easy_cleanup (curlh); + TALER_EXCHANGE_age_withdraw_blinded_cancel (awbh); + return; +#undef FAIL_IF +} + + +/** + * @brief Callback to copy the results from the call to TALER_age_withdraw_blinded + * to the result for the originating call from TALER_age_withdraw. + * + * @param cls struct TALER_AgeWithdrawHandle + * @param awbr The response + */ +static void +copy_results ( + void *cls, + const struct TALER_EXCHANGE_AgeWithdrawBlindedResponse *awbr) +{ + struct TALER_EXCHANGE_AgeWithdrawHandle *awh = cls; + uint8_t k = awbr->details.ok.noreveal_index; + struct TALER_EXCHANGE_AgeWithdrawCoinPrivateDetails details[awh->num_coins]; + struct TALER_BlindedCoinHashP blinded_coin_hs[awh->num_coins]; + struct TALER_EXCHANGE_AgeWithdrawResponse resp = { + .hr = awbr->hr, + .details = { + .ok = { .noreveal_index = awbr->details.ok.noreveal_index, + .h_commitment = awbr->details.ok.h_commitment, + .exchange_pub = awbr->details.ok.exchange_pub, + .num_coins = awh->num_coins, + .coin_details = details, + .blinded_coin_hs = blinded_coin_hs}, + }, + }; + + for (size_t n = 0; n< awh->num_coins; n++) + { + details[n] = awh->coin_data[n].coin_candidates[k].details; + details[n].planchet = awh->coin_data[n].planchet_details[k]; + blinded_coin_hs[n] = awh->coin_data[n].coin_candidates[k].blinded_coin_h; + } + + awh->callback (awh->callback_cls, + &resp); + + awh->callback = NULL; +} + + +/** + * @brief Prepares and executes TALER_EXCHANGE_age_withdraw_blinded. + * If there were CS-denominations involved, started once the all calls + * to /csr-withdraw are done. + */ +static void +call_age_withdraw_blinded ( + struct TALER_EXCHANGE_AgeWithdrawHandle *awh) +{ + struct TALER_EXCHANGE_AgeWithdrawBlindedInput blinded_input[awh->num_coins]; + + /* Prepare the blinded planchets as input */ + for (size_t n = 0; n < awh->num_coins; n++) + { + blinded_input[n].denom_pub = &awh->coin_data[n].denom_pub; + for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++) + blinded_input[n].planchet_details[k] = + awh->coin_data[n].planchet_details[k]; + } + + awh->procotol_handle = + TALER_EXCHANGE_age_withdraw_blinded ( + awh->curl_ctx, + awh->keys, + awh->exchange_url, + awh->reserve_priv, + awh->max_age, + awh->num_coins, + blinded_input, + copy_results, + awh); +} + + +/** + * Prepares the request URL for the age-withdraw request + * + * @param awbh The handler + * @param exchange_url The base-URL to the exchange + */ +static +enum GNUNET_GenericReturnValue +prepare_url ( + struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh, + const char *exchange_url) +{ + 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 ( + &awbh->reserve_pub, + sizeof (awbh->reserve_pub), + pub_str, + sizeof (pub_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "reserves/%s/age-withdraw", + pub_str); + + awbh->request_url = TALER_url_join (exchange_url, + arg_str, + NULL); + if (NULL == awbh->request_url) + { + GNUNET_break (0); + TALER_EXCHANGE_age_withdraw_blinded_cancel (awbh); + return GNUNET_SYSERR; + } + + return GNUNET_OK; +} + + +/** + * @brief Function called when CSR withdraw retrieval is finished + * + * @param cls the `struct CSRClosure *` + * @param csrr replies from the /csr-withdraw request + */ +static void +csr_withdraw_done ( + void *cls, + const struct TALER_EXCHANGE_CsRWithdrawResponse *csrr) +{ + struct CSRClosure *csr = cls; + struct CoinCandidate *can; + struct TALER_PlanchetDetail *planchet; + struct TALER_EXCHANGE_AgeWithdrawHandle *awh; + + GNUNET_assert (NULL != csr); + awh = csr->age_withdraw_handle; + planchet = csr->planchet; + can = csr->candidate; + + GNUNET_assert (NULL != can); + GNUNET_assert (NULL != planchet); + GNUNET_assert (NULL != awh); + + csr->csr_withdraw_handle = NULL; + + switch (csrr->hr.http_status) + { + case MHD_HTTP_OK: + { + bool success = false; + /* Complete the initialization of the coin with CS denomination */ + can->details.alg_values = csrr->details.ok.alg_values; + GNUNET_assert (can->details.alg_values.cipher + == TALER_DENOMINATION_CS); + TALER_planchet_setup_coin_priv (&can->secret, + &can->details.alg_values, + &can->details.coin_priv); + TALER_planchet_blinding_secret_create (&can->secret, + &can->details.alg_values, + &can->details.blinding_key); + /* This initializes the 2nd half of the + can->planchet_detail.blinded_planchet! */ + do { + if (GNUNET_OK != + TALER_planchet_prepare (&csr->denom_pub->key, + &can->details.alg_values, + &can->details.blinding_key, + &can->details.coin_priv, + &can->details.h_age_commitment, + &can->details.h_coin_pub, + planchet)) + { + GNUNET_break (0); + TALER_EXCHANGE_age_withdraw_cancel (awh); + break; + } + + if (GNUNET_OK != + TALER_coin_ev_hash (&planchet->blinded_planchet, + &planchet->denom_pub_hash, + &can->blinded_coin_h)) + { + GNUNET_break (0); + TALER_EXCHANGE_age_withdraw_cancel (awh); + break; + } + success = true; + } while(0); + + awh->csr.pending--; + + /* No more pending requests to /csr-withdraw, we can now perform the + * actual age-withdraw operation */ + if (0 == awh->csr.pending && success) + call_age_withdraw_blinded (awh); + return; + } + default: + break; + } + + TALER_EXCHANGE_age_withdraw_cancel (awh); +} + + +/** + * @brief Prepare the coins for the call to age-withdraw and calculates + * the total amount with fees. + * + * For denomination with CS as cipher, initiates the preflight to retrieve the + * csr-parameter via /csr-withdraw. + * + * @param awh The handler to the age-withdraw + * @param num_coins The number of coins in @e coin_inputs + * @param coin_inputs The input for the individual coin(-candidates) + * @return GNUNET_OK on success, GNUNET_SYSERR on failure + */ +static +enum GNUNET_GenericReturnValue +prepare_coins ( + struct TALER_EXCHANGE_AgeWithdrawHandle *awh, + size_t num_coins, + const struct TALER_EXCHANGE_AgeWithdrawCoinInput coin_inputs[ + static num_coins]) +{ +#define FAIL_IF(cond) \ + do { \ + if ((cond)) \ + { \ + GNUNET_break (! (cond)); \ + goto ERROR; \ + } \ + } while(0) + + GNUNET_assert (0 < num_coins); + awh->age_mask = coin_inputs[0].denom_pub->key.age_mask; + + awh->coin_data = GNUNET_new_array (awh->num_coins, + struct CoinData); + + for (size_t i = 0; i < num_coins; i++) + { + struct CoinData *cd = &awh->coin_data[i]; + const struct TALER_EXCHANGE_AgeWithdrawCoinInput *input = &coin_inputs[i]; + cd->denom_pub = *input->denom_pub; + + /* The mask must be the same for all coins */ + FAIL_IF (awh->age_mask.bits != input->denom_pub->key.age_mask.bits); + + TALER_denom_pub_deep_copy (&cd->denom_pub.key, + &input->denom_pub->key); + + for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++) + { + struct CoinCandidate *can = &cd->coin_candidates[k]; + struct TALER_PlanchetDetail *planchet = &cd->planchet_details[k]; + + can->secret = input->secrets[k]; + /* Derive the age restriction from the given secret and + * the maximum age */ + TALER_age_restriction_from_secret ( + &can->secret, + &input->denom_pub->key.age_mask, + awh->max_age, + &can->details.age_commitment_proof); + + TALER_age_commitment_hash (&can->details.age_commitment_proof.commitment, + &can->details.h_age_commitment); + + switch (input->denom_pub->key.cipher) + { + case TALER_DENOMINATION_RSA: + { + can->details.alg_values.cipher = TALER_DENOMINATION_RSA; + TALER_planchet_setup_coin_priv (&can->secret, + &can->details.alg_values, + &can->details.coin_priv); + TALER_planchet_blinding_secret_create (&can->secret, + &can->details.alg_values, + &can->details.blinding_key); + FAIL_IF (GNUNET_OK != + TALER_planchet_prepare (&cd->denom_pub.key, + &can->details.alg_values, + &can->details.blinding_key, + &can->details.coin_priv, + &can->details.h_age_commitment, + &can->details.h_coin_pub, + planchet)); + FAIL_IF (GNUNET_OK != + TALER_coin_ev_hash (&planchet->blinded_planchet, + &planchet->denom_pub_hash, + &can->blinded_coin_h)); + break; + } + case TALER_DENOMINATION_CS: + { + can->details.alg_values.cipher = TALER_DENOMINATION_CS; + + struct CSRClosure *cls = &cd->csr_cls[k]; + /** + * Save the handler and the denomination for the callback + * after the call to csr-withdraw */ + cls->age_withdraw_handle = awh; + cls->candidate = can; + cls->planchet = planchet; + cls->denom_pub = &cd->denom_pub; + + TALER_cs_withdraw_nonce_derive ( + &can->secret, + &planchet->blinded_planchet.details.cs_blinded_planchet.nonce); + + /* Note that we only initialize the first half + of the blinded_planchet here; the other part + will be done after the /csr-withdraw request! */ + planchet->blinded_planchet.cipher = TALER_DENOMINATION_CS; + cls->csr_withdraw_handle = + TALER_EXCHANGE_csr_withdraw ( + awh->curl_ctx, + awh->exchange_url, + &cd->denom_pub, + &planchet->blinded_planchet.details.cs_blinded_planchet.nonce, + &csr_withdraw_done, + cls); + FAIL_IF (NULL == cls->csr_withdraw_handle); + + awh->csr.pending++; + break; + } + default: + FAIL_IF (1); + } + } + } + return GNUNET_OK; + +ERROR: + TALER_EXCHANGE_age_withdraw_cancel (awh); + return GNUNET_SYSERR; +#undef FAIL_IF +}; + +struct TALER_EXCHANGE_AgeWithdrawHandle * +TALER_EXCHANGE_age_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_AgeWithdrawCoinInput coin_inputs[const static + num_coins], + uint8_t max_age, + TALER_EXCHANGE_AgeWithdrawCallback res_cb, + void *res_cb_cls) +{ + struct TALER_EXCHANGE_AgeWithdrawHandle *awh; + + awh = GNUNET_new (struct TALER_EXCHANGE_AgeWithdrawHandle); + awh->exchange_url = exchange_url; + awh->keys = TALER_EXCHANGE_keys_incref (keys); + awh->curl_ctx = curl_ctx; + awh->reserve_priv = reserve_priv; + awh->callback = res_cb; + awh->callback_cls = res_cb_cls; + awh->num_coins = num_coins; + awh->max_age = max_age; + + + if (GNUNET_OK != prepare_coins (awh, + num_coins, + coin_inputs)) + return NULL; + + /* If there were no CS denominations, we can now perform the actual + * age-withdraw protocol. Otherwise, there are calls to /csr-withdraw + * in flight and once they finish, the age-withdraw-protocol will be + * called from within the csr_withdraw_done-function. + */ + if (0 == awh->csr.pending) + call_age_withdraw_blinded (awh); + + return awh; +} + + +void +TALER_EXCHANGE_age_withdraw_cancel ( + struct TALER_EXCHANGE_AgeWithdrawHandle *awh) +{ + /* Cleanup coin data */ + for (unsigned int i = 0; i<awh->num_coins; i++) + { + struct CoinData *cd = &awh->coin_data[i]; + + for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++) + { + struct TALER_PlanchetDetail *planchet = &cd->planchet_details[k]; + struct CSRClosure *cls = &cd->csr_cls[k]; + + if (NULL != cls->csr_withdraw_handle) + { + TALER_EXCHANGE_csr_withdraw_cancel (cls->csr_withdraw_handle); + cls->csr_withdraw_handle = NULL; + } + TALER_blinded_planchet_free (&planchet->blinded_planchet); + } + TALER_denom_pub_free (&cd->denom_pub.key); + } + GNUNET_free (awh->coin_data); + TALER_EXCHANGE_keys_decref (awh->keys); + TALER_EXCHANGE_age_withdraw_blinded_cancel (awh->procotol_handle); + awh->procotol_handle = NULL; + GNUNET_free (awh); +} + + +struct TALER_EXCHANGE_AgeWithdrawBlindedHandle * +TALER_EXCHANGE_age_withdraw_blinded ( + struct GNUNET_CURL_Context *curl_ctx, + struct TALER_EXCHANGE_Keys *keys, + const char *exchange_url, + const struct TALER_ReservePrivateKeyP *reserve_priv, + uint8_t max_age, + unsigned int num_input, + const struct TALER_EXCHANGE_AgeWithdrawBlindedInput blinded_input[static + num_input], + TALER_EXCHANGE_AgeWithdrawBlindedCallback res_cb, + void *res_cb_cls) +{ + struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh = + GNUNET_new (struct TALER_EXCHANGE_AgeWithdrawBlindedHandle); + + awbh->num_input = num_input; + awbh->blinded_input = blinded_input; + awbh->keys = TALER_EXCHANGE_keys_incref (keys); + awbh->curl_ctx = curl_ctx; + awbh->reserve_priv = reserve_priv; + awbh->callback = res_cb; + awbh->callback_cls = res_cb_cls; + awbh->max_age = max_age; + + GNUNET_CRYPTO_eddsa_key_get_public (&awbh->reserve_priv->eddsa_priv, + &awbh->reserve_pub.eddsa_pub); + + if (GNUNET_OK != prepare_url (awbh, + exchange_url)) + return NULL; + + perform_protocol (awbh); + return awbh; +} + + +void +TALER_EXCHANGE_age_withdraw_blinded_cancel ( + struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh) +{ + if (NULL == awbh) + return; + + if (NULL != awbh->job) + { + GNUNET_CURL_job_cancel (awbh->job); + awbh->job = NULL; + } + GNUNET_free (awbh->request_url); + TALER_EXCHANGE_keys_decref (awbh->keys); + TALER_curl_easy_post_finished (&awbh->post_ctx); + GNUNET_free (awbh); +} + + +/* exchange_api_age_withdraw.c */ diff --git a/src/lib/exchange_api_age_withdraw_reveal.c b/src/lib/exchange_api_age_withdraw_reveal.c new file mode 100644 index 000000000..a448d109d --- /dev/null +++ b/src/lib/exchange_api_age_withdraw_reveal.c @@ -0,0 +1,471 @@ +/* + 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_age_withdraw_reveal.c + * @brief Implementation of /age-withdraw/$ACH/reveal requests + * @author Ă–zgĂĽr Kesim + */ + +#include "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_curl_lib.h" +#include "taler_json_lib.h" +#include "taler_exchange_service.h" +#include "exchange_api_common.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "exchange_api_curl_defaults.h" + +/** + * Handler for a running age-withdraw-reveal request + */ +struct TALER_EXCHANGE_AgeWithdrawRevealHandle +{ + + /* The index not to be disclosed */ + uint8_t noreveal_index; + + /* The age-withdraw commitment */ + struct TALER_AgeWithdrawCommitmentHashP h_commitment; + + /* The reserve's public key */ + const struct TALER_ReservePublicKeyP *reserve_pub; + + /* Number of coins */ + size_t num_coins; + + /* The @e num_coins * kappa coin secrets from the age-withdraw commitment */ + const struct TALER_EXCHANGE_AgeWithdrawCoinInput *coins_input; + + /* The url for the reveal request */ + const 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_AgeWithdrawRevealCallback callback; + + /* Reveal */ + void *callback_cls; +}; + + +/** + * We got a 200 OK response for the /age-withdraw/$ACH/reveal operation. + * Extract the signed blindedcoins and return it to the caller. + * + * @param awrh operation handle + * @param j_response reply from the exchange + * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors + */ +static enum GNUNET_GenericReturnValue +age_withdraw_reveal_ok ( + struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh, + const json_t *j_response) +{ + struct TALER_EXCHANGE_AgeWithdrawRevealResponse 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 (awrh->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[awrh->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 spec[] = { + TALER_JSON_spec_blinded_denom_sig (NULL, + &denom_sigs[n]), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != GNUNET_JSON_parse (j_sig, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + } + + response.details.ok.num_sigs = awrh->num_coins; + response.details.ok.blinded_denom_sigs = denom_sigs; + awrh->callback (awrh->callback_cls, + &response); + /* Make sure the callback isn't called again */ + awrh->callback = NULL; + } + + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /age-withdraw/$ACH/reveal request. + * + * @param cls the `struct TALER_EXCHANGE_AgeWithdrawRevealHandle` + * @param response_code The HTTP response code + * @param response response data + */ +static void +handle_age_withdraw_reveal_finished ( + void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh = cls; + const json_t *j_response = response; + struct TALER_EXCHANGE_AgeWithdrawRevealResponse awr = { + .hr.reply = j_response, + .hr.http_status = (unsigned int) response_code + }; + + awrh->job = NULL; + /* FIXME[oec]: Only handle response-codes that are in the spec */ + switch (response_code) + { + case 0: + awr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + { + enum GNUNET_GenericReturnValue ret; + + ret = age_withdraw_reveal_ok (awrh, + 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 == awrh->callback); + TALER_EXCHANGE_age_withdraw_reveal_cancel (awrh); + return; + } + case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: + /* only validate reply is well-formed */ + { + uint64_t ptu; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_uint64 ("legitimization_uuid", + &ptu), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j_response, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + awr.hr.http_status = 0; + awr.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 */ + awr.hr.ec = TALER_JSON_get_error_code (j_response); + awr.hr.hint = TALER_JSON_get_error_hint (j_response); + break; + case MHD_HTTP_FORBIDDEN: + GNUNET_break_op (0); + /** + * This should never happen, as we don't sent any signatures + * to the exchange to verify. We should simply pass the 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. + */ + awr.hr.ec = TALER_JSON_get_error_code (j_response); + awr.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 */ + 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; + } + awrh->callback (awrh->callback_cls, + &awr); + TALER_EXCHANGE_age_withdraw_reveal_cancel (awrh); +} + + +/** + * Prepares the request URL for the age-withdraw-reveal request + * + * @param exchange_url The base-URL to the exchange + * @param[in,out] awrh The handler + * @return GNUNET_OK on success, GNUNET_SYSERR otherwise + */ +static +enum GNUNET_GenericReturnValue +prepare_url ( + const char *exchange_url, + struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh) +{ + char arg_str[sizeof (struct TALER_AgeWithdrawCommitmentHashP) * 2 + 32]; + char pub_str[sizeof (struct TALER_AgeWithdrawCommitmentHashP) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string (&awrh->h_commitment, + sizeof (awrh->h_commitment), + pub_str, + sizeof (pub_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "age-withdraw/%s/reveal", + pub_str); + + awrh->request_url = TALER_url_join (exchange_url, + arg_str, + NULL); + if (NULL == awrh->request_url) + { + GNUNET_break (0); + TALER_EXCHANGE_age_withdraw_reveal_cancel (awrh); + return GNUNET_SYSERR; + } + + return GNUNET_OK; +} + + +/** + * Call /age-withdraw/$ACH/reveal + * + * @param curl_ctx The context for CURL + * @param awrh The handler + */ +static +void +perform_protocol ( + struct GNUNET_CURL_Context *curl_ctx, + struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh) +{ + CURL *curlh = NULL; + json_t *j_request_body = NULL; + json_t *j_array_of_secrets = NULL; + json_t *j_secrets = NULL; + json_t *j_sec = NULL; + +#define FAIL_IF(cond) \ + do { \ + if ((cond)) \ + { \ + GNUNET_break (! (cond)); \ + goto ERROR; \ + } \ + } while(0) + + j_array_of_secrets = json_array (); + FAIL_IF (NULL == j_array_of_secrets); + + for (size_t n = 0; n < awrh->num_coins; n++) + { + const struct TALER_PlanchetMasterSecretP *secrets = + awrh->coins_input[n].secrets; + + j_secrets = json_array (); + FAIL_IF (NULL == j_secrets); + + for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++) + { + const struct TALER_PlanchetMasterSecretP *secret = &secrets[k]; + if (awrh->noreveal_index == k) + continue; + + j_sec = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto (NULL, secret)); + + FAIL_IF (NULL == j_sec); + FAIL_IF (0 < json_array_append_new (j_secrets, + j_sec)); + } + + FAIL_IF (0 < json_array_append_new (j_array_of_secrets, + j_secrets)); + } + j_request_body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("reserve_pub", + awrh->reserve_pub), + GNUNET_JSON_pack_array_steal ("disclosed_coin_secrets", + j_array_of_secrets)); + FAIL_IF (NULL == j_request_body); + + curlh = TALER_EXCHANGE_curl_easy_get_ (awrh->request_url); + FAIL_IF (NULL == curlh); + FAIL_IF (GNUNET_OK != + TALER_curl_easy_post (&awrh->post_ctx, + curlh, + j_request_body)); + json_decref (j_request_body); + j_request_body = NULL; + + awrh->job = GNUNET_CURL_job_add2 (curl_ctx, + curlh, + awrh->post_ctx.headers, + &handle_age_withdraw_reveal_finished, + awrh); + FAIL_IF (NULL == awrh->job); + + /* No error, return */ + return; + +ERROR: + if (NULL != j_sec) + json_decref (j_sec); + if (NULL != j_secrets) + json_decref (j_secrets); + if (NULL != j_array_of_secrets) + json_decref (j_array_of_secrets); + if (NULL != j_request_body) + json_decref (j_request_body); + if (NULL != curlh) + curl_easy_cleanup (curlh); + TALER_EXCHANGE_age_withdraw_reveal_cancel (awrh); + return; +#undef FAIL_IF +} + + +struct TALER_EXCHANGE_AgeWithdrawRevealHandle * +TALER_EXCHANGE_age_withdraw_reveal ( + struct GNUNET_CURL_Context *curl_ctx, + const char *exchange_url, + size_t num_coins, + const struct TALER_EXCHANGE_AgeWithdrawCoinInput coins_input[static + num_coins], + uint8_t noreveal_index, + const struct TALER_AgeWithdrawCommitmentHashP *h_commitment, + const struct TALER_ReservePublicKeyP *reserve_pub, + TALER_EXCHANGE_AgeWithdrawRevealCallback reveal_cb, + void *reveal_cb_cls) +{ + struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh = + GNUNET_new (struct TALER_EXCHANGE_AgeWithdrawRevealHandle); + awrh->noreveal_index = noreveal_index; + awrh->h_commitment = *h_commitment; + awrh->num_coins = num_coins; + awrh->coins_input = coins_input; + awrh->callback = reveal_cb; + awrh->callback_cls = reveal_cb_cls; + awrh->reserve_pub = reserve_pub; + + if (GNUNET_OK != + prepare_url (exchange_url, + awrh)) + return NULL; + + perform_protocol (curl_ctx, awrh); + + return awrh; +} + + +void +TALER_EXCHANGE_age_withdraw_reveal_cancel ( + struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh) +{ + if (NULL != awrh->job) + { + GNUNET_CURL_job_cancel (awrh->job); + awrh->job = NULL; + } + TALER_curl_easy_post_finished (&awrh->post_ctx); + /* FIXME[oec]: anything else left to cleanup!? */ + GNUNET_free (awrh); +} + + +/* exchange_api_age_withdraw_reveal.c */ diff --git a/src/lib/exchange_api_auditor_add_denomination.c b/src/lib/exchange_api_auditor_add_denomination.c new file mode 100644 index 000000000..89de0d7f1 --- /dev/null +++ b/src/lib/exchange_api_auditor_add_denomination.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_auditor_add_denomination.c + * @brief functions for the auditor to add its signature for denomination at the exchange + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_exchange_service.h" +#include "auditor_api_curl_defaults.h" +#include "taler_signatures.h" +#include "taler_curl_lib.h" +#include "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_csr_melt.c b/src/lib/exchange_api_csr_melt.c new file mode 100644 index 000000000..f59995af3 --- /dev/null +++ b/src/lib/exchange_api_csr_melt.c @@ -0,0 +1,317 @@ +/* + 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_csr_melt.c + * @brief Implementation of /csr-melt requests (get R in exchange used for Clause Schnorr refresh) + * @author Lucien Heuzeveldt + * @author Gian Demarmels + */ +#include "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_exchange_service.h" +#include "taler_json_lib.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A Clause Schnorr R Handle + */ +struct TALER_EXCHANGE_CsRMeltHandle +{ + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_CsRMeltCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * 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; +}; + + +/** + * We got a 200 OK response for the /reserves/$RESERVE_PUB/withdraw operation. + * Extract the coin's signature and return it to the caller. The signature we + * get from the exchange is for the blinded value. Thus, we first must + * unblind it and then should verify its validity against our coin's hash. + * + * If everything checks out, we return the unblinded signature + * to the application via the callback. + * + * @param csrh operation handle + * @param arr reply from the exchange + * @param hr http response details + * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors + */ +static enum GNUNET_GenericReturnValue +csr_ok (struct TALER_EXCHANGE_CsRMeltHandle *csrh, + const json_t *arr, + struct TALER_EXCHANGE_HttpResponse *hr) +{ + unsigned int alen = json_array_size (arr); + struct TALER_ExchangeWithdrawValues alg_values[GNUNET_NZL (alen)]; + struct TALER_EXCHANGE_CsRMeltResponse csrr = { + .hr = *hr, + .details.ok.alg_values_len = alen, + .details.ok.alg_values = alg_values + }; + + for (unsigned int i = 0; i<alen; i++) + { + json_t *av = json_array_get (arr, + i); + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_exchange_withdraw_values ( + "ewv", + &alg_values[i]), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (av, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + csrh->cb (csrh->cb_cls, + &csrr); + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the HTTP /csr request. + * + * @param cls the `struct TALER_EXCHANGE_CsRMeltHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_csr_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_CsRMeltHandle *csrh = cls; + const json_t *j = response; + struct TALER_EXCHANGE_HttpResponse hr = { + .reply = j, + .http_status = (unsigned int) response_code + }; + struct TALER_EXCHANGE_CsRMeltResponse csrr = { + .hr = hr + }; + + csrh->job = NULL; + switch (response_code) + { + case 0: + csrr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + { + json_t *arr; + + arr = json_object_get (j, + "ewvs"); + if ( (NULL == arr) || + (0 == json_array_size (arr)) || + (GNUNET_OK != + csr_ok (csrh, + arr, + &hr)) ) + { + GNUNET_break_op (0); + csrr.hr.http_status = 0; + csrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + } + TALER_EXCHANGE_csr_melt_cancel (csrh); + 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 */ + csrr.hr.ec = TALER_JSON_get_error_code (j); + csrr.hr.hint = TALER_JSON_get_error_hint (j); + 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. */ + csrr.hr.ec = TALER_JSON_get_error_code (j); + csrr.hr.hint = TALER_JSON_get_error_hint (j); + 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 */ + csrr.hr.ec = TALER_JSON_get_error_code (j); + csrr.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 */ + csrr.hr.ec = TALER_JSON_get_error_code (j); + csrr.hr.hint = TALER_JSON_get_error_hint (j); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + csrr.hr.ec = TALER_JSON_get_error_code (j); + csrr.hr.hint = TALER_JSON_get_error_hint (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for CS R request\n", + (unsigned int) response_code, + (int) hr.ec); + break; + } + csrh->cb (csrh->cb_cls, + &csrr); + csrh->cb = NULL; + TALER_EXCHANGE_csr_melt_cancel (csrh); +} + + +struct TALER_EXCHANGE_CsRMeltHandle * +TALER_EXCHANGE_csr_melt ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const struct TALER_RefreshMasterSecretP *rms, + unsigned int nks_len, + struct TALER_EXCHANGE_NonceKey nks[static nks_len], + TALER_EXCHANGE_CsRMeltCallback res_cb, + void *res_cb_cls) +{ + struct TALER_EXCHANGE_CsRMeltHandle *csrh; + json_t *csr_arr; + + if (0 == nks_len) + { + GNUNET_break (0); + return NULL; + } + for (unsigned int i = 0; i<nks_len; i++) + if (TALER_DENOMINATION_CS != nks[i].pk->key.cipher) + { + GNUNET_break (0); + return NULL; + } + csrh = GNUNET_new (struct TALER_EXCHANGE_CsRMeltHandle); + csrh->cb = res_cb; + csrh->cb_cls = res_cb_cls; + csr_arr = json_array (); + GNUNET_assert (NULL != csr_arr); + for (unsigned int i = 0; i<nks_len; i++) + { + const struct TALER_EXCHANGE_NonceKey *nk = &nks[i]; + json_t *csr_obj; + + csr_obj = 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 != csr_obj); + GNUNET_assert (0 == + json_array_append_new (csr_arr, + csr_obj)); + } + csrh->url = TALER_url_join (url, + "csr-melt", + NULL); + if (NULL == csrh->url) + { + json_decref (csr_arr); + GNUNET_free (csrh); + return NULL; + } + { + CURL *eh; + json_t *req; + + req = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("rms", + rms), + GNUNET_JSON_pack_array_steal ("nks", + csr_arr)); + eh = TALER_EXCHANGE_curl_easy_get_ (csrh->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&csrh->post_ctx, + eh, + req)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (req); + GNUNET_free (csrh->url); + GNUNET_free (csrh); + return NULL; + } + json_decref (req); + csrh->job = GNUNET_CURL_job_add2 (ctx, + eh, + csrh->post_ctx.headers, + &handle_csr_finished, + csrh); + } + return csrh; +} + + +void +TALER_EXCHANGE_csr_melt_cancel (struct TALER_EXCHANGE_CsRMeltHandle *csrh) +{ + if (NULL != csrh->job) + { + GNUNET_CURL_job_cancel (csrh->job); + csrh->job = NULL; + } + GNUNET_free (csrh->url); + TALER_curl_easy_post_finished (&csrh->post_ctx); + GNUNET_free (csrh); +} diff --git a/src/lib/exchange_api_kyc_check.c b/src/lib/exchange_api_kyc_check.c new file mode 100644 index 000000000..373dd89a7 --- /dev/null +++ b/src/lib/exchange_api_kyc_check.c @@ -0,0 +1,329 @@ +/* + This file is part of TALER + Copyright (C) 2021-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_check.c + * @brief Implementation of the /kyc-check request + * @author Christian Grothoff + */ +#include "platform.h" +#include <microhttpd.h> /* just for HTTP check codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_exchange_service.h" +#include "taler_json_lib.h" +#include "exchange_api_handle.h" +#include "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; + + /** + * Keys of the exchange. + */ + struct TALER_EXCHANGE_Keys *keys; + + /** + * 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; + + /** + * Hash of the payto:// URL that is being KYC'ed. + */ + struct TALER_PaytoHashP h_payto; + +}; + + +/** + * 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 = { + .http_status = (unsigned int) response_code + }; + + kch->job = NULL; + switch (response_code) + { + case 0: + ks.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + { + const json_t *kyc_details; + uint32_t status; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &ks.details.ok.exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &ks.details.ok.exchange_pub), + GNUNET_JSON_spec_timestamp ("now", + &ks.details.ok.timestamp), + GNUNET_JSON_spec_object_const ("kyc_details", + &kyc_details), + GNUNET_JSON_spec_uint32 ("aml_status", + &status), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + ks.http_status = 0; + ks.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + ks.details.ok.kyc_details = kyc_details; + ks.details.ok.aml_status + = (enum TALER_AmlDecisionState) status; + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (kch->keys, + &ks.details.ok.exchange_pub)) + { + GNUNET_break_op (0); + ks.http_status = 0; + ks.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + GNUNET_JSON_parse_free (spec); + break; + } + + if (GNUNET_OK != + TALER_exchange_online_account_setup_success_verify ( + &kch->h_payto, + ks.details.ok.kyc_details, + ks.details.ok.timestamp, + &ks.details.ok.exchange_pub, + &ks.details.ok.exchange_sig)) + { + GNUNET_break_op (0); + ks.http_status = 0; + ks.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + GNUNET_JSON_parse_free (spec); + break; + } + kch->cb (kch->cb_cls, + &ks); + GNUNET_JSON_parse_free (spec); + TALER_EXCHANGE_kyc_check_cancel (kch); + return; + } + case MHD_HTTP_ACCEPTED: + { + uint32_t status; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("kyc_url", + &ks.details.accepted.kyc_url), + GNUNET_JSON_spec_uint32 ("aml_status", + &status), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + ks.http_status = 0; + ks.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + ks.details.accepted.aml_status + = (enum TALER_AmlDecisionState) status; + kch->cb (kch->cb_cls, + &ks); + GNUNET_JSON_parse_free (spec); + TALER_EXCHANGE_kyc_check_cancel (kch); + return; + } + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_BAD_REQUEST: + ks.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.ec = TALER_JSON_get_error_code (j); + break; + case MHD_HTTP_NOT_FOUND: + ks.ec = TALER_JSON_get_error_code (j); + break; + case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: + { + uint32_t status; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_uint32 ("aml_status", + &status), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + ks.http_status = 0; + ks.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + ks.details.unavailable_for_legal_reasons.aml_status + = (enum TALER_AmlDecisionState) status; + kch->cb (kch->cb_cls, + &ks); + GNUNET_JSON_parse_free (spec); + TALER_EXCHANGE_kyc_check_cancel (kch); + return; + } + case MHD_HTTP_INTERNAL_SERVER_ERROR: + ks.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.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.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, + struct TALER_EXCHANGE_Keys *keys, + uint64_t requirement_row, + const struct TALER_PaytoHashP *h_payto, + enum TALER_KYCLOGIC_KycUserType ut, + struct GNUNET_TIME_Relative timeout, + TALER_EXCHANGE_KycStatusCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_KycCheckHandle *kch; + CURL *eh; + char *arg_str; + + { + char payto_str[sizeof (*h_payto) * 2]; + char *end; + unsigned long long timeout_ms; + + end = GNUNET_STRINGS_data_to_string ( + h_payto, + sizeof (*h_payto), + payto_str, + sizeof (payto_str) - 1); + *end = '\0'; + timeout_ms = timeout.rel_value_us + / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us; + GNUNET_asprintf (&arg_str, + "kyc-check/%llu/%s/%s?timeout_ms=%llu", + (unsigned long long) requirement_row, + payto_str, + TALER_KYCLOGIC_kyc_user_type2s (ut), + timeout_ms); + } + kch = GNUNET_new (struct TALER_EXCHANGE_KycCheckHandle); + kch->h_payto = *h_payto; + kch->cb = cb; + kch->cb_cls = cb_cls; + kch->url = TALER_url_join (url, + arg_str, + NULL); + GNUNET_free (arg_str); + 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; + } + kch->keys = TALER_EXCHANGE_keys_incref (keys); + kch->job = GNUNET_CURL_job_add_with_ct_json (ctx, + eh, + &handle_kyc_check_finished, + kch); + 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; + } + TALER_EXCHANGE_keys_decref (kch->keys); + GNUNET_free (kch->url); + GNUNET_free (kch); +} + + +/* end of exchange_api_kyc_check.c */ diff --git a/src/lib/exchange_api_kyc_proof.c b/src/lib/exchange_api_kyc_proof.c new file mode 100644 index 000000000..e7cc9c4cf --- /dev/null +++ b/src/lib/exchange_api_kyc_proof.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_kyc_proof.c + * @brief Implementation of the /kyc-proof request + * @author Christian Grothoff + */ +#include "platform.h" +#include <microhttpd.h> /* just for HTTP proof codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_exchange_service.h" +#include "taler_json_lib.h" +#include "exchange_api_handle.h" +#include "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 = { + .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_PaytoHashP *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 (struct TALER_PaytoHashP) * 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_wallet.c b/src/lib/exchange_api_kyc_wallet.c new file mode 100644 index 000000000..7197694ae --- /dev/null +++ b/src/lib/exchange_api_kyc_wallet.c @@ -0,0 +1,230 @@ +/* + 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 "platform.h" +#include <microhttpd.h> /* just for HTTP wallet codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_exchange_service.h" +#include "taler_json_lib.h" +#include "exchange_api_handle.h" +#include "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 = { + .http_status = (unsigned int) response_code + }; + + kwh->job = NULL; + switch (response_code) + { + case 0: + ks.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_BAD_REQUEST: + ks.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.ec = TALER_JSON_get_error_code (j); + break; + case MHD_HTTP_NOT_FOUND: + ks.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.http_status = 0; + ks.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + break; + } + case MHD_HTTP_INTERNAL_SERVER_ERROR: + ks.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.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.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_decision.c b/src/lib/exchange_api_lookup_aml_decision.c new file mode 100644 index 000000000..01e98213b --- /dev/null +++ b/src/lib/exchange_api_lookup_aml_decision.c @@ -0,0 +1,419 @@ +/* + 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_lookup_aml_decision.c + * @brief Implementation of the /aml/$OFFICER_PUB/decision request + * @author Christian Grothoff + */ +#include "platform.h" +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_exchange_service.h" +#include "taler_json_lib.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A /coins/$COIN_PUB/link Handle + */ +struct TALER_EXCHANGE_LookupAmlDecision +{ + + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_LookupAmlDecisionCallback decision_cb; + + /** + * Closure for @e cb. + */ + void *decision_cb_cls; + + /** + * HTTP headers for the job. + */ + struct curl_slist *job_headers; +}; + + +/** + * Parse AML decision history. + * + * @param aml_history JSON array with AML history + * @param[out] aml_history_ar where to write the result + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +parse_aml_history (const json_t *aml_history, + struct TALER_EXCHANGE_AmlDecisionDetail *aml_history_ar) +{ + json_t *obj; + size_t idx; + + json_array_foreach (aml_history, idx, obj) + { + struct TALER_EXCHANGE_AmlDecisionDetail *aml = &aml_history_ar[idx]; + uint32_t state32; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_timestamp ("decision_time", + &aml->decision_time), + GNUNET_JSON_spec_string ("justification", + &aml->justification), + TALER_JSON_spec_amount_any ("new_threshold", + &aml->new_threshold), + GNUNET_JSON_spec_uint32 ("new_state", + &state32), + GNUNET_JSON_spec_fixed_auto ("decider_pub", + &aml->decider_pub), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (obj, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + aml->new_state = (enum TALER_AmlDecisionState) state32; + } + return GNUNET_OK; +} + + +/** + * Parse KYC response array. + * + * @param kyc_attributes JSON array with KYC details + * @param[out] kyc_attributes_ar where to write the result + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +parse_kyc_attributes (const json_t *kyc_attributes, + struct TALER_EXCHANGE_KycHistoryDetail *kyc_attributes_ar) +{ + json_t *obj; + size_t idx; + + json_array_foreach (kyc_attributes, idx, obj) + { + struct TALER_EXCHANGE_KycHistoryDetail *kyc = &kyc_attributes_ar[idx]; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_timestamp ("collection_time", + &kyc->collection_time), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_object_const ("attributes", + &kyc->attributes), + NULL), + GNUNET_JSON_spec_string ("provider_section", + &kyc->provider_section), + 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_decision_ok (struct TALER_EXCHANGE_LookupAmlDecision *lh, + const json_t *json) +{ + struct TALER_EXCHANGE_AmlDecisionResponse lr = { + .hr.reply = json, + .hr.http_status = MHD_HTTP_OK + }; + const json_t *aml_history; + const json_t *kyc_attributes; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_array_const ("aml_history", + &aml_history), + GNUNET_JSON_spec_array_const ("kyc_attributes", + &kyc_attributes), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + lr.details.ok.aml_history_length = json_array_size (aml_history); + lr.details.ok.kyc_attributes_length = json_array_size (kyc_attributes); + { + struct TALER_EXCHANGE_AmlDecisionDetail aml_history_ar[ + GNUNET_NZL (lr.details.ok.aml_history_length)]; + struct TALER_EXCHANGE_KycHistoryDetail kyc_attributes_ar[ + GNUNET_NZL (lr.details.ok.kyc_attributes_length)]; + enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR; + + memset (aml_history_ar, + 0, + sizeof (aml_history_ar)); + memset (kyc_attributes_ar, + 0, + sizeof (kyc_attributes_ar)); + lr.details.ok.aml_history = aml_history_ar; + lr.details.ok.kyc_attributes = kyc_attributes_ar; + ret = parse_aml_history (aml_history, + aml_history_ar); + if (GNUNET_OK == ret) + ret = parse_kyc_attributes (kyc_attributes, + kyc_attributes_ar); + if (GNUNET_OK == ret) + { + lh->decision_cb (lh->decision_cb_cls, + &lr); + lh->decision_cb = NULL; + } + return ret; + } +} + + +/** + * Function called when we're done processing the + * HTTP /aml/$OFFICER_PUB/decision request. + * + * @param cls the `struct TALER_EXCHANGE_LookupAmlDecision` + * @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_LookupAmlDecision *lh = cls; + const json_t *j = response; + struct TALER_EXCHANGE_AmlDecisionResponse 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_decision_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->decision_cb); + TALER_EXCHANGE_lookup_aml_decision_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 exchange lookup AML decision\n", + (unsigned int) response_code, + (int) lr.hr.ec); + break; + } + if (NULL != lh->decision_cb) + lh->decision_cb (lh->decision_cb_cls, + &lr); + TALER_EXCHANGE_lookup_aml_decision_cancel (lh); +} + + +struct TALER_EXCHANGE_LookupAmlDecision * +TALER_EXCHANGE_lookup_aml_decision ( + struct GNUNET_CURL_Context *ctx, + const char *exchange_url, + const struct TALER_PaytoHashP *h_payto, + const struct TALER_AmlOfficerPrivateKeyP *officer_priv, + bool history, + TALER_EXCHANGE_LookupAmlDecisionCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_LookupAmlDecision *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 pub_str[sizeof (officer_pub) * 2]; + char pt_str[sizeof (*h_payto) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string ( + &officer_pub, + sizeof (officer_pub), + pub_str, + sizeof (pub_str)); + *end = '\0'; + end = GNUNET_STRINGS_data_to_string ( + h_payto, + sizeof (*h_payto), + pt_str, + sizeof (pt_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "aml/%s/decision/%s", + pub_str, + pt_str); + } + lh = GNUNET_new (struct TALER_EXCHANGE_LookupAmlDecision); + lh->decision_cb = cb; + lh->decision_cb_cls = cb_cls; + lh->url = TALER_url_join (exchange_url, + arg_str, + "history", + history + ? "true" + : 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_decision_cancel ( + struct TALER_EXCHANGE_LookupAmlDecision *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_decision.c */ diff --git a/src/lib/exchange_api_lookup_aml_decisions.c b/src/lib/exchange_api_lookup_aml_decisions.c new file mode 100644 index 000000000..22222b1e4 --- /dev/null +++ b/src/lib/exchange_api_lookup_aml_decisions.c @@ -0,0 +1,378 @@ +/* + 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_lookup_aml_decisions.c + * @brief Implementation of the /aml/$OFFICER_PUB/decisions request + * @author Christian Grothoff + */ +#include "platform.h" +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_exchange_service.h" +#include "taler_json_lib.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "exchange_api_curl_defaults.h" + + +/** + * @brief A /coins/$COIN_PUB/link 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; +}; + + +/** + * Parse AML decision summary array. + * + * @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 (const json_t *decisions, + struct TALER_EXCHANGE_AmlDecisionSummary *decision_ar) +{ + json_t *obj; + size_t idx; + + json_array_foreach (decisions, idx, obj) + { + struct TALER_EXCHANGE_AmlDecisionSummary *decision = &decision_ar[idx]; + uint32_t state32; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("h_payto", + &decision->h_payto), + GNUNET_JSON_spec_uint32 ("current_state", + &state32), + TALER_JSON_spec_amount_any ("threshold", + &decision->threshold), + GNUNET_JSON_spec_uint64 ("rowid", + &decision->rowid), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (obj, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + decision->current_state = (enum TALER_AmlDecisionState) state32; + } + 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_AmlDecisionSummary decisions[ + GNUNET_NZL (lr.details.ok.decisions_length)]; + enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR; + + lr.details.ok.decisions = decisions; + ret = parse_aml_decisions (records, + decisions); + if (GNUNET_OK == ret) + { + lh->decisions_cb (lh->decisions_cb_cls, + &lr); + lh->decisions_cb = NULL; + } + 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: + 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, + uint64_t start, + int delta, + enum TALER_AmlDecisionState state, + 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]; + const char *state_str = NULL; + + switch (state) + { + case TALER_AML_NORMAL: + state_str = "normal"; + break; + case TALER_AML_PENDING: + state_str = "pending"; + break; + case TALER_AML_FROZEN: + state_str = "frozen"; + break; + } + GNUNET_assert (NULL != state_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/decisions/%s", + pub_str, + state_str); + } + lh = GNUNET_new (struct TALER_EXCHANGE_LookupAmlDecisions); + lh->decisions_cb = cb; + lh->decisions_cb_cls = cb_cls; + { + char delta_s[24]; + char start_s[24]; + + GNUNET_snprintf (delta_s, + sizeof (delta_s), + "%d", + delta); + GNUNET_snprintf (start_s, + sizeof (start_s), + "%llu", + (unsigned long long) start); + lh->url = TALER_url_join (exchange_url, + arg_str, + "delta", + delta_s, + "start", + start_s, + 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_management_add_partner.c b/src/lib/exchange_api_management_add_partner.c new file mode 100644 index 000000000..fec66c567 --- /dev/null +++ b/src/lib/exchange_api_management_add_partner.c @@ -0,0 +1,218 @@ +/* + 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 "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_exchange_service.h" +#include "exchange_api_curl_defaults.h" +#include "taler_signatures.h" +#include "taler_curl_lib.h" +#include "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 new file mode 100644 index 000000000..8bce7f74f --- /dev/null +++ b/src/lib/exchange_api_management_auditor_disable.c @@ -0,0 +1,219 @@ +/* + 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 "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_exchange_service.h" +#include "exchange_api_curl_defaults.h" +#include "taler_signatures.h" +#include "taler_curl_lib.h" +#include "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 new file mode 100644 index 000000000..41c5049c2 --- /dev/null +++ b/src/lib/exchange_api_management_auditor_enable.c @@ -0,0 +1,224 @@ +/* + 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 "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_exchange_service.h" +#include "exchange_api_curl_defaults.h" +#include "taler_signatures.h" +#include "taler_curl_lib.h" +#include "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 aer = { + .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: + aer.hr.ec = TALER_JSON_get_error_code (json); + aer.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) + { + aer.hr.ec = TALER_JSON_get_error_code (json); + aer.hr.hint = TALER_JSON_get_error_hint (json); + } + else + { + aer.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + aer.hr.hint = TALER_ErrorCode_get_hint (aer.hr.ec); + } + break; + case MHD_HTTP_CONFLICT: + aer.hr.ec = TALER_JSON_get_error_code (json); + aer.hr.hint = TALER_JSON_get_error_hint (json); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + aer.hr.ec = TALER_JSON_get_error_code (json); + aer.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) aer.hr.ec); + break; + } + if (NULL != ah->cb) + { + ah->cb (ah->cb_cls, + &aer); + 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 new file mode 100644 index 000000000..bc7232b87 --- /dev/null +++ b/src/lib/exchange_api_management_drain_profits.c @@ -0,0 +1,213 @@ +/* + 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 "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_curl_defaults.h" +#include "taler_exchange_service.h" +#include "taler_signatures.h" +#include "taler_curl_lib.h" +#include "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 char *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), + GNUNET_JSON_pack_string ("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_post_extensions.c b/src/lib/exchange_api_management_post_extensions.c new file mode 100644 index 000000000..00d1c5e3f --- /dev/null +++ b/src/lib/exchange_api_management_post_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_management_post_extensions.c + * @brief functions to handle the settings for extensions (p2p and age restriction) + * @author Ă–zgĂĽr Kesim + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_extensions.h" +#include "exchange_api_curl_defaults.h" +#include "taler_exchange_service.h" +#include "taler_signatures.h" +#include "taler_curl_lib.h" +#include "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 new file mode 100644 index 000000000..a46124d90 --- /dev/null +++ b/src/lib/exchange_api_management_post_keys.c @@ -0,0 +1,237 @@ +/* + 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 "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_exchange_service.h" +#include "exchange_api_curl_defaults.h" +#include "taler_signatures.h" +#include "taler_curl_lib.h" +#include "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 new file mode 100644 index 000000000..aa4d527a7 --- /dev/null +++ b/src/lib/exchange_api_management_revoke_denomination_key.c @@ -0,0 +1,221 @@ +/* + This file is part of TALER + Copyright (C) 2015-2020 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 "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_exchange_service.h" +#include "exchange_api_curl_defaults.h" +#include "taler_signatures.h" +#include "taler_curl_lib.h" +#include "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); + 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 new file mode 100644 index 000000000..c4d634248 --- /dev/null +++ b/src/lib/exchange_api_management_revoke_signing_key.c @@ -0,0 +1,211 @@ +/* + 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 "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_exchange_service.h" +#include "exchange_api_curl_defaults.h" +#include "taler_signatures.h" +#include "taler_curl_lib.h" +#include "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); + 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 new file mode 100644 index 000000000..54c37fd64 --- /dev/null +++ b/src/lib/exchange_api_management_set_global_fee.c @@ -0,0 +1,235 @@ +/* + 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 "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_curl_defaults.h" +#include "taler_exchange_service.h" +#include "taler_signatures.h" +#include "taler_curl_lib.h" +#include "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); + 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 new file mode 100644 index 000000000..03cab8c99 --- /dev/null +++ b/src/lib/exchange_api_management_set_wire_fee.c @@ -0,0 +1,227 @@ +/* + 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 "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_curl_defaults.h" +#include "taler_exchange_service.h" +#include "taler_signatures.h" +#include "taler_curl_lib.h" +#include "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); + 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_update_aml_officer.c b/src/lib/exchange_api_management_update_aml_officer.c new file mode 100644 index 000000000..76cbc7e8d --- /dev/null +++ b/src/lib/exchange_api_management_update_aml_officer.c @@ -0,0 +1,229 @@ +/* + 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_update_aml_officer.c + * @brief functions to update AML officer status + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_exchange_service.h" +#include "exchange_api_curl_defaults.h" +#include "taler_signatures.h" +#include "taler_curl_lib.h" +#include "taler_json_lib.h" + + +struct TALER_EXCHANGE_ManagementUpdateAmlOfficer +{ + + /** + * 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_ManagementUpdateAmlOfficerCallback 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_update_aml_officer_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_ManagementUpdateAmlOfficer *wh = cls; + const json_t *json = response; + struct TALER_EXCHANGE_ManagementUpdateAmlOfficerResponse uar = { + .hr.http_status = (unsigned int) response_code, + .hr.reply = json + }; + + wh->job = NULL; + switch (response_code) + { + case 0: + /* no reply */ + uar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + uar.hr.hint = "server offline?"; + break; + case MHD_HTTP_NO_CONTENT: + break; + case MHD_HTTP_FORBIDDEN: + uar.hr.ec = TALER_JSON_get_error_code (json); + uar.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) + { + uar.hr.ec = TALER_JSON_get_error_code (json); + uar.hr.hint = TALER_JSON_get_error_hint (json); + } + else + { + uar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + uar.hr.hint = TALER_ErrorCode_get_hint (uar.hr.ec); + } + break; + case MHD_HTTP_CONFLICT: + uar.hr.ec = TALER_JSON_get_error_code (json); + uar.hr.hint = TALER_JSON_get_error_hint (json); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + uar.hr.ec = TALER_JSON_get_error_code (json); + uar.hr.hint = TALER_JSON_get_error_hint (json); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d for exchange management update AML officer\n", + (unsigned int) response_code, + (int) uar.hr.ec); + break; + } + if (NULL != wh->cb) + { + wh->cb (wh->cb_cls, + &uar); + wh->cb = NULL; + } + TALER_EXCHANGE_management_update_aml_officer_cancel (wh); +} + + +struct TALER_EXCHANGE_ManagementUpdateAmlOfficer * +TALER_EXCHANGE_management_update_aml_officer ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const struct TALER_AmlOfficerPublicKeyP *officer_pub, + const char *officer_name, + struct GNUNET_TIME_Timestamp change_date, + bool is_active, + bool read_only, + const struct TALER_MasterSignatureP *master_sig, + TALER_EXCHANGE_ManagementUpdateAmlOfficerCallback cb, + void *cb_cls) +{ + struct TALER_EXCHANGE_ManagementUpdateAmlOfficer *wh; + CURL *eh; + json_t *body; + + wh = GNUNET_new (struct TALER_EXCHANGE_ManagementUpdateAmlOfficer); + wh->cb = cb; + wh->cb_cls = cb_cls; + wh->ctx = ctx; + wh->url = TALER_url_join (url, + "management/aml-officers", + 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 ("officer_name", + officer_name), + GNUNET_JSON_pack_data_auto ("officer_pub", + officer_pub), + GNUNET_JSON_pack_data_auto ("master_sig", + master_sig), + GNUNET_JSON_pack_bool ("is_active", + is_active), + GNUNET_JSON_pack_bool ("read_only", + read_only), + GNUNET_JSON_pack_timestamp ("change_date", + change_date)); + 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_update_aml_officer_finished, + wh); + if (NULL == wh->job) + { + TALER_EXCHANGE_management_update_aml_officer_cancel (wh); + return NULL; + } + return wh; +} + + +void +TALER_EXCHANGE_management_update_aml_officer_cancel ( + struct TALER_EXCHANGE_ManagementUpdateAmlOfficer *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_disable.c b/src/lib/exchange_api_management_wire_disable.c new file mode 100644 index 000000000..30749b0e4 --- /dev/null +++ b/src/lib/exchange_api_management_wire_disable.c @@ -0,0 +1,220 @@ +/* + 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 "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_exchange_service.h" +#include "exchange_api_curl_defaults.h" +#include "taler_signatures.h" +#include "taler_curl_lib.h" +#include "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 char *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 ( + GNUNET_JSON_pack_string ("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); + 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 new file mode 100644 index 000000000..5add3e0b0 --- /dev/null +++ b/src/lib/exchange_api_management_wire_enable.c @@ -0,0 +1,245 @@ +/* + 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 "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_exchange_service.h" +#include "exchange_api_curl_defaults.h" +#include "taler_signatures.h" +#include "taler_curl_lib.h" +#include "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 char *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, + 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 ( + GNUNET_JSON_pack_string ("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_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); + 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 new file mode 100644 index 000000000..ba4241dab --- /dev/null +++ b/src/lib/exchange_api_melt.c @@ -0,0 +1,596 @@ +/* + 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_melt.c + * @brief Implementation of the /coins/$COIN_PUB/melt request + * @author Christian Grothoff + */ +#include "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_json_lib.h" +#include "taler_exchange_service.h" +#include "exchange_api_common.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "exchange_api_curl_defaults.h" +#include "exchange_api_refresh_common.h" + + +/** + * @brief A /coins/$COIN_PUB/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 secret the entire melt operation is seeded from. + */ + struct TALER_RefreshMasterSecretP rms; + + /** + * Details about the characteristics of the requested melt operation. + */ + const struct TALER_EXCHANGE_RefreshData *rd; + + /** + * Array of `num_fresh_coins` per-coin values + * returned from melt operation. + */ + struct TALER_EXCHANGE_MeltBlindingDetail *mbds; + + /** + * Handle for the preflight request, or NULL. + */ + struct TALER_EXCHANGE_CsRMeltHandle *csr; + + /** + * 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; + + /** + * True if we need to include @e rms in our melt request. + */ + bool send_rms; +}; + + +/** + * 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; + const struct TALER_EXCHANGE_Keys *key_state; + 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 */ + key_state = mh->keys; + if (GNUNET_OK != + TALER_EXCHANGE_test_signing_key (key_state, + 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 /coins/$COIN_PUB/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_mbds = mh->rd->fresh_pks_len; + mr.details.ok.mbds = mh->mbds; + 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) +{ + const struct TALER_EXCHANGE_Keys *key_state; + json_t *melt_obj; + CURL *eh; + char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32]; + struct TALER_DenominationHashP h_denom_pub; + struct TALER_ExchangeWithdrawValues alg_values[mh->rd->fresh_pks_len]; + + for (unsigned int i = 0; i<mh->rd->fresh_pks_len; i++) + alg_values[i] = mh->mbds[i].alg_value; + if (GNUNET_OK != + TALER_EXCHANGE_get_melt_data_ (&mh->rms, + mh->rd, + alg_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); + melt_obj = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("denom_pub_hash", + &h_denom_pub), + TALER_JSON_pack_denom_sig ("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_data_auto ("rc", + &mh->md.rc), + GNUNET_JSON_pack_allow_null ( + (NULL != mh->md.melted_coin.h_age_commitment) + ? GNUNET_JSON_pack_data_auto ("age_commitment_hash", + mh->md.melted_coin.h_age_commitment) + : GNUNET_JSON_pack_string ("age_commitment_hash", + NULL)), + GNUNET_JSON_pack_allow_null ( + mh->send_rms + ? GNUNET_JSON_pack_data_auto ("rms", + &mh->rms) + : GNUNET_JSON_pack_string ("rms", + NULL))); + { + char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string ( + &mh->coin_pub, + sizeof (struct TALER_CoinSpendPublicKeyP), + pub_str, + sizeof (pub_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "coins/%s/melt", + pub_str); + } + + key_state = mh->keys; + mh->dki = TALER_EXCHANGE_get_denomination_key (key_state, + &mh->md.melted_coin.pub_key); + + /* and now we can at last begin the actual request handling */ + + mh->url = TALER_url_join (mh->exchange_url, + arg_str, + NULL); + if (NULL == mh->url) + { + json_decref (melt_obj); + return GNUNET_SYSERR; + } + eh = TALER_EXCHANGE_curl_easy_get_ (mh->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&mh->ctx, + eh, + melt_obj)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (melt_obj); + return GNUNET_SYSERR; + } + json_decref (melt_obj); + 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 + * CS R request to a exchange. + * + * @param cls closure with our `struct TALER_EXCHANGE_MeltHandle *` + * @param csrr response details + */ +static void +csr_cb (void *cls, + const struct TALER_EXCHANGE_CsRMeltResponse *csrr) +{ + struct TALER_EXCHANGE_MeltHandle *mh = cls; + unsigned int nks_off = 0; + + mh->csr = NULL; + if (MHD_HTTP_OK != csrr->hr.http_status) + { + struct TALER_EXCHANGE_MeltResponse mr = { + .hr = csrr->hr + }; + + mr.hr.hint = "/csr-melt failed"; + mh->melt_cb (mh->melt_cb_cls, + &mr); + TALER_EXCHANGE_melt_cancel (mh); + return; + } + for (unsigned int i = 0; i<mh->rd->fresh_pks_len; i++) + { + const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk = + &mh->rd->fresh_pks[i]; + struct TALER_ExchangeWithdrawValues *wv = &mh->mbds[i].alg_value; + + switch (fresh_pk->key.cipher) + { + case TALER_DENOMINATION_INVALID: + GNUNET_break (0); + fail_mh (mh, + TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR); + return; + case TALER_DENOMINATION_RSA: + GNUNET_assert (TALER_DENOMINATION_RSA == wv->cipher); + break; + case TALER_DENOMINATION_CS: + GNUNET_assert (TALER_DENOMINATION_CS == wv->cipher); + *wv = csrr->details.ok.alg_values[nks_off]; + nks_off++; + break; + } + } + mh->send_rms = true; + 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_RefreshMasterSecretP *rms, + const struct TALER_EXCHANGE_RefreshData *rd, + TALER_EXCHANGE_MeltCallback melt_cb, + void *melt_cb_cls) +{ + struct TALER_EXCHANGE_NonceKey nks[GNUNET_NZL (rd->fresh_pks_len)]; + unsigned int nks_off = 0; + struct TALER_EXCHANGE_MeltHandle *mh; + + if (0 == rd->fresh_pks_len) + { + 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->mbds = GNUNET_new_array (rd->fresh_pks_len, + struct TALER_EXCHANGE_MeltBlindingDetail); + for (unsigned int i = 0; i<rd->fresh_pks_len; i++) + { + const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk = &rd->fresh_pks[i]; + struct TALER_ExchangeWithdrawValues *wv = &mh->mbds[i].alg_value; + + switch (fresh_pk->key.cipher) + { + case TALER_DENOMINATION_INVALID: + GNUNET_break (0); + GNUNET_free (mh->mbds); + GNUNET_free (mh); + return NULL; + case TALER_DENOMINATION_RSA: + wv->cipher = TALER_DENOMINATION_RSA; + break; + case TALER_DENOMINATION_CS: + wv->cipher = TALER_DENOMINATION_CS; + nks[nks_off].pk = fresh_pk; + nks[nks_off].cnc_num = nks_off; + nks_off++; + break; + } + } + mh->keys = TALER_EXCHANGE_keys_incref (keys); + if (0 != nks_off) + { + mh->csr = TALER_EXCHANGE_csr_melt (ctx, + url, + rms, + nks_off, + nks, + &csr_cb, + mh); + if (NULL == mh->csr) + { + 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) +{ + if (NULL != mh->job) + { + GNUNET_CURL_job_cancel (mh->job); + mh->job = NULL; + } + if (NULL != mh->csr) + { + TALER_EXCHANGE_csr_melt_cancel (mh->csr); + mh->csr = NULL; + } + TALER_EXCHANGE_free_melt_data_ (&mh->md); /* does not free 'md' itself */ + GNUNET_free (mh->mbds); + 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_recoup.c b/src/lib/exchange_api_recoup.c new file mode 100644 index 000000000..cfd265f04 --- /dev/null +++ b/src/lib/exchange_api_recoup.c @@ -0,0 +1,369 @@ +/* + 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 "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_json_lib.h" +#include "taler_exchange_service.h" +#include "exchange_api_common.h" +#include "exchange_api_handle.h" +#include "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_ExchangeWithdrawValues *exchange_vals, + const struct TALER_PlanchetMasterSecretP *ps, + 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 TALER_DenominationBlindingKeyP bks; + + ph = GNUNET_new (struct TALER_EXCHANGE_RecoupHandle); + TALER_planchet_setup_coin_priv (ps, + exchange_vals, + &coin_priv); + TALER_planchet_blinding_secret_create (ps, + exchange_vals, + &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_withdraw_values ("ewv", + exchange_vals), + GNUNET_JSON_pack_data_auto ("coin_sig", + &ph->coin_sig), + GNUNET_JSON_pack_data_auto ("coin_blind_key_secret", + &bks)); + if (TALER_DENOMINATION_CS == denom_sig->cipher) + { + struct TALER_CsNonce 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); + GNUNET_assert ( + 0 == + json_object_set_new (recoup_obj, + "cs_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 new file mode 100644 index 000000000..0bcd44dec --- /dev/null +++ b/src/lib/exchange_api_recoup_refresh.c @@ -0,0 +1,363 @@ +/* + 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 "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_json_lib.h" +#include "taler_exchange_service.h" +#include "exchange_api_common.h" +#include "exchange_api_handle.h" +#include "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); +} + + +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_ExchangeWithdrawValues *exchange_vals, + const struct TALER_RefreshMasterSecretP *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 TALER_DenominationBlindingKeyP 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, + exchange_vals, + &coin_priv); + TALER_planchet_blinding_secret_create (ps, + exchange_vals, + &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_withdraw_values ("ewv", + exchange_vals), + GNUNET_JSON_pack_data_auto ("coin_sig", + &ph->coin_sig), + GNUNET_JSON_pack_data_auto ("coin_blind_key_secret", + &bks)); + + if (TALER_DENOMINATION_CS == denom_sig->cipher) + { + struct TALER_CsNonce 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_refresh_nonce_derive (rms, + idx, + &nonce); + GNUNET_assert ( + 0 == + json_object_set_new (recoup_obj, + "cs_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-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_refresh_common.c b/src/lib/exchange_api_refresh_common.c new file mode 100644 index 000000000..0a6665b55 --- /dev/null +++ b/src/lib/exchange_api_refresh_common.c @@ -0,0 +1,249 @@ +/* + This file is part of TALER + Copyright (C) 2015-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_refresh_common.c + * @brief Serialization logic shared between melt and reveal steps during refreshing + * @author Christian Grothoff + */ +#include "platform.h" +#include "exchange_api_refresh_common.h" + + +void +TALER_EXCHANGE_free_melt_data_ (struct MeltData *md) +{ + for (unsigned int i = 0; i < TALER_CNC_KAPPA; i++) + { + struct TALER_RefreshCoinData *rcds = md->rcd[i]; + + if (NULL == rcds) + continue; + for (unsigned int j = 0; j < md->num_fresh_coins; j++) + TALER_blinded_planchet_free (&rcds[j].blinded_planchet); + GNUNET_free (rcds); + } + TALER_denom_pub_free (&md->melted_coin.pub_key); + TALER_denom_sig_free (&md->melted_coin.sig); + if (NULL != md->fcds) + { + for (unsigned int j = 0; j<md->num_fresh_coins; j++) + { + struct FreshCoinData *fcd = &md->fcds[j]; + + TALER_denom_pub_free (&fcd->fresh_pk); + for (size_t i = 0; i < TALER_CNC_KAPPA; i++) + { + TALER_age_commitment_proof_free (fcd->age_commitment_proofs[i]); + GNUNET_free (fcd->age_commitment_proofs[i]); + } + } + GNUNET_free (md->fcds); + } + /* Finally, clean up a bit... */ + GNUNET_CRYPTO_zero_keys (md, + sizeof (struct MeltData)); +} + + +enum GNUNET_GenericReturnValue +TALER_EXCHANGE_get_melt_data_ ( + const struct TALER_RefreshMasterSecretP *rms, + const struct TALER_EXCHANGE_RefreshData *rd, + const struct TALER_ExchangeWithdrawValues *alg_values, + struct MeltData *md) +{ + struct TALER_Amount total; + struct TALER_CoinSpendPublicKeyP coin_pub; + struct TALER_CsNonce nonces[rd->fresh_pks_len]; + bool uses_cs = false; + + GNUNET_CRYPTO_eddsa_key_get_public (&rd->melt_priv.eddsa_priv, + &coin_pub.eddsa_pub); + /* build up melt data structure */ + memset (md, + 0, + sizeof (*md)); + md->num_fresh_coins = rd->fresh_pks_len; + md->melted_coin.coin_priv = rd->melt_priv; + md->melted_coin.melt_amount_with_fee = rd->melt_amount; + md->melted_coin.fee_melt = rd->melt_pk.fees.refresh; + md->melted_coin.original_value = rd->melt_pk.value; + md->melted_coin.expire_deposit = rd->melt_pk.expire_deposit; + md->melted_coin.age_commitment_proof = rd->melt_age_commitment_proof; + md->melted_coin.h_age_commitment = rd->melt_h_age_commitment; + + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (rd->melt_amount.currency, + &total)); + TALER_denom_pub_deep_copy (&md->melted_coin.pub_key, + &rd->melt_pk.key); + TALER_denom_sig_deep_copy (&md->melted_coin.sig, + &rd->melt_sig); + md->fcds = GNUNET_new_array (md->num_fresh_coins, + struct FreshCoinData); + for (unsigned int j = 0; j<rd->fresh_pks_len; j++) + { + struct FreshCoinData *fcd = &md->fcds[j]; + + if (alg_values[j].cipher != rd->fresh_pks[j].key.cipher) + { + GNUNET_break (0); + TALER_EXCHANGE_free_melt_data_ (md); + return GNUNET_SYSERR; + } + if (TALER_DENOMINATION_CS == alg_values[j].cipher) + { + uses_cs = true; + TALER_cs_refresh_nonce_derive ( + rms, + j, + &nonces[j]); + } + TALER_denom_pub_deep_copy (&fcd->fresh_pk, + &rd->fresh_pks[j].key); + if ( (0 > + TALER_amount_add (&total, + &total, + &rd->fresh_pks[j].value)) || + (0 > + TALER_amount_add (&total, + &total, + &rd->fresh_pks[j].fees.withdraw)) ) + { + GNUNET_break (0); + TALER_EXCHANGE_free_melt_data_ (md); + return GNUNET_SYSERR; + } + } + + /* verify that melt_amount is above total cost */ + if (1 == + TALER_amount_cmp (&total, + &rd->melt_amount) ) + { + /* Eh, this operation is more expensive than the + @a melt_amount. This is not OK. */ + GNUNET_break (0); + TALER_EXCHANGE_free_melt_data_ (md); + return GNUNET_SYSERR; + } + + /* build up coins */ + for (unsigned int i = 0; i<TALER_CNC_KAPPA; i++) + { + struct TALER_TransferSecretP trans_sec; + + TALER_planchet_secret_to_transfer_priv ( + rms, + &rd->melt_priv, + i, + &md->transfer_priv[i]); + + GNUNET_CRYPTO_ecdhe_key_get_public ( + &md->transfer_priv[i].ecdhe_priv, + &md->transfer_pub[i].ecdhe_pub); + + TALER_link_derive_transfer_secret (&rd->melt_priv, + &md->transfer_priv[i], + &trans_sec); + + md->rcd[i] = GNUNET_new_array (rd->fresh_pks_len, + struct TALER_RefreshCoinData); + + for (unsigned int j = 0; j<rd->fresh_pks_len; j++) + { + struct FreshCoinData *fcd = &md->fcds[j]; + struct TALER_CoinSpendPrivateKeyP *coin_priv = &fcd->coin_priv; + struct TALER_PlanchetMasterSecretP *ps = &fcd->ps[i]; + struct TALER_RefreshCoinData *rcd = &md->rcd[i][j]; + union TALER_DenominationBlindingKeyP *bks = &fcd->bks[i]; + struct TALER_PlanchetDetail pd; + struct TALER_CoinPubHashP c_hash; + struct TALER_AgeCommitmentHash ach; + struct TALER_AgeCommitmentHash *pah = NULL; + + TALER_transfer_secret_to_planchet_secret (&trans_sec, + j, + ps); + + TALER_planchet_setup_coin_priv (ps, + &alg_values[j], + coin_priv); + + TALER_planchet_blinding_secret_create (ps, + &alg_values[j], + bks); + + if (NULL != rd->melt_age_commitment_proof) + { + fcd->age_commitment_proofs[i] = GNUNET_new (struct + TALER_AgeCommitmentProof); + + GNUNET_assert (GNUNET_OK == + TALER_age_commitment_derive ( + md->melted_coin.age_commitment_proof, + &trans_sec.key, + fcd->age_commitment_proofs[i])); + + TALER_age_commitment_hash ( + &fcd->age_commitment_proofs[i]->commitment, + &ach); + pah = &ach; + } + + if (TALER_DENOMINATION_CS == alg_values[j].cipher) + pd.blinded_planchet.details.cs_blinded_planchet.nonce = nonces[j]; + + if (GNUNET_OK != + TALER_planchet_prepare (&fcd->fresh_pk, + &alg_values[j], + bks, + coin_priv, + pah, + &c_hash, + &pd)) + { + GNUNET_break_op (0); + TALER_EXCHANGE_free_melt_data_ (md); + return GNUNET_SYSERR; + } + rcd->blinded_planchet = pd.blinded_planchet; + rcd->dk = &fcd->fresh_pk; + } + } + + /* Finally, compute refresh commitment */ + { + struct TALER_RefreshCommitmentEntry rce[TALER_CNC_KAPPA]; + + for (unsigned int i = 0; i<TALER_CNC_KAPPA; i++) + { + rce[i].transfer_pub = md->transfer_pub[i]; + rce[i].new_coins = md->rcd[i]; + } + TALER_refresh_get_commitment (&md->rc, + TALER_CNC_KAPPA, + uses_cs + ? rms + : NULL, + rd->fresh_pks_len, + rce, + &coin_pub, + &rd->melt_amount); + } + return GNUNET_OK; +} diff --git a/src/lib/exchange_api_refresh_common.h b/src/lib/exchange_api_refresh_common.h new file mode 100644 index 000000000..0cb80f17e --- /dev/null +++ b/src/lib/exchange_api_refresh_common.h @@ -0,0 +1,201 @@ +/* + This file is part of TALER + Copyright (C) 2015-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_refresh_common.h + * @brief shared (serialization) logic for refresh protocol + * @author Christian Grothoff + */ +#ifndef REFRESH_COMMON_H +#define REFRESH_COMMON_H +#include <jansson.h> +#include "taler_json_lib.h" +#include "taler_exchange_service.h" +#include "taler_signatures.h" + + +/** + * Information about a coin we are melting. + */ +struct MeltedCoin +{ + /** + * Private key of the coin. + */ + struct TALER_CoinSpendPrivateKeyP coin_priv; + + /** + * Amount this coin contributes to the melt, including fee. + */ + struct TALER_Amount melt_amount_with_fee; + + /** + * The applicable fee for melting a coin of this denomination + */ + struct TALER_Amount fee_melt; + + /** + * The original value of the coin. + */ + struct TALER_Amount original_value; + + /** + * The original age commitment, its proof and its hash. MUST be NULL if no + * age commitment was set. + */ + const struct TALER_AgeCommitmentProof *age_commitment_proof; + const struct TALER_AgeCommitmentHash *h_age_commitment; + + /** + * Timestamp indicating when coins of this denomination become invalid. + */ + struct GNUNET_TIME_Timestamp expire_deposit; + + /** + * Denomination key of the original coin. + */ + struct TALER_DenominationPublicKey pub_key; + + /** + * Exchange's signature over the coin. + */ + struct TALER_DenominationSignature sig; + +}; + + +/** + * Data we keep for each fresh coin created in the + * melt process. + */ +struct FreshCoinData +{ + /** + * Denomination public key of the coin. + */ + struct TALER_DenominationPublicKey fresh_pk; + + /** + * Array of planchet secrets for the coins, depending + * on the cut-and-choose. + */ + struct TALER_PlanchetMasterSecretP ps[TALER_CNC_KAPPA]; + + /** + * Private key of the coin. + */ + struct TALER_CoinSpendPrivateKeyP coin_priv; + + /** + * Arrays of age commitments and proofs to be created, one for each + * cut-and-choose dimension. NULL if age restriction is not applicable. + */ + struct TALER_AgeCommitmentProof *age_commitment_proofs[TALER_CNC_KAPPA]; + + /** + * Blinding key secrets for the coins, depending on the + * cut-and-choose. + */ + union TALER_DenominationBlindingKeyP bks[TALER_CNC_KAPPA]; + +}; + + +/** + * Melt data in non-serialized format for convenient processing. + */ +struct MeltData +{ + + /** + * Hash over the committed data during refresh operation. + */ + struct TALER_RefreshCommitmentP rc; + + /** + * Information about the melted coin. + */ + struct MeltedCoin melted_coin; + + /** + * Array of length @e num_fresh_coins with information + * about each fresh coin. + */ + struct FreshCoinData *fcds; + + /** + * Transfer secrets, one per cut and choose. + */ + struct TALER_TransferSecretP trans_sec[TALER_CNC_KAPPA]; + + /** + * Transfer private keys for each cut-and-choose dimension. + */ + struct TALER_TransferPrivateKeyP transfer_priv[TALER_CNC_KAPPA]; + + /** + * Transfer public key of this commitment. + */ + struct TALER_TransferPublicKeyP transfer_pub[TALER_CNC_KAPPA]; + + /** + * Transfer secrets, one per cut and choose. + */ + struct TALER_RefreshCommitmentEntry rce[TALER_CNC_KAPPA]; + + /** + * Blinded planchets and denominations of the fresh coins, depending on the cut-and-choose. Array of length + * @e num_fresh_coins. + */ + struct TALER_RefreshCoinData *rcd[TALER_CNC_KAPPA]; + + /** + * Number of coins we are creating + */ + uint16_t num_fresh_coins; + +}; + + +/** + * Compute the melt data from the refresh data and secret. + * + * @param rms secret internals of the refresh-reveal operation + * @param rd refresh data with the characteristics of the operation + * @param alg_values contributions from the exchange into the melt + * @param[out] md where to write the derived melt data + */ +enum GNUNET_GenericReturnValue +TALER_EXCHANGE_get_melt_data_ ( + const struct TALER_RefreshMasterSecretP *rms, + const struct TALER_EXCHANGE_RefreshData *rd, + const struct TALER_ExchangeWithdrawValues *alg_values, + struct MeltData *md); + + +/** + * Free all information associated with a melting session. Note + * that we allow the melting session to be only partially initialized, + * as we use this function also when freeing melt data that was not + * fully initialized. + * + * @param[in] md melting data to release, the pointer itself is NOT + * freed (as it is typically not allocated by itself) + */ +void +TALER_EXCHANGE_free_melt_data_ (struct MeltData *md); + +#endif diff --git a/src/lib/exchange_api_refreshes_reveal.c b/src/lib/exchange_api_refreshes_reveal.c new file mode 100644 index 000000000..220682992 --- /dev/null +++ b/src/lib/exchange_api_refreshes_reveal.c @@ -0,0 +1,531 @@ +/* + 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_refreshes_reveal.c + * @brief Implementation of the /refreshes/$RCH/reveal requests + * @author Christian Grothoff + */ +#include "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_json_lib.h" +#include "taler_exchange_service.h" +#include "exchange_api_handle.h" +#include "taler_signatures.h" +#include "exchange_api_curl_defaults.h" +#include "exchange_api_refresh_common.h" + + +/** + * @brief A /refreshes/$RCH/reveal Handle + */ +struct TALER_EXCHANGE_RefreshesRevealHandle +{ + + /** + * 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; + + /** + * Exchange-contributed values to the operation. + */ + struct TALER_ExchangeWithdrawValues *alg_values; + + /** + * Function to call with the result. + */ + TALER_EXCHANGE_RefreshesRevealCallback reveal_cb; + + /** + * Closure for @e reveal_cb. + */ + void *reveal_cb_cls; + + /** + * Actual information about the melt operation. + */ + struct MeltData md; + + /** + * The index selected by the exchange in cut-and-choose to not be revealed. + */ + uint16_t noreveal_index; + +}; + + +/** + * We got a 200 OK response for the /refreshes/$RCH/reveal operation. Extract + * the coin signatures and return them to the caller. The signatures we get + * from the exchange is for the blinded value. Thus, we first must unblind + * them and then should verify their validity. + * + * If everything checks out, we return the unblinded signatures + * to the application via the callback. + * + * @param rrh operation handle + * @param json reply from the exchange + * @param[out] rcis array of length `num_fresh_coins`, initialized to contain the coin data + * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors + */ +static enum GNUNET_GenericReturnValue +refresh_reveal_ok (struct TALER_EXCHANGE_RefreshesRevealHandle *rrh, + const json_t *json, + struct TALER_EXCHANGE_RevealedCoinInfo *rcis) +{ + const json_t *jsona; + struct GNUNET_JSON_Specification outer_spec[] = { + GNUNET_JSON_spec_array_const ("ev_sigs", + &jsona), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + outer_spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (rrh->md.num_fresh_coins != json_array_size (jsona)) + { + /* Number of coins generated does not match our expectation */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + for (unsigned int i = 0; i<rrh->md.num_fresh_coins; i++) + { + struct TALER_EXCHANGE_RevealedCoinInfo *rci = &rcis[i]; + const struct FreshCoinData *fcd = &rrh->md.fcds[i]; + const struct TALER_DenominationPublicKey *pk; + json_t *jsonai; + struct TALER_BlindedDenominationSignature blind_sig; + struct TALER_CoinSpendPublicKeyP coin_pub; + struct TALER_CoinPubHashP coin_hash; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_blinded_denom_sig ("ev_sig", + &blind_sig), + GNUNET_JSON_spec_end () + }; + struct TALER_FreshCoin coin; + union TALER_DenominationBlindingKeyP bks; + const struct TALER_AgeCommitmentHash *pah = NULL; + + rci->ps = fcd->ps[rrh->noreveal_index]; + rci->bks = fcd->bks[rrh->noreveal_index]; + rci->age_commitment_proof = NULL; + + pk = &fcd->fresh_pk; + jsonai = json_array_get (jsona, i); + + GNUNET_assert (NULL != jsonai); + + if (NULL != rrh->md.melted_coin.age_commitment_proof) + { + rci->age_commitment_proof = + fcd->age_commitment_proofs[rrh->noreveal_index]; + + TALER_age_commitment_hash (&rci->age_commitment_proof->commitment, + &rci->h_age_commitment); + pah = &rci->h_age_commitment; + } + + if (GNUNET_OK != + GNUNET_JSON_parse (jsonai, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + TALER_planchet_setup_coin_priv (&rci->ps, + &rrh->alg_values[i], + &rci->coin_priv); + TALER_planchet_blinding_secret_create (&rci->ps, + &rrh->alg_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_sig, + &bks, + &rci->coin_priv, + pah, + &coin_hash, + &rrh->alg_values[i], + &coin)) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return GNUNET_SYSERR; + } + GNUNET_JSON_parse_free (spec); + rci->sig = coin.sig; + } + return GNUNET_OK; +} + + +/** + * Function called when we're done processing the + * HTTP /refreshes/$RCH/reveal request. + * + * @param cls the `struct TALER_EXCHANGE_RefreshHandle` + * @param response_code HTTP response code, 0 on error + * @param response parsed JSON result, NULL on error + */ +static void +handle_refresh_reveal_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_RefreshesRevealHandle *rrh = cls; + const json_t *j = response; + struct TALER_EXCHANGE_RevealResult rr = { + .hr.reply = j, + .hr.http_status = (unsigned int) response_code + }; + + rrh->job = NULL; + switch (response_code) + { + case 0: + rr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + { + struct TALER_EXCHANGE_RevealedCoinInfo rcis[rrh->md.num_fresh_coins]; + enum GNUNET_GenericReturnValue ret; + + memset (rcis, + 0, + sizeof (rcis)); + ret = refresh_reveal_ok (rrh, + j, + rcis); + if (GNUNET_OK != ret) + { + rr.hr.http_status = 0; + rr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + else + { + GNUNET_assert (rrh->noreveal_index < TALER_CNC_KAPPA); + rr.details.ok.num_coins = rrh->md.num_fresh_coins; + rr.details.ok.coins = rcis; + rrh->reveal_cb (rrh->reveal_cb_cls, + &rr); + rrh->reveal_cb = NULL; + } + for (unsigned int i = 0; i<rrh->md.num_fresh_coins; i++) + { + TALER_denom_sig_free (&rcis[i].sig); + TALER_age_commitment_proof_free (rcis[i].age_commitment_proof); + } + TALER_EXCHANGE_refreshes_reveal_cancel (rrh); + 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: + /* Nothing really to verify, exchange says our reveal is inconsistent + with our commitment, so either side is buggy; 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: + /* Server claims key expired or has been revoked */ + 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 refreshes reveal\n", + (unsigned int) response_code, + (int) rr.hr.ec); + break; + } + if (NULL != rrh->reveal_cb) + rrh->reveal_cb (rrh->reveal_cb_cls, + &rr); + TALER_EXCHANGE_refreshes_reveal_cancel (rrh); +} + + +struct TALER_EXCHANGE_RefreshesRevealHandle * +TALER_EXCHANGE_refreshes_reveal ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const struct TALER_RefreshMasterSecretP *rms, + const struct TALER_EXCHANGE_RefreshData *rd, + unsigned int num_coins, + const struct TALER_ExchangeWithdrawValues alg_values[static num_coins], + uint32_t noreveal_index, + TALER_EXCHANGE_RefreshesRevealCallback reveal_cb, + void *reveal_cb_cls) +{ + struct TALER_EXCHANGE_RefreshesRevealHandle *rrh; + json_t *transfer_privs; + json_t *new_denoms_h; + json_t *coin_evs; + json_t *reveal_obj; + json_t *link_sigs; + json_t *old_age_commitment = NULL; + CURL *eh; + struct MeltData md; + char arg_str[sizeof (struct TALER_RefreshCommitmentP) * 2 + 32]; + bool send_rms = false; + + GNUNET_assert (num_coins == rd->fresh_pks_len); + if (noreveal_index >= TALER_CNC_KAPPA) + { + /* We check this here, as it would be really bad to below just + disclose all the transfer keys. Note that this error should + have been caught way earlier when the exchange replied, but maybe + we had some internal corruption that changed the value... */ + GNUNET_break (0); + return NULL; + } + if (GNUNET_OK != + TALER_EXCHANGE_get_melt_data_ (rms, + rd, + alg_values, + &md)) + { + GNUNET_break (0); + return NULL; + } + + /* now new_denoms */ + GNUNET_assert (NULL != (new_denoms_h = json_array ())); + GNUNET_assert (NULL != (coin_evs = json_array ())); + GNUNET_assert (NULL != (link_sigs = json_array ())); + for (unsigned int i = 0; i<md.num_fresh_coins; i++) + { + const struct TALER_RefreshCoinData *rcd = &md.rcd[noreveal_index][i]; + struct TALER_DenominationHashP denom_hash; + + if (TALER_DENOMINATION_CS == md.fcds[i].fresh_pk.cipher) + send_rms = true; + TALER_denom_pub_hash (&md.fcds[i].fresh_pk, + &denom_hash); + GNUNET_assert (0 == + json_array_append_new (new_denoms_h, + GNUNET_JSON_from_data_auto ( + &denom_hash))); + GNUNET_assert (0 == + json_array_append_new ( + coin_evs, + GNUNET_JSON_PACK ( + TALER_JSON_pack_blinded_planchet ( + NULL, + &rcd->blinded_planchet)))); + { + struct TALER_CoinSpendSignatureP link_sig; + struct TALER_BlindedCoinHashP bch; + + TALER_coin_ev_hash (&rcd->blinded_planchet, + &denom_hash, + &bch); + TALER_wallet_link_sign ( + &denom_hash, + &md.transfer_pub[noreveal_index], + &bch, + &md.melted_coin.coin_priv, + &link_sig); + GNUNET_assert (0 == + json_array_append_new ( + link_sigs, + GNUNET_JSON_from_data_auto (&link_sig))); + } + } + + /* build array of transfer private keys */ + GNUNET_assert (NULL != (transfer_privs = json_array ())); + for (unsigned int j = 0; j<TALER_CNC_KAPPA; j++) + { + if (j == noreveal_index) + { + /* This is crucial: exclude the transfer key for the noreval index! */ + continue; + } + GNUNET_assert (0 == + json_array_append_new (transfer_privs, + GNUNET_JSON_from_data_auto ( + &md.transfer_priv[j]))); + } + + /* build array of old age commitment, if applicable */ + if (NULL != rd->melt_age_commitment_proof) + { + GNUNET_assert (NULL != rd->melt_h_age_commitment); + GNUNET_assert (NULL != (old_age_commitment = json_array ())); + + for (size_t i = 0; i < rd->melt_age_commitment_proof->commitment.num; i++) + { + enum GNUNET_GenericReturnValue ret; + ret = json_array_append_new ( + old_age_commitment, + GNUNET_JSON_from_data_auto ( + &rd->melt_age_commitment_proof->commitment.keys[i])); + GNUNET_assert (0 == ret); + } + } + + /* build main JSON request */ + reveal_obj = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("transfer_pub", + &md.transfer_pub[noreveal_index]), + GNUNET_JSON_pack_allow_null ( + send_rms + ? GNUNET_JSON_pack_data_auto ("rms", + rms) + : GNUNET_JSON_pack_string ("rms", + NULL)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_array_steal ("old_age_commitment", + old_age_commitment)), + GNUNET_JSON_pack_array_steal ("transfer_privs", + transfer_privs), + GNUNET_JSON_pack_array_steal ("link_sigs", + link_sigs), + GNUNET_JSON_pack_array_steal ("new_denoms_h", + new_denoms_h), + GNUNET_JSON_pack_array_steal ("coin_evs", + coin_evs)); + { + char pub_str[sizeof (struct TALER_RefreshCommitmentP) * 2]; + char *end; + + end = GNUNET_STRINGS_data_to_string (&md.rc, + sizeof (md.rc), + pub_str, + sizeof (pub_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "refreshes/%s/reveal", + pub_str); + } + /* finally, we can actually issue the request */ + rrh = GNUNET_new (struct TALER_EXCHANGE_RefreshesRevealHandle); + rrh->noreveal_index = noreveal_index; + rrh->reveal_cb = reveal_cb; + rrh->reveal_cb_cls = reveal_cb_cls; + rrh->md = md; + rrh->alg_values + = GNUNET_memdup (alg_values, + md.num_fresh_coins + * sizeof (struct TALER_ExchangeWithdrawValues)); + rrh->url = TALER_url_join (url, + arg_str, + NULL); + if (NULL == rrh->url) + { + json_decref (reveal_obj); + TALER_EXCHANGE_free_melt_data_ (&md); + GNUNET_free (rrh->alg_values); + GNUNET_free (rrh); + return NULL; + } + + eh = TALER_EXCHANGE_curl_easy_get_ (rrh->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&rrh->ctx, + eh, + reveal_obj)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (reveal_obj); + TALER_EXCHANGE_free_melt_data_ (&md); + GNUNET_free (rrh->alg_values); + GNUNET_free (rrh->url); + GNUNET_free (rrh); + return NULL; + } + json_decref (reveal_obj); + rrh->job = GNUNET_CURL_job_add2 (ctx, + eh, + rrh->ctx.headers, + &handle_refresh_reveal_finished, + rrh); + return rrh; +} + + +void +TALER_EXCHANGE_refreshes_reveal_cancel ( + struct TALER_EXCHANGE_RefreshesRevealHandle *rrh) +{ + if (NULL != rrh->job) + { + GNUNET_CURL_job_cancel (rrh->job); + rrh->job = NULL; + } + GNUNET_free (rrh->alg_values); + GNUNET_free (rrh->url); + TALER_curl_easy_post_finished (&rrh->ctx); + TALER_EXCHANGE_free_melt_data_ (&rrh->md); + GNUNET_free (rrh); +} + + +/* exchange_api_refreshes_reveal.c */ diff --git a/src/lib/exchange_api_refund.c b/src/lib/exchange_api_refund.c new file mode 100644 index 000000000..7401bfe4f --- /dev/null +++ b/src/lib/exchange_api_refund.c @@ -0,0 +1,480 @@ +/* + 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 "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_json_lib.h" +#include "taler_exchange_service.h" +#include "exchange_api_handle.h" +#include "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_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_stefan.c b/src/lib/exchange_api_stefan.c new file mode 100644 index 000000000..c3576cd73 --- /dev/null +++ b/src/lib/exchange_api_stefan.c @@ -0,0 +1,320 @@ +/* + 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_stefan.c + * @brief calculations on the STEFAN curve + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" +#include <math.h> + + +/** + * Determine smallest denomination in @a keys. + * + * @param keys exchange response to evaluate + * @return NULL on error (no denominations) + */ +static const struct TALER_Amount * +get_unit (const struct TALER_EXCHANGE_Keys *keys) +{ + const struct TALER_Amount *min = NULL; + + for (unsigned int i = 0; i<keys->num_denom_keys; i++) + { + const struct TALER_EXCHANGE_DenomPublicKey *dk + = &keys->denom_keys[i]; + + if ( (NULL == min) || + (1 == TALER_amount_cmp (min, + /* > */ + &dk->value)) ) + min = &dk->value; + } + GNUNET_break (NULL != min); + return min; +} + + +/** + * Convert amount to double for STEFAN curve evaluation. + * + * @param a input amount + * @return (rounded) amount as a double + */ +static double +amount_to_double (const struct TALER_Amount *a) +{ + double d = (double) a->value; + + d += a->fraction / ((double) TALER_AMOUNT_FRAC_BASE); + return d; +} + + +/** + * Convert double to amount for STEFAN curve evaluation. + * + * @param dv input amount + * @param currency deisred currency + * @param[out] rval (rounded) amount as a double + */ +static void +double_to_amount (double dv, + const char *currency, + struct TALER_Amount *rval) +{ + double rem; + + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (currency, + rval)); + rval->value = floorl (dv); + rem = dv - ((double) rval->value); + if (rem < 0.0) + rem = 0.0; + rem *= TALER_AMOUNT_FRAC_BASE; + rval->fraction = floorl (rem); + if (rval->fraction >= TALER_AMOUNT_FRAC_BASE) + { + /* Strange, multiplication overflowed our range, + round up value instead */ + rval->fraction = 0; + rval->value += 1; + } +} + + +enum GNUNET_GenericReturnValue +TALER_EXCHANGE_keys_stefan_b2n ( + const struct TALER_EXCHANGE_Keys *keys, + const struct TALER_Amount *brut, + struct TALER_Amount *net) +{ + const struct TALER_Amount *min; + double log_d = amount_to_double (&keys->stefan_log); + double lin_d = keys->stefan_lin; + double abs_d = amount_to_double (&keys->stefan_abs); + double bru_d = amount_to_double (brut); + double min_d; + double fee_d; + double net_d; + + if (TALER_amount_is_zero (brut)) + { + *net = *brut; + return GNUNET_NO; + } + min = get_unit (keys); + if (NULL == min) + return GNUNET_SYSERR; + if (1.0f <= keys->stefan_lin) + { + /* This cannot work, linear STEFAN fee estimate always + exceed any gross amount. */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + min_d = amount_to_double (min); + fee_d = abs_d + + log_d * log2 (bru_d / min_d) + + lin_d * bru_d; + if (fee_d > bru_d) + { + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (brut->currency, + net)); + return GNUNET_NO; + } + net_d = bru_d - fee_d; + double_to_amount (net_d, + brut->currency, + net); + return GNUNET_OK; +} + + +/** + * Our function + * f(x) := ne + ab + lo * log2(x/mi) + li * x - x + * for #newton(). + */ +static double +eval_f (double mi, + double ab, + double lo, + double li, + double ne, + double x) +{ + return ne + ab + lo * log2 (x / mi) + li * x - x; +} + + +/** + * Our function + * f'(x) := lo / log(2) / x + li - 1 + * for #newton(). + */ +static double +eval_fp (double mi, + double lo, + double li, + double ne, + double x) +{ + return lo / log (2) / x + li - 1; +} + + +/** + * Use Newton's method to find x where f(x)=0. + * + * @return x where "eval_f(x)==0". + */ +static double +newton (double mi, + double ab, + double lo, + double li, + double ne) +{ + const double eps = 0.00000001; /* max error allowed */ + double min_ab = ne + ab; /* result cannot be smaller than this! */ + /* compute lower bounds by various heuristics */ + double min_ab_li = min_ab + min_ab * li; + double min_ab_li_lo = min_ab_li + log2 (min_ab_li / mi) * lo; + double min_ab_lo = min_ab + log2 (min_ab / mi) * lo; + double min_ab_lo_li = min_ab_lo + min_ab_lo * li; + /* take global lower bound */ + double x_min = GNUNET_MAX (min_ab_lo_li, + min_ab_li_lo); + double x = x_min; /* use lower bound as starting point */ + + /* Objective: invert + ne := br - ab - lo * log2 (br/mi) - li * br + to find 'br'. + Method: use Newton's method to find root of: + f(x) := ne + ab + lo * log2 (x/mi) + li * x - x + using also + f'(x) := lo / log(2) / x + li - 1 + */ + /* Loop to abort in case of divergence; + 100 is already very high, 2-4 is normal! */ + for (unsigned int i = 0; i<100; i++) + { + double fx = eval_f (mi, ab, lo, li, ne, x); + double fxp = eval_fp (mi, lo, li, ne, x); + double x_new = x - fx / fxp; + + if (fabs (x - x_new) <= eps) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Needed %u rounds from %f to result BRUT %f => NET: %f\n", + i, + x_min, + x_new, + x_new - ab - li * x_new - lo * log2 (x / mi)); + return x_new; + } + if (x_new < x_min) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Divergence, obtained very bad estimate %f after %u rounds!\n", + x_new, + i); + return x_min; + } + x = x_new; + } + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Slow convergence, returning bad estimate %f!\n", + x); + return x; +} + + +enum GNUNET_GenericReturnValue +TALER_EXCHANGE_keys_stefan_n2b ( + const struct TALER_EXCHANGE_Keys *keys, + const struct TALER_Amount *net, + struct TALER_Amount *brut) +{ + const struct TALER_Amount *min; + double lin_d = keys->stefan_lin; + double log_d = amount_to_double (&keys->stefan_log); + double abs_d = amount_to_double (&keys->stefan_abs); + double net_d = amount_to_double (net); + double min_d; + double brut_d; + + if (TALER_amount_is_zero (net)) + { + *brut = *net; + return GNUNET_NO; + } + min = get_unit (keys); + if (NULL == min) + return GNUNET_SYSERR; + if (1.0f <= keys->stefan_lin) + { + /* This cannot work, linear STEFAN fee estimate always + exceed any gross amount. */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + min_d = amount_to_double (min); + brut_d = newton (min_d, + abs_d, + log_d, + lin_d, + net_d); + double_to_amount (brut_d, + net->currency, + brut); + return GNUNET_OK; +} + + +void +TALER_EXCHANGE_keys_stefan_round ( + const struct TALER_EXCHANGE_Keys *keys, + struct TALER_Amount *val) +{ + const struct TALER_Amount *min; + uint32_t mod = 1; + uint32_t frac; + uint32_t rst; + + min = get_unit (keys); + if (NULL == min) + return; + frac = min->fraction; + while (0 == frac % 10) + { + mod *= 10; + frac /= 10; + } + rst = val->fraction % mod; + if (rst < mod / 2) + val->fraction -= rst; + else + val->fraction += mod - rst; +} diff --git a/src/lib/test_stefan.c b/src/lib/test_stefan.c new file mode 100644 index 000000000..838cca769 --- /dev/null +++ b/src/lib/test_stefan.c @@ -0,0 +1,206 @@ +/* + 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/test_stefan.c + * @brief test calculations on the STEFAN curve + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "exchange_api_handle.h" + + +/** + * Check if @a a and @a b are numerically close. + * + * @param a an amount + * @param b an amount + * @return true if both values are quite close + */ +static bool +amount_close (const struct TALER_Amount *a, + const struct TALER_Amount *b) +{ + struct TALER_Amount delta; + + switch (TALER_amount_cmp (a, + b)) + { + case -1: /* a < b */ + GNUNET_assert (0 < + TALER_amount_subtract (&delta, + b, + a)); + break; + case 0: + /* perfect */ + return true; + case 1: /* a > b */ + GNUNET_assert (0 < + TALER_amount_subtract (&delta, + a, + b)); + break; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Rounding error is %s\n", + TALER_amount2s (&delta)); + if (delta.value > 0) + { + GNUNET_break (0); + return false; + } + if (delta.fraction > 5000) + { + GNUNET_break (0); + return false; + } + return true; /* let's consider this a rounding error */ +} + + +int +main (int argc, + char **argv) +{ + struct TALER_EXCHANGE_DenomPublicKey dk; + struct TALER_EXCHANGE_Keys keys = { + .denom_keys = &dk, + .num_denom_keys = 1 + }; + struct TALER_Amount brut; + struct TALER_Amount net; + + (void) argc; + (void) argv; + GNUNET_log_setup ("test-stefan", + "INFO", + NULL); + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount ("MAGIC:0.13", + &dk.value)); + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount ("MAGIC:1", + &keys.stefan_abs)); + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount ("MAGIC:0.13", + &keys.stefan_log)); + keys.stefan_lin = 1.15; + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount ("MAGIC:4", + &brut)); + GNUNET_log_skip (1, + GNUNET_NO); + GNUNET_assert (GNUNET_SYSERR == + TALER_EXCHANGE_keys_stefan_b2n (&keys, + &brut, + &net)); + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount ("MAGIC:4", + &net)); + GNUNET_log_skip (1, + GNUNET_NO); + GNUNET_assert (GNUNET_SYSERR == + TALER_EXCHANGE_keys_stefan_n2b (&keys, + &net, + &brut)); + keys.stefan_lin = 1.0; + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount ("MAGIC:4", + &brut)); + GNUNET_log_skip (1, + GNUNET_NO); + GNUNET_assert (GNUNET_SYSERR == + TALER_EXCHANGE_keys_stefan_b2n (&keys, + &brut, + &net)); + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount ("MAGIC:4", + &net)); + GNUNET_log_skip (1, + GNUNET_NO); + GNUNET_assert (GNUNET_SYSERR == + TALER_EXCHANGE_keys_stefan_n2b (&keys, + &net, + &brut)); + GNUNET_assert (0 == GNUNET_get_log_skip ()); + keys.stefan_lin = 0.1; + + /* try various values for lin and log STEFAN values */ + for (unsigned int li = 1; li < 13; li += 1) + { + keys.stefan_lin = 1.0 * li / 100.0; + + for (unsigned int lx = 1; lx < 100; lx += 1) + { + keys.stefan_log.fraction = lx * TALER_AMOUNT_FRAC_BASE / 100; + + /* Check brutto-to-netto is stable */ + for (unsigned int i = 0; i<10; i++) + { + struct TALER_Amount rval; + + brut.value = i; + brut.fraction = i * TALER_AMOUNT_FRAC_BASE / 10; + GNUNET_assert (GNUNET_SYSERR != + TALER_EXCHANGE_keys_stefan_b2n (&keys, + &brut, + &net)); + GNUNET_assert (GNUNET_SYSERR != + TALER_EXCHANGE_keys_stefan_n2b (&keys, + &net, + &rval)); + if (TALER_amount_is_zero (&net)) + GNUNET_assert (TALER_amount_is_zero (&rval)); + else + { + GNUNET_assert (amount_close (&brut, + &rval)); + TALER_EXCHANGE_keys_stefan_round (&keys, + &rval); + GNUNET_assert (amount_close (&brut, + &rval)); + } + } + + /* Check netto-to-brutto is stable */ + for (unsigned int i = 0; i<10; i++) + { + struct TALER_Amount rval; + + net.value = i; + net.fraction = i * TALER_AMOUNT_FRAC_BASE / 10; + GNUNET_assert (GNUNET_SYSERR != + TALER_EXCHANGE_keys_stefan_n2b (&keys, + &net, + &brut)); + GNUNET_assert (GNUNET_SYSERR != + TALER_EXCHANGE_keys_stefan_b2n (&keys, + &brut, + &rval)); + GNUNET_assert (amount_close (&net, + &rval)); + TALER_EXCHANGE_keys_stefan_round (&keys, + &rval); + GNUNET_assert (amount_close (&net, + &rval)); + } + } + } + return 0; +} |